\n \u003Cdiv class=\"comment-author\">${comment.name} \u003Ca title=\"permalink\" href=\"#\">#\u003C/a>\u003C/div>\n \u003Cdiv class=\"cmeta\">Commented on ${comment.dateCreated.format('MM/dd/yyyy hh:mm')}\u003C/div>\n ${comment.content}\n \u003Cdiv class=\"clearfix\">\u003C/div>\n \u003C/li>\n \u003C/ul>\n\u003C/div>\n\u003Cdiv class=\"clear\">\u003C/div>\n",[676,5222,5223,5238,5253,5267,5275,5310,5326,5388,5432,5452,5457,5476,5485,5493,5501],{"__ignoreMap":674},[679,5224,5225,5227,5229,5231,5233,5236],{"class":681,"line":682},[679,5226,4505],{"class":693},[679,5228,4509],{"class":4508},[679,5230,4512],{"class":880},[679,5232,686],{"class":693},[679,5234,5235],{"class":689},"\"comments well\"",[679,5237,4519],{"class":693},[679,5239,5240,5242,5244,5246,5248,5251],{"class":681,"line":790},[679,5241,4524],{"class":693},[679,5243,4509],{"class":4508},[679,5245,4512],{"class":880},[679,5247,686],{"class":693},[679,5249,5250],{"class":689},"\"title\"",[679,5252,4519],{"class":693},[679,5254,5255,5257,5260,5263,5265],{"class":681,"line":892},[679,5256,4904],{"class":693},[679,5258,5259],{"class":4508},"h4",[679,5261,5262],{"class":693},">${post.comments.size()} Comments\u003C/",[679,5264,5259],{"class":4508},[679,5266,4519],{"class":693},[679,5268,5269,5271,5273],{"class":681,"line":901},[679,5270,4577],{"class":693},[679,5272,4509],{"class":4508},[679,5274,4519],{"class":693},[679,5276,5277,5279,5281,5284,5286,5289,5291,5293,5295,5297,5299,5301,5304,5306,5308],{"class":681,"line":909},[679,5278,4524],{"class":693},[679,5280,812],{"class":4508},[679,5282,5283],{"class":880}," name",[679,5285,686],{"class":693},[679,5287,5288],{"class":689},"\"comments\"",[679,5290,4563],{"class":693},[679,5292,812],{"class":4508},[679,5294,4545],{"class":693},[679,5296,812],{"class":4508},[679,5298,5283],{"class":880},[679,5300,686],{"class":693},[679,5302,5303],{"class":689},"\"${comment.id}\"",[679,5305,4563],{"class":693},[679,5307,812],{"class":4508},[679,5309,4519],{"class":693},[679,5311,5312,5314,5317,5319,5321,5324],{"class":681,"line":918},[679,5313,4524],{"class":693},[679,5315,5316],{"class":4508},"ul",[679,5318,4512],{"class":880},[679,5320,686],{"class":693},[679,5322,5323],{"class":689},"\"comment-list\"",[679,5325,4519],{"class":693},[679,5327,5328,5330,5333,5335,5337,5340,5342,5344,5346,5348,5351,5354,5356,5359,5362,5364,5367,5370,5373,5375,5378,5381,5383,5385],{"class":681,"line":935},[679,5329,4904],{"class":693},[679,5331,5332],{"class":4508},"li",[679,5334,4512],{"class":880},[679,5336,686],{"class":693},[679,5338,5339],{"class":689},"\"comment\"",[679,5341,4545],{"class":693},[679,5343,660],{"class":4508},[679,5345,4512],{"class":880},[679,5347,686],{"class":693},[679,5349,5350],{"class":689},"\"avatar pull-left\"",[679,5352,5353],{"class":880}," title",[679,5355,686],{"class":693},[679,5357,5358],{"class":689},"\"${comment.name}\"",[679,5360,5361],{"class":880}," src",[679,5363,686],{"class":693},[679,5365,5366],{"class":689},"\"http://www.gravatar.com/avatar/${comment.emailHash}?s=64",[679,5368,5369],{"class":931},"&",[679,5371,5372],{"class":689},"r=pg",[679,5374,5369],{"class":931},[679,5376,5377],{"class":689},"d=mm\"",[679,5379,5380],{"class":880}," alt",[679,5382,686],{"class":693},[679,5384,3579],{"class":689},[679,5386,5387],{"class":693}," />\n",[679,5389,5390,5393,5395,5397,5399,5402,5405,5407,5409,5411,5414,5416,5418,5421,5424,5426,5428,5430],{"class":681,"line":944},[679,5391,5392],{"class":693}," \u003C",[679,5394,4509],{"class":4508},[679,5396,4512],{"class":880},[679,5398,686],{"class":693},[679,5400,5401],{"class":689},"\"comment-author\"",[679,5403,5404],{"class":693},">${comment.name} \u003C",[679,5406,812],{"class":4508},[679,5408,5353],{"class":880},[679,5410,686],{"class":693},[679,5412,5413],{"class":689},"\"permalink\"",[679,5415,4550],{"class":880},[679,5417,686],{"class":693},[679,5419,5420],{"class":689},"\"#\"",[679,5422,5423],{"class":693},">#\u003C/",[679,5425,812],{"class":4508},[679,5427,4563],{"class":693},[679,5429,4509],{"class":4508},[679,5431,4519],{"class":693},[679,5433,5434,5436,5438,5440,5442,5445,5448,5450],{"class":681,"line":959},[679,5435,5392],{"class":693},[679,5437,4509],{"class":4508},[679,5439,4512],{"class":880},[679,5441,686],{"class":693},[679,5443,5444],{"class":689},"\"cmeta\"",[679,5446,5447],{"class":693},">Commented on ${comment.dateCreated.format('MM/dd/yyyy hh:mm')}\u003C/",[679,5449,4509],{"class":4508},[679,5451,4519],{"class":693},[679,5453,5454],{"class":681,"line":964},[679,5455,5456],{"class":693}," ${comment.content}\n",[679,5458,5459,5461,5463,5465,5467,5470,5472,5474],{"class":681,"line":977},[679,5460,5392],{"class":693},[679,5462,4509],{"class":4508},[679,5464,4512],{"class":880},[679,5466,686],{"class":693},[679,5468,5469],{"class":689},"\"clearfix\"",[679,5471,4563],{"class":693},[679,5473,4509],{"class":4508},[679,5475,4519],{"class":693},[679,5477,5478,5481,5483],{"class":681,"line":982},[679,5479,5480],{"class":693}," \u003C/",[679,5482,5332],{"class":4508},[679,5484,4519],{"class":693},[679,5486,5487,5489,5491],{"class":681,"line":988},[679,5488,4577],{"class":693},[679,5490,5316],{"class":4508},[679,5492,4519],{"class":693},[679,5494,5495,5497,5499],{"class":681,"line":993},[679,5496,4586],{"class":693},[679,5498,4509],{"class":4508},[679,5500,4519],{"class":693},[679,5502,5503,5505,5507,5509,5511,5514,5516,5518],{"class":681,"line":2129},[679,5504,4505],{"class":693},[679,5506,4509],{"class":4508},[679,5508,4512],{"class":880},[679,5510,686],{"class":693},[679,5512,5513],{"class":689},"\"clear\"",[679,5515,4563],{"class":693},[679,5517,4509],{"class":4508},[679,5519,4519],{"class":693},[786,5521,5522],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":5524},[],"Gravatars in a Grails Application",{"slug":5527,"published":797,"date":5528,"tags":5529,"cover":849},"gravatars-on-a-grails-application","2014-04-08T14:04:00-04:00",[848,870],{"title":261,"description":5525},"blog/2014/04/08/gravatars-on-a-grails-application","Km68QeLMpoUCS-O1M1mE-NGLeYGm6Q2Vycfeh5WYWCA",{"id":5534,"title":258,"body":5535,"description":5822,"extension":793,"meta":5823,"navigation":797,"path":259,"seo":5828,"stem":5829,"__hash__":5830},"content/blog/2015/02/05/sql-server-exception-statement-not-return-result-set.md",{"type":648,"value":5536,"toc":5820},[5537,5546,5560,5620,5623,5674,5677,5682,5688,5747,5750,5814,5817],[651,5538,5539,5540,5545],{},"We are working on moving a fairly large Dynamic Java Web Project over to a Grails application. In this application we have a ton of stored procedures that we need to continue to use in our Grails app. Luckily it couldn't be easier to call a stored proc in Groovy. If you look at the ",[812,5541,5544],{"href":5542,"rel":5543},"http://groovy.codehaus.org/api/groovy/sql/Sql.html",[816],"groovy.sql.Sql"," class there are a ton of methods for this.",[651,5547,5548,5549,5552,5553,5556,5557,5559],{},"There are numerous calls methods but one of the easiest ways to get back a list is to use the rows method and pass in a ",[676,5550,5551],{},"gstring",". In this example we are calling a stored procedure called ",[676,5554,5555],{},"getFooStoredProc"," that takes 1 parameter and because this is a ",[676,5558,5551],{}," we can include our variable in our call.",[669,5561,5563],{"className":868,"code":5562,"language":870,"meta":674,"style":674},"def getFoo( String id ) {\n List results = sql.rows( \"{call getFooStoredProc($id)}\" )\n results\n}\n",[676,5564,5565,5582,5611,5616],{"__ignoreMap":674},[679,5566,5567,5569,5572,5574,5576,5579],{"class":681,"line":682},[679,5568,1323],{"class":685},[679,5570,5571],{"class":880}," getFoo",[679,5573,1234],{"class":693},[679,5575,4758],{"class":685},[679,5577,5578],{"class":2099}," id",[679,5580,5581],{"class":693}," ) {\n",[679,5583,5584,5587,5590,5592,5594,5596,5599,5602,5605,5608],{"class":681,"line":790},[679,5585,5586],{"class":685}," List",[679,5588,5589],{"class":693}," results ",[679,5591,686],{"class":685},[679,5593,2072],{"class":693},[679,5595,664],{"class":685},[679,5597,5598],{"class":693},"rows( ",[679,5600,5601],{"class":689},"\"{call getFooStoredProc(",[679,5603,5604],{"class":693},"$id",[679,5606,5607],{"class":689},")}\"",[679,5609,5610],{"class":693}," )\n",[679,5612,5613],{"class":681,"line":892},[679,5614,5615],{"class":693}," results\n",[679,5617,5618],{"class":681,"line":901},[679,5619,996],{"class":693},[651,5621,5622],{},"So I had this working for a ton of stored procedure calls already when I came across a very strange error yesterday.",[669,5624,5626],{"className":868,"code":5625,"language":870,"meta":674,"style":674},"def getBar( String id ) {\n List results = sql.rows( \"{call getBarStoredProc($id)}\" )\n results\n}\n",[676,5627,5628,5643,5666,5670],{"__ignoreMap":674},[679,5629,5630,5632,5635,5637,5639,5641],{"class":681,"line":682},[679,5631,1323],{"class":685},[679,5633,5634],{"class":880}," getBar",[679,5636,1234],{"class":693},[679,5638,4758],{"class":685},[679,5640,5578],{"class":2099},[679,5642,5581],{"class":693},[679,5644,5645,5647,5649,5651,5653,5655,5657,5660,5662,5664],{"class":681,"line":790},[679,5646,5586],{"class":685},[679,5648,5589],{"class":693},[679,5650,686],{"class":685},[679,5652,2072],{"class":693},[679,5654,664],{"class":685},[679,5656,5598],{"class":693},[679,5658,5659],{"class":689},"\"{call getBarStoredProc(",[679,5661,5604],{"class":693},[679,5663,5607],{"class":689},[679,5665,5610],{"class":693},[679,5667,5668],{"class":681,"line":892},[679,5669,5615],{"class":693},[679,5671,5672],{"class":681,"line":901},[679,5673,996],{"class":693},[651,5675,5676],{},"This looks almost identical to the call I made before but this time the code was throwing an exception.",[1004,5678,5679],{},[651,5680,5681],{},"SqlServerException: The Statement Did Not Return A Result Set",[651,5683,5684,5685],{},"I was really confused by this so I went to the database and the stored procedure with the same parameter was running just fine. After some digging around and talking it through with some coworkers we finally found the solution. As I said before this wasn't happening for stored procedures that I was converting before but that is because none of them were doing any inserts or updates. If you are doing any inserts/updates before the final select for whatever reason this error will be thrown. For the life of me I still don't understand why I just know that it happens. So you have 2 solutions to fix this. First you can update the stored procedure to ",[676,5686,5687],{},"SET NOTCOUNT ON",[669,5689,5691],{"className":671,"code":5690,"language":673,"meta":674,"style":674},"create procedure getBarStoredProc (\n @id int\n)\nAS\nBEGIN\nSET NOCOUNT ON\n...\nEND\n",[676,5692,5693,5704,5712,5716,5721,5726,5737,5742],{"__ignoreMap":674},[679,5694,5695,5698,5701],{"class":681,"line":682},[679,5696,5697],{"class":685},"create",[679,5699,5700],{"class":685}," procedure",[679,5702,5703],{"class":693}," getBarStoredProc (\n",[679,5705,5706,5709],{"class":681,"line":790},[679,5707,5708],{"class":693}," @id ",[679,5710,5711],{"class":685},"int\n",[679,5713,5714],{"class":681,"line":892},[679,5715,1339],{"class":693},[679,5717,5718],{"class":681,"line":901},[679,5719,5720],{"class":685},"AS\n",[679,5722,5723],{"class":681,"line":909},[679,5724,5725],{"class":685},"BEGIN\n",[679,5727,5728,5731,5734],{"class":681,"line":918},[679,5729,5730],{"class":685},"SET",[679,5732,5733],{"class":685}," NOCOUNT",[679,5735,5736],{"class":685}," ON\n",[679,5738,5739],{"class":681,"line":935},[679,5740,5741],{"class":693},"...\n",[679,5743,5744],{"class":681,"line":944},[679,5745,5746],{"class":685},"END\n",[651,5748,5749],{},"If you only have a few stored procedures this would be an ideal solution. In my case though I have way to many to do that. The good news is you can do this via code before that stored procedure is called by executing a sql statement before your stored procedure call.",[669,5751,5753],{"className":868,"code":5752,"language":870,"meta":674,"style":674},"def getBar( String id ) {\n sql.execute( \"SET NOCOUNT ON\" )\n List results = sql.rows( \"{call getBarStoredProc($id)}\" )\n results\n}\n",[676,5754,5755,5769,5784,5806,5810],{"__ignoreMap":674},[679,5756,5757,5759,5761,5763,5765,5767],{"class":681,"line":682},[679,5758,1323],{"class":685},[679,5760,5634],{"class":880},[679,5762,1234],{"class":693},[679,5764,4758],{"class":685},[679,5766,5578],{"class":2099},[679,5768,5581],{"class":693},[679,5770,5771,5774,5776,5779,5782],{"class":681,"line":790},[679,5772,5773],{"class":693}," sql",[679,5775,664],{"class":685},[679,5777,5778],{"class":693},"execute( ",[679,5780,5781],{"class":689},"\"SET NOCOUNT ON\"",[679,5783,5610],{"class":693},[679,5785,5786,5788,5790,5792,5794,5796,5798,5800,5802,5804],{"class":681,"line":892},[679,5787,5586],{"class":685},[679,5789,5589],{"class":693},[679,5791,686],{"class":685},[679,5793,2072],{"class":693},[679,5795,664],{"class":685},[679,5797,5598],{"class":693},[679,5799,5659],{"class":689},[679,5801,5604],{"class":693},[679,5803,5607],{"class":689},[679,5805,5610],{"class":693},[679,5807,5808],{"class":681,"line":901},[679,5809,5615],{"class":693},[679,5811,5812],{"class":681,"line":909},[679,5813,996],{"class":693},[651,5815,5816],{},"Hopefully this helps someone else who comes across this crazy issue.",[786,5818,5819],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":5821},[],"Using Stored Procedures in a Grails Application with Groovy",{"slug":5824,"published":797,"date":5825,"tags":5826,"cover":5827},"sql-server-exception-statement-not-return-result-set","2015-02-05T11:48:18-05:00",[870,673],"./data-cover.jpg",{"title":258,"description":5822},"blog/2015/02/05/sql-server-exception-statement-not-return-result-set","FHyuYteXLsa-R34YmXLJ2w5fS2dYriLrQL-25icSgkc",{"id":5832,"title":255,"body":5833,"description":255,"extension":793,"meta":5895,"navigation":797,"path":256,"seo":5901,"stem":5902,"__hash__":5903},"content/blog/2015/04/16/windows-kill-process-by-port-number.md",{"type":648,"value":5834,"toc":5893},[5835,5838,5846,5849,5864,5874,5879,5887,5890],[651,5836,5837],{},"In this short tutorial, we will look at how to kill a process by port number on Windows. I am doing a lot of Java development these days and I have about 5 different applications that I may fire up during a day and because they all run on my local machine most of them use port 8080 by default. Once in a while, I will forget to kill an application. When I try and start another application I will receive this error.",[651,5839,5840],{},[812,5841,5843],{"href":5842},"./port_already_in_use.png",[660,5844],{"alt":5845,"src":5842},"Windows Port Already in use",[651,5847,5848],{},"If you need to kill a process manually on Windows it's actually pretty easy. First, fire up a command prompt and type the following command.",[669,5850,5854],{"className":5851,"code":5852,"language":5853,"meta":674,"style":674},"language-bash shiki shiki-themes github-light github-dark github-light","> netstat -a -o -n\n","bash",[676,5855,5856],{"__ignoreMap":674},[679,5857,5858,5861],{"class":681,"line":682},[679,5859,5860],{"class":685},">",[679,5862,5863],{"class":693}," netstat -a -o -n\n",[651,5865,5866,5867,5873],{},"To kill the process we need to find the PID of the process in question. I just run down the list by port until I find port 8080 and here you will see the process id was 28344. ",[812,5868,5870],{"href":5869},"./netstat_1.png",[660,5871],{"alt":5872,"src":5869},"netstat_1"," Finally, with the PID we can run the following command to kill the process",[1004,5875,5876],{},[651,5877,5878],{},"taskkill /F /PID 28344",[651,5880,5881],{},[812,5882,5884],{"href":5883},"./netstat_2.png",[660,5885],{"alt":5886,"src":5883},"Windows Netstat",[651,5888,5889],{},"Pretty easy solution to a pretty simple problem. Hope this helps!",[786,5891,5892],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":5894},[],{"slug":5896,"published":797,"date":5897,"tags":5898,"cover":5900},"windows-kill-process-by-port-number","2015-04-16T10:35:05-04:00",[5899],"random","./windows-kill-port-cover.jpg",{"title":255,"description":255},"blog/2015/04/16/windows-kill-process-by-port-number","148k8IMN7K6WoKFHL04kYL4mkfiq7C6romwD0uAxPCo",{"id":5905,"title":252,"body":5906,"description":6011,"extension":793,"meta":6012,"navigation":797,"path":253,"seo":6018,"stem":6019,"__hash__":6020},"content/blog/2015/04/24/ready-player-one-review.md",{"type":648,"value":5907,"toc":6006},[5908,5913,5922,5931,5936,5939,5948,5951,5954,5958,5967,5973,5987,5991,5995,5998],[5909,5910,5912],"h3",{"id":5911},"the-book","The Book",[651,5914,5915,5916,5921],{},"I recently finished the book ",[812,5917,5920],{"href":5918,"rel":5919},"http://www.amazon.com/Ready-Player-One-Ernest-Cline/dp/0307887448/ref=sr_1_1?ie=UTF8&qid=1429924661&sr=8-1&keywords=ready+player+one",[816],"Ready Player One"," and I thought I would take this opportunity to give it a quick review. I don't give a ton of book reviews on this website but I enjoy talking about what is going on in my life and I love to hear from my readers so let's give it a try. I'm not going to give any spoilers away so if you haven't read the book, yet you can continue reading without worry.",[651,5923,5924,5925,5930],{},"I don't think I would have ever come across this book if it were not for ",[812,5926,5929],{"href":5927,"rel":5928},"https://www.lootcrate.com/",[816],"Loot Crate",". Loot Crate is wonderful monthly subscription box and I highly recommend checking them out. A couple of months ago I got a new Loot Crate that contained the book Ready Player One. It was a pretty small book, so it was right in my wheelhouse. Not having heard of or having any idea what this book was about I decided to read the synopsis that went something like this",[1004,5932,5933],{},[651,5934,5935],{},"In the year 2044, reality is an ugly place. The only time teenage Wade Watts really feels alive is when he's jacked into the virtual utopia known as the OASIS. Wade's devoted his life to studying the puzzles hidden within this world's digital confines—puzzles that are based on their creator's obsession with the pop culture of decades past and that promise massive power and fortune to whoever can unlock them. But when Wade stumbles upon the first clue, he finds himself beset by players willing to kill to take this ultimate prize. The race is on, and if Wade's going to survive, he'll have to win—and confront the real world he's always been so desperate to escape.",[651,5937,5938],{},"I was immediately hooked. In fact if you call yourself a geek I just can't imagine how you don't get excited reading that. The book centers around a virtual world called the Oasis. The Oasis was a way to escape the reality that at the time wasn't that great for a lot of people, so it was easy to see why it became so popular. When the book was written Virtual Reality technology was still in its infancy but what really excites me is that I could totally see this becoming reality. It's not that far-fetched to think that we could all be logging into a virtual reality to escape the real world in the not to distant future. Thrown in are some really great references to 80's pop culture and video games play a huge role throughout the book.",[651,5940,5941,5942,5947],{},"I mean the creator of the Oasis ends up turning the entire hunt into one large video game. I read about half the book, and then I just couldn't find any time to finish it. I really wanted to find out how this story ended, so I decided to check out the audiobook which is narrated by ",[812,5943,5946],{"href":5944,"rel":5945},"https://twitter.com/wilw",[816],"Wil Wheaton",". I run a lot on the treadmill at lunch so this was a perfect way to kill 2 birds with one stone. I made the decision that I would just start from the beginning, and I am really glad I did because I immediately learned a few more things the 2nd time through.",[651,5949,5950],{},"I fell in love with the characters and how they developed throughout the book. Wade Watts, our fearless leader shows such devotion to his quest. It was also a very suspenseful book. I found myself running on the treadmill at lunch pumping my fist in excitement at certain points in the quest.",[651,5952,5953],{},"I really don't want to give away too much, so I will leave it at this. The book is very well written and the story is packed with excitement from start to end. There are also some surprises throughout,a fantastic ending and a huge moral to the story.",[5909,5955,5957],{"id":5956},"the-movie","The Movie",[651,5959,5960,5961,5966],{},"When I finished the book I was just surfing the web and trying to find more info about the book when ",[812,5962,5965],{"href":5963,"rel":5964},"http://readyplayerone.com/",[816],"I came across the official website",". There were some really great fan submitted art that helped visualize certain aspects of the book. This is one of my favorite images and I don't want to give much away but if you have read the book you should recognize it.",[651,5968,5969],{},[660,5970],{"alt":5971,"src":5972},"Ready Player One Movie","/images/blog/2015/04/24/ready_player_one-300x244.jpg",[651,5974,5975,5976,5981,5982,664],{},"There was also a mention on the website that the book had plans to be turned into movie. This was exactly what I wanted to hear. This magical adventure is going to be turned into something I can see with my eyes I thought? My next thought was I really hope they find someone great to direct it. ",[812,5977,5980],{"href":5978,"rel":5979},"http://deadline.com/2015/03/ready-player-one-movie-steven-spielberg-ernest-cline-warner-bros-1201398299/",[816],"I found the story on Deadline"," and to my amazement Steven Spielberg had signed on to direct it. This was not only great news but actually pretty funny as a ton of his movies are referenced throughout the book. Here is a quick breakdown of the news by ",[812,5983,5986],{"href":5984,"rel":5985},"http://nerdist.com/",[816],"Nerdist",[5988,5989],"you-tube",{"id":5990},"zwcpM0mXoPg",[5909,5992,5994],{"id":5993},"read-it-listen-to-it-watch-it","Read It. Listen to It. Watch It.",[651,5996,5997],{},"I hope that this is the first you're hearing about this book and I have you excited to get this book ASAP. If you have heard of it before and were on the fence what are you waiting for? This is a must-read / listen and I would encourage all of you to find some time to enjoy this wonderful fantasy thrill ride.",[651,5999,6000,6001,1223],{},"If you have already read the book I would love to hear your thoughts on it. Please remember though people reading the comments may not have read it yet so don't give anything big away. If you read the book based on this review I would certainly like to hear that as well. All in all this is by far one of the best books I have ever read. Thank you ",[812,6002,6005],{"href":6003,"rel":6004},"https://twitter.com/erniecline",[816],"Ernest Cline",{"title":674,"searchDepth":790,"depth":790,"links":6007},[6008,6009,6010],{"id":5911,"depth":892,"text":5912},{"id":5956,"depth":892,"text":5957},{"id":5993,"depth":892,"text":5994},"A quick review of the book Ready Player One",{"slug":6013,"published":797,"date":6014,"tags":6015,"cover":6017},"ready-player-one-review","2015-04-24T17:00:00.000Z",[6016],"Books","bookcase-books-bookshelves-159711.jpg",{"title":252,"description":6011},"blog/2015/04/24/ready-player-one-review","3ae1_RUTnUt-BUe-1Hj-Qdwz4zbq-yfatouKQtaKeB8",{"id":6022,"title":249,"body":6023,"description":249,"extension":793,"meta":7051,"navigation":797,"path":250,"seo":7057,"stem":7058,"__hash__":7059},"content/blog/2015/10/23/spring-boot-application-annotation.md",{"type":648,"value":6024,"toc":7049},[6025,6028,6130,6133,6157,6160,6291,6294,7046],[651,6026,6027],{},"I have been working a lot with Spring Boot lately so you may see a flood of posts from me on the subject (you have been warned). If you are looking at older example and create new projects you may have come across the Spring Boot Application Annotation. If you create a simple project your main class might look something like this.",[669,6029,6031],{"className":4107,"code":6030,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class HelloSpringApplication {\n\n public static void main(String\\[\\] args) {\n SpringApplication.run(HelloSpringApplication.class, args);\n }\n}\n",[676,6032,6033,6040,6044,6051,6058,6062,6069,6081,6085,6111,6122,6126],{"__ignoreMap":674},[679,6034,6035,6037],{"class":681,"line":682},[679,6036,2543],{"class":685},[679,6038,6039],{"class":693}," com.therealdanvega;\n",[679,6041,6042],{"class":681,"line":790},[679,6043,889],{"emptyLinePlaceholder":797},[679,6045,6046,6048],{"class":681,"line":892},[679,6047,1999],{"class":685},[679,6049,6050],{"class":693}," org.springframework.boot.SpringApplication;\n",[679,6052,6053,6055],{"class":681,"line":901},[679,6054,1999],{"class":685},[679,6056,6057],{"class":693}," org.springframework.boot.autoconfigure.SpringBootApplication;\n",[679,6059,6060],{"class":681,"line":909},[679,6061,889],{"emptyLinePlaceholder":797},[679,6063,6064,6066],{"class":681,"line":918},[679,6065,4116],{"class":693},[679,6067,6068],{"class":685},"SpringBootApplication\n",[679,6070,6071,6074,6076,6079],{"class":681,"line":935},[679,6072,6073],{"class":685},"public",[679,6075,4512],{"class":685},[679,6077,6078],{"class":880}," HelloSpringApplication",[679,6080,884],{"class":693},[679,6082,6083],{"class":681,"line":944},[679,6084,889],{"emptyLinePlaceholder":797},[679,6086,6087,6090,6093,6096,6099,6101,6103,6106,6109],{"class":681,"line":959},[679,6088,6089],{"class":685}," public",[679,6091,6092],{"class":685}," static",[679,6094,6095],{"class":685}," void",[679,6097,6098],{"class":880}," main",[679,6100,745],{"class":693},[679,6102,4758],{"class":2099},[679,6104,6105],{"class":693},"\\[\\] ",[679,6107,6108],{"class":2099},"args",[679,6110,4390],{"class":693},[679,6112,6113,6116,6119],{"class":681,"line":964},[679,6114,6115],{"class":693}," SpringApplication.",[679,6117,6118],{"class":880},"run",[679,6120,6121],{"class":693},"(HelloSpringApplication.class, args);\n",[679,6123,6124],{"class":681,"line":977},[679,6125,985],{"class":693},[679,6127,6128],{"class":681,"line":982},[679,6129,996],{"class":693},[651,6131,6132],{},"The Spring Boot Application Annotation is a convenience annotation that adds all of the following:",[5316,6134,6135,6141,6147],{},[5332,6136,6137,6140],{},[676,6138,6139],{},"@Configuration"," tags the class as a source of bean definitions for the application context.",[5332,6142,6143,6146],{},[676,6144,6145],{},"@EnableAutoConfiguration"," tells Spring Boot to start adding beans based on class path settings, other beans, and various property settings.",[5332,6148,6149,6152,6153,6156],{},[676,6150,6151],{},"@ComponentScan"," tells Spring to look for other components, configurations, and services in the the ",[676,6154,6155],{},"com.therealdanvega"," package.",[651,6158,6159],{},"If those 3 annotations look familiar to you its because the new annotation is the same as using those 3. It became so common to have to use those 3 on the main application class that they created the new one. It is perfectly fine to put the other 3 back especially if you get into a situation where you need to pass arguments to one of the annotations. In the following example I moved the 3 annotations back and customized the component scan to search 2 different packages instead of just the current one.",[669,6161,6163],{"className":4107,"code":6162,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\n@EnableAutoConfiguration\n@ComponentScan({\"com.therealdanvega\",\"com.foo.bar\"})\npublic class HelloSpringApplication {\n\n public static void main(String\\[\\] args) {\n SpringApplication.run(HelloSpringApplication.class, args);\n }\n}\n",[676,6164,6165,6171,6175,6181,6188,6195,6202,6206,6213,6220,6241,6251,6255,6275,6283,6287],{"__ignoreMap":674},[679,6166,6167,6169],{"class":681,"line":682},[679,6168,2543],{"class":685},[679,6170,6039],{"class":693},[679,6172,6173],{"class":681,"line":790},[679,6174,889],{"emptyLinePlaceholder":797},[679,6176,6177,6179],{"class":681,"line":892},[679,6178,1999],{"class":685},[679,6180,6050],{"class":693},[679,6182,6183,6185],{"class":681,"line":901},[679,6184,1999],{"class":685},[679,6186,6187],{"class":693}," org.springframework.boot.autoconfigure.EnableAutoConfiguration;\n",[679,6189,6190,6192],{"class":681,"line":909},[679,6191,1999],{"class":685},[679,6193,6194],{"class":693}," org.springframework.context.annotation.ComponentScan;\n",[679,6196,6197,6199],{"class":681,"line":918},[679,6198,1999],{"class":685},[679,6200,6201],{"class":693}," org.springframework.context.annotation.Configuration;\n",[679,6203,6204],{"class":681,"line":935},[679,6205,889],{"emptyLinePlaceholder":797},[679,6207,6208,6210],{"class":681,"line":944},[679,6209,4116],{"class":693},[679,6211,6212],{"class":685},"Configuration\n",[679,6214,6215,6217],{"class":681,"line":959},[679,6216,4116],{"class":693},[679,6218,6219],{"class":685},"EnableAutoConfiguration\n",[679,6221,6222,6224,6227,6230,6233,6235,6238],{"class":681,"line":964},[679,6223,4116],{"class":693},[679,6225,6226],{"class":685},"ComponentScan",[679,6228,6229],{"class":693},"({",[679,6231,6232],{"class":689},"\"com.therealdanvega\"",[679,6234,1202],{"class":693},[679,6236,6237],{"class":689},"\"com.foo.bar\"",[679,6239,6240],{"class":693},"})\n",[679,6242,6243,6245,6247,6249],{"class":681,"line":977},[679,6244,6073],{"class":685},[679,6246,4512],{"class":685},[679,6248,6078],{"class":880},[679,6250,884],{"class":693},[679,6252,6253],{"class":681,"line":982},[679,6254,889],{"emptyLinePlaceholder":797},[679,6256,6257,6259,6261,6263,6265,6267,6269,6271,6273],{"class":681,"line":988},[679,6258,6089],{"class":685},[679,6260,6092],{"class":685},[679,6262,6095],{"class":685},[679,6264,6098],{"class":880},[679,6266,745],{"class":693},[679,6268,4758],{"class":2099},[679,6270,6105],{"class":693},[679,6272,6108],{"class":2099},[679,6274,4390],{"class":693},[679,6276,6277,6279,6281],{"class":681,"line":993},[679,6278,6115],{"class":693},[679,6280,6118],{"class":880},[679,6282,6121],{"class":693},[679,6284,6285],{"class":681,"line":2129},[679,6286,985],{"class":693},[679,6288,6289],{"class":681,"line":2140},[679,6290,996],{"class":693},[651,6292,6293],{},"If you ctrl + click (cmd+click on the mac) on the Spring Boot Application Annotation you will be taken to the class definition of that so you can see exactly what is going on there.",[669,6295,6297],{"className":4107,"code":6296,"language":4109,"meta":674,"style":674},"package org.springframework.boot.autoconfigure;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Inherited;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.ComponentScan;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.annotation.AliasFor;\n\n/\\*\\*\n \\* Indicates a {@link Configuration configuration} class that declares one or more\n \\* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration\n \\* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience\n \\* annotation that is equivalent to declaring {@code @Configuration},\n \\* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.\n \\*\n \\* @author Phillip Webb\n \\* @author Stephane Nicoll\n \\* @since 1.2.0\n \\*/\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@Configuration\n@EnableAutoConfiguration\n@ComponentScan\npublic @interface SpringBootApplication {\n\n /\\*\\*\n \\* Exclude specific auto-configuration classes such that they will never be applied.\n \\* @return the classes to exclude\n \\*/\n Class\u003C?>\\[\\] exclude() default {};\n\n /\\*\\*\n \\* Exclude specific auto-configuration class names such that they will never be\n \\* applied.\n \\* @return the class names to exclude\n \\* @since 1.3.0\n \\*/\n String\\[\\] excludeName() default {};\n\n /\\*\\*\n \\* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}\n \\* for a type-safe alternative to String-based package names.\n \\* @return base packages to scan\n \\* @since 1.3.0\n \\*/\n @AliasFor(annotation = ComponentScan.class, attribute = \"basePackages\")\n String\\[\\] scanBasePackages() default {};\n\n /\\*\\*\n \\* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to\n \\* scan for annotated components. The package of each class specified will be scanned.\n \\* \u003Cp>\n \\* Consider creating a special no-op marker class or interface in each package that\n \\* serves no purpose other than being referenced by this attribute.\n \\* @return base packages to scan\n \\* @since 1.3.0\n \\*/\n @AliasFor(annotation = ComponentScan.class, attribute = \"basePackageClasses\")\n Class\u003C?>\\[\\] scanBasePackageClasses() default {};\n\n}\n",[676,6298,6299,6306,6310,6317,6324,6331,6338,6345,6352,6356,6363,6369,6375,6382,6386,6398,6421,6442,6462,6482,6510,6516,6530,6543,6563,6569,6579,6589,6596,6603,6609,6615,6622,6636,6640,6652,6666,6679,6685,6707,6711,6723,6739,6744,6756,6761,6766,6775,6779,6791,6810,6832,6845,6862,6868,6897,6911,6915,6927,6951,6973,6982,7001,7006,7011,7015,7019,7024,7038,7042],{"__ignoreMap":674},[679,6300,6301,6303],{"class":681,"line":682},[679,6302,2543],{"class":685},[679,6304,6305],{"class":693}," org.springframework.boot.autoconfigure;\n",[679,6307,6308],{"class":681,"line":790},[679,6309,889],{"emptyLinePlaceholder":797},[679,6311,6312,6314],{"class":681,"line":892},[679,6313,1999],{"class":685},[679,6315,6316],{"class":693}," java.lang.annotation.Documented;\n",[679,6318,6319,6321],{"class":681,"line":901},[679,6320,1999],{"class":685},[679,6322,6323],{"class":693}," java.lang.annotation.ElementType;\n",[679,6325,6326,6328],{"class":681,"line":909},[679,6327,1999],{"class":685},[679,6329,6330],{"class":693}," java.lang.annotation.Inherited;\n",[679,6332,6333,6335],{"class":681,"line":918},[679,6334,1999],{"class":685},[679,6336,6337],{"class":693}," java.lang.annotation.Retention;\n",[679,6339,6340,6342],{"class":681,"line":935},[679,6341,1999],{"class":685},[679,6343,6344],{"class":693}," java.lang.annotation.RetentionPolicy;\n",[679,6346,6347,6349],{"class":681,"line":944},[679,6348,1999],{"class":685},[679,6350,6351],{"class":693}," java.lang.annotation.Target;\n",[679,6353,6354],{"class":681,"line":959},[679,6355,889],{"emptyLinePlaceholder":797},[679,6357,6358,6360],{"class":681,"line":964},[679,6359,1999],{"class":685},[679,6361,6362],{"class":693}," org.springframework.context.annotation.Bean;\n",[679,6364,6365,6367],{"class":681,"line":977},[679,6366,1999],{"class":685},[679,6368,6194],{"class":693},[679,6370,6371,6373],{"class":681,"line":982},[679,6372,1999],{"class":685},[679,6374,6201],{"class":693},[679,6376,6377,6379],{"class":681,"line":988},[679,6378,1999],{"class":685},[679,6380,6381],{"class":693}," org.springframework.core.annotation.AliasFor;\n",[679,6383,6384],{"class":681,"line":993},[679,6385,889],{"emptyLinePlaceholder":797},[679,6387,6388,6390,6392,6394,6396],{"class":681,"line":2129},[679,6389,4408],{"class":685},[679,6391,4147],{"class":693},[679,6393,4150],{"class":685},[679,6395,4147],{"class":693},[679,6397,4155],{"class":685},[679,6399,6400,6402,6404,6407,6410,6413,6415,6418],{"class":681,"line":2140},[679,6401,4721],{"class":693},[679,6403,4150],{"class":685},[679,6405,6406],{"class":693}," Indicates a {@",[679,6408,6409],{"class":685},"link",[679,6411,6412],{"class":693}," Configuration configuration} ",[679,6414,877],{"class":685},[679,6416,6417],{"class":880}," that",[679,6419,6420],{"class":693}," declares one or more\n",[679,6422,6423,6426,6428,6431,6434,6437,6439],{"class":681,"line":2145},[679,6424,6425],{"class":693}," \\* {@",[679,6427,6409],{"class":685},[679,6429,6430],{"class":693}," Bean @",[679,6432,6433],{"class":685},"Bean",[679,6435,6436],{"class":693},"} methods and also triggers {@",[679,6438,6409],{"class":685},[679,6440,6441],{"class":693}," EnableAutoConfiguration\n",[679,6443,6444,6446,6448,6451,6454,6457,6459],{"class":681,"line":2154},[679,6445,4721],{"class":693},[679,6447,4150],{"class":685},[679,6449,6450],{"class":693}," auto",[679,6452,6453],{"class":685},"-",[679,6455,6456],{"class":693},"configuration} and {@",[679,6458,6409],{"class":685},[679,6460,6461],{"class":693}," ComponentScan component scanning}. This is a convenience\n",[679,6463,6464,6466,6468,6471,6473,6476,6479],{"class":681,"line":2159},[679,6465,4721],{"class":693},[679,6467,4150],{"class":685},[679,6469,6470],{"class":693}," annotation that is equivalent to declaring {@",[679,6472,676],{"class":685},[679,6474,6475],{"class":693}," @",[679,6477,6478],{"class":685},"Configuration",[679,6480,6481],{"class":693},"},\n",[679,6483,6484,6486,6488,6491,6493,6495,6498,6501,6503,6505,6507],{"class":681,"line":2164},[679,6485,4721],{"class":693},[679,6487,4150],{"class":685},[679,6489,6490],{"class":693}," {@",[679,6492,676],{"class":685},[679,6494,6475],{"class":693},[679,6496,6497],{"class":685},"EnableAutoConfiguration",[679,6499,6500],{"class":693},"} and {@",[679,6502,676],{"class":685},[679,6504,6475],{"class":693},[679,6506,6226],{"class":685},[679,6508,6509],{"class":693},"}.\n",[679,6511,6512,6514],{"class":681,"line":3134},[679,6513,4721],{"class":693},[679,6515,4155],{"class":685},[679,6517,6518,6520,6522,6524,6527],{"class":681,"line":3139},[679,6519,4721],{"class":693},[679,6521,4150],{"class":685},[679,6523,6475],{"class":693},[679,6525,6526],{"class":685},"author",[679,6528,6529],{"class":693}," Phillip Webb\n",[679,6531,6532,6534,6536,6538,6540],{"class":681,"line":3144},[679,6533,4721],{"class":693},[679,6535,4150],{"class":685},[679,6537,6475],{"class":693},[679,6539,6526],{"class":685},[679,6541,6542],{"class":693}," Stephane Nicoll\n",[679,6544,6545,6547,6549,6551,6554,6557,6559],{"class":681,"line":3149},[679,6546,4721],{"class":693},[679,6548,4150],{"class":685},[679,6550,6475],{"class":693},[679,6552,6553],{"class":685},"since",[679,6555,6556],{"class":931}," 1.2",[679,6558,664],{"class":693},[679,6560,6562],{"class":6561},"shOWo","0\n",[679,6564,6565,6567],{"class":681,"line":3169},[679,6566,4721],{"class":693},[679,6568,4172],{"class":685},[679,6570,6571,6573,6576],{"class":681,"line":3185},[679,6572,4116],{"class":693},[679,6574,6575],{"class":685},"Target",[679,6577,6578],{"class":693},"(ElementType.TYPE)\n",[679,6580,6581,6583,6586],{"class":681,"line":3194},[679,6582,4116],{"class":693},[679,6584,6585],{"class":685},"Retention",[679,6587,6588],{"class":693},"(RetentionPolicy.RUNTIME)\n",[679,6590,6591,6593],{"class":681,"line":3199},[679,6592,4116],{"class":693},[679,6594,6595],{"class":685},"Documented\n",[679,6597,6598,6600],{"class":681,"line":3212},[679,6599,4116],{"class":693},[679,6601,6602],{"class":685},"Inherited\n",[679,6604,6605,6607],{"class":681,"line":3217},[679,6606,4116],{"class":693},[679,6608,6212],{"class":685},[679,6610,6611,6613],{"class":681,"line":3222},[679,6612,4116],{"class":693},[679,6614,6219],{"class":685},[679,6616,6617,6619],{"class":681,"line":3227},[679,6618,4116],{"class":693},[679,6620,6621],{"class":685},"ComponentScan\n",[679,6623,6624,6626,6628,6631,6634],{"class":681,"line":3232},[679,6625,6073],{"class":685},[679,6627,6475],{"class":693},[679,6629,6630],{"class":685},"interface",[679,6632,6633],{"class":685}," SpringBootApplication",[679,6635,884],{"class":693},[679,6637,6638],{"class":681,"line":3499},[679,6639,889],{"emptyLinePlaceholder":797},[679,6641,6642,6644,6646,6648,6650],{"class":681,"line":3509},[679,6643,4144],{"class":685},[679,6645,4147],{"class":693},[679,6647,4150],{"class":685},[679,6649,4147],{"class":693},[679,6651,4155],{"class":685},[679,6653,6654,6656,6658,6661,6663],{"class":681,"line":3516},[679,6655,4160],{"class":693},[679,6657,4150],{"class":685},[679,6659,6660],{"class":693}," Exclude specific auto",[679,6662,6453],{"class":685},[679,6664,6665],{"class":693},"configuration classes such that they will never be applied.\n",[679,6667,6668,6670,6672,6674,6676],{"class":681,"line":3531},[679,6669,4160],{"class":693},[679,6671,4150],{"class":685},[679,6673,6475],{"class":693},[679,6675,1307],{"class":685},[679,6677,6678],{"class":693}," the classes to exclude\n",[679,6680,6681,6683],{"class":681,"line":3536},[679,6682,4160],{"class":693},[679,6684,4172],{"class":685},[679,6686,6687,6690,6693,6695,6698,6701,6704],{"class":681,"line":3541},[679,6688,6689],{"class":693}," Class",[679,6691,6692],{"class":685},"\u003C?>",[679,6694,6105],{"class":693},[679,6696,6697],{"class":880},"exclude",[679,6699,6700],{"class":693},"() ",[679,6702,6703],{"class":685},"default",[679,6705,6706],{"class":693}," {};\n",[679,6708,6709],{"class":681,"line":3546},[679,6710,889],{"emptyLinePlaceholder":797},[679,6712,6713,6715,6717,6719,6721],{"class":681,"line":3551},[679,6714,4144],{"class":685},[679,6716,4147],{"class":693},[679,6718,4150],{"class":685},[679,6720,4147],{"class":693},[679,6722,4155],{"class":685},[679,6724,6725,6727,6729,6732,6734,6736],{"class":681,"line":3557},[679,6726,4160],{"class":693},[679,6728,4150],{"class":685},[679,6730,6731],{"class":693}," Exclude specific auto-configuration ",[679,6733,877],{"class":685},[679,6735,1732],{"class":880},[679,6737,6738],{"class":693}," such that they will never be\n",[679,6740,6741],{"class":681,"line":3567},[679,6742,6743],{"class":693}," \\* applied.\n",[679,6745,6746,6749,6751,6753],{"class":681,"line":3574},[679,6747,6748],{"class":693}," \\* @return the ",[679,6750,877],{"class":685},[679,6752,1732],{"class":880},[679,6754,6755],{"class":693}," to exclude\n",[679,6757,6758],{"class":681,"line":3589},[679,6759,6760],{"class":693}," \\* @since 1.3.0\n",[679,6762,6763],{"class":681,"line":3594},[679,6764,6765],{"class":693}," \\*/\n",[679,6767,6768,6771,6773],{"class":681,"line":3602},[679,6769,6770],{"class":693}," String\\[\\] excludeName() ",[679,6772,6703],{"class":685},[679,6774,6706],{"class":693},[679,6776,6777],{"class":681,"line":3608},[679,6778,889],{"emptyLinePlaceholder":797},[679,6780,6781,6783,6785,6787,6789],{"class":681,"line":3619},[679,6782,4144],{"class":685},[679,6784,4147],{"class":693},[679,6786,4150],{"class":685},[679,6788,4147],{"class":693},[679,6790,4155],{"class":685},[679,6792,6793,6795,6797,6800,6802,6805,6807],{"class":681,"line":3624},[679,6794,4160],{"class":693},[679,6796,4150],{"class":685},[679,6798,6799],{"class":693}," Base packages to scan ",[679,6801,1466],{"class":685},[679,6803,6804],{"class":693}," annotated components. Use {@",[679,6806,6409],{"class":685},[679,6808,6809],{"class":693}," #scanBasePackageClasses}\n",[679,6811,6812,6814,6816,6819,6822,6824,6827,6829],{"class":681,"line":3629},[679,6813,4160],{"class":693},[679,6815,4150],{"class":685},[679,6817,6818],{"class":685}," for",[679,6820,6821],{"class":693}," a type",[679,6823,6453],{"class":685},[679,6825,6826],{"class":693},"safe alternative to String",[679,6828,6453],{"class":685},[679,6830,6831],{"class":693},"based package names.\n",[679,6833,6834,6836,6838,6840,6842],{"class":681,"line":3639},[679,6835,4160],{"class":693},[679,6837,4150],{"class":685},[679,6839,6475],{"class":693},[679,6841,1307],{"class":685},[679,6843,6844],{"class":693}," base packages to scan\n",[679,6846,6847,6849,6851,6853,6855,6858,6860],{"class":681,"line":3644},[679,6848,4160],{"class":693},[679,6850,4150],{"class":685},[679,6852,6475],{"class":693},[679,6854,6553],{"class":685},[679,6856,6857],{"class":931}," 1.3",[679,6859,664],{"class":693},[679,6861,6562],{"class":6561},[679,6863,6864,6866],{"class":681,"line":3649},[679,6865,4160],{"class":693},[679,6867,4172],{"class":685},[679,6869,6870,6873,6876,6878,6881,6884,6887,6890,6892,6895],{"class":681,"line":3659},[679,6871,6872],{"class":693}," @",[679,6874,6875],{"class":685},"AliasFor",[679,6877,745],{"class":693},[679,6879,6880],{"class":931},"annotation",[679,6882,6883],{"class":685}," =",[679,6885,6886],{"class":693}," ComponentScan.class, ",[679,6888,6889],{"class":931},"attribute",[679,6891,6883],{"class":685},[679,6893,6894],{"class":689}," \"basePackages\"",[679,6896,1339],{"class":693},[679,6898,6899,6902,6905,6907,6909],{"class":681,"line":3664},[679,6900,6901],{"class":693}," String\\[\\] ",[679,6903,6904],{"class":880},"scanBasePackages",[679,6906,6700],{"class":693},[679,6908,6703],{"class":685},[679,6910,6706],{"class":693},[679,6912,6913],{"class":681,"line":3669},[679,6914,889],{"emptyLinePlaceholder":797},[679,6916,6917,6919,6921,6923,6925],{"class":681,"line":3679},[679,6918,4144],{"class":685},[679,6920,4147],{"class":693},[679,6922,4150],{"class":685},[679,6924,4147],{"class":693},[679,6926,4155],{"class":685},[679,6928,6929,6931,6933,6936,6938,6941,6943,6946,6948],{"class":681,"line":3684},[679,6930,4160],{"class":693},[679,6932,4150],{"class":685},[679,6934,6935],{"class":693}," Type",[679,6937,6453],{"class":685},[679,6939,6940],{"class":693},"safe alternative to {@",[679,6942,6409],{"class":685},[679,6944,6945],{"class":693}," #scanBasePackages} ",[679,6947,1466],{"class":685},[679,6949,6950],{"class":693}," specifying the packages to\n",[679,6952,6953,6955,6957,6960,6962,6965,6967,6970],{"class":681,"line":3689},[679,6954,4160],{"class":693},[679,6956,4150],{"class":685},[679,6958,6959],{"class":693}," scan ",[679,6961,1466],{"class":685},[679,6963,6964],{"class":693}," annotated components. The package of each ",[679,6966,877],{"class":685},[679,6968,6969],{"class":880}," specified",[679,6971,6972],{"class":693}," will be scanned.\n",[679,6974,6975,6978,6980],{"class":681,"line":3699},[679,6976,6977],{"class":693}," \\* \u003C",[679,6979,651],{"class":685},[679,6981,4519],{"class":693},[679,6983,6984,6987,6989,6992,6995,6998],{"class":681,"line":3704},[679,6985,6986],{"class":693}," \\* Consider creating a special no-op marker ",[679,6988,877],{"class":685},[679,6990,6991],{"class":880}," or",[679,6993,6994],{"class":685}," interface",[679,6996,6997],{"class":880}," in",[679,6999,7000],{"class":693}," each package that\n",[679,7002,7003],{"class":681,"line":3709},[679,7004,7005],{"class":693}," \\* serves no purpose other than being referenced by this attribute.\n",[679,7007,7008],{"class":681,"line":3719},[679,7009,7010],{"class":693}," \\* @return base packages to scan\n",[679,7012,7013],{"class":681,"line":3724},[679,7014,6760],{"class":693},[679,7016,7017],{"class":681,"line":3729},[679,7018,6765],{"class":693},[679,7020,7021],{"class":681,"line":3739},[679,7022,7023],{"class":693}," @AliasFor(annotation = ComponentScan.class, attribute = \"basePackageClasses\")\n",[679,7025,7026,7029,7031,7034,7036],{"class":681,"line":3744},[679,7027,7028],{"class":693}," Class\u003C",[679,7030,2381],{"class":685},[679,7032,7033],{"class":693},">\\[\\] scanBasePackageClasses() ",[679,7035,6703],{"class":685},[679,7037,6706],{"class":693},[679,7039,7040],{"class":681,"line":3749},[679,7041,889],{"emptyLinePlaceholder":797},[679,7043,7044],{"class":681,"line":3759},[679,7045,996],{"class":693},[786,7047,7048],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}",{"title":674,"searchDepth":790,"depth":790,"links":7050},[],{"slug":7052,"published":797,"date":7053,"tags":7054,"cover":7056},"spring-boot-application-annotation","2015-10-23T09:08:37-04:00",[4109,7055],"spring","./access-code-connection-1181467.jpg",{"title":249,"description":249},"blog/2015/10/23/spring-boot-application-annotation","v8WhY4hgkj3WN_dteZZKszy0NesV9LtAxA763lzhURU",{"id":7061,"title":246,"body":7062,"description":246,"extension":793,"meta":9062,"navigation":797,"path":247,"seo":9067,"stem":9068,"__hash__":9069},"content/blog/2015/11/25/using-gorm-in-spring-boot.md",{"type":648,"value":7063,"toc":9054},[7064,7084,7087,7099,7106,7110,7125,7142,7151,7155,7293,7296,7304,7307,7311,7314,7339,7348,7355,7359,7362,7616,7619,7642,7645,7981,7984,7988,7991,8355,8358,8361,8364,8369,8372,8376,8379,8382,8398,8429,8438,8616,8619,8623,8640,8643,8646,9036,9039,9043,9051],[651,7065,7066,7067,7072,7073,7078,7079,664],{},"In this tutorial I am going to explain how to use ",[812,7068,7071],{"href":7069,"rel":7070},"http://grails.github.io/grails-data-mapping/latest/",[816],"GORM"," in Spring Boot. If you have been following along on this blog lately you know that I have been working a lot with ",[812,7074,7077],{"href":7075,"rel":7076},"http://projects.spring.io/spring-boot/",[816],"Spring Boot",". So much so that ",[812,7080,7083],{"href":7081,"rel":7082},"http://udemy.com/spring-boot-intro",[816],"I created a course on it",[651,7085,7086],{},"I really do like Spring Data in the limited time that I have had to play around with it and this is by no means an article against it. I like to play outside the sandbox sometimes and thats what we are going to do here. I have been working with Grails the past few years and really have grown to love GORM.",[651,7088,7089,7090,7092,7093,7098],{},"If you don't know what GORM is it stands for Grails Object Relational Mapper. ",[2939,7091,7071],{}," is the data access toolkit used by ",[812,7094,7097],{"href":7095,"rel":7096},"http://grails.org/",[816],"Grails"," and provides a rich set of APIs for accessing relational and non-relational data including implementations for Hibernate (SQL), MongoDB, Neo4j, Cassandra, Redis and an in-memory ConcurrentHashMap for testing. They just released a new website to go along with",[651,7100,7101,7105],{},[660,7102],{"alt":7103,"src":7104},"gorm5","./gorm5.png"," ",[5909,7107,7109],{"id":7108},"gorm-in-spring-boot-demo","GORM in Spring Boot Demo",[651,7111,7112,7113,7118,7119,7124],{},"The first thing you will want to do is create a new project using the ",[812,7114,7117],{"href":7115,"rel":7116},"https://start.spring.io/",[816],"Spring Initializr",". You will want to include the Web & H2 database dependencies. I am also selecting Groovy ( ",[812,7120,7123],{"href":7121,"rel":7122},"http://www.groovy-lang.org/",[816],"Apache Groovy FTW"," ) as my language and Gradle as my build system. I am using IntelliJ here but you can use whatever your comfortable using. When the project has loaded we are going to add a new dependency to our build.gradle.",[669,7126,7128],{"className":4107,"code":7127,"language":4109,"meta":674,"style":674},"compile(\"org.grails:gorm-hibernate4-spring-boot:5.0.0.RC1\")\n",[676,7129,7130],{"__ignoreMap":674},[679,7131,7132,7135,7137,7140],{"class":681,"line":682},[679,7133,7134],{"class":880},"compile",[679,7136,745],{"class":693},[679,7138,7139],{"class":689},"\"org.grails:gorm-hibernate4-spring-boot:5.0.0.RC1\"",[679,7141,1339],{"class":693},[651,7143,7144,7145,7150],{},"Now that we have the appropriate dependencies included we can begin building our application. I will talk through a couple GORM specific things in here but this is no way a GORM 101 tutorial. If you want to learn more about how to use GORM ",[812,7146,7149],{"href":7147,"rel":7148},"http://grails.github.io/grails-doc/latest/guide/GORM.html",[816],"please read through the documentation",". As a convention I like to keep my entities in a separate package so I will create a new one called domain. I will create a new Groovy class and call it Post.",[5909,7152,7154],{"id":7153},"creating-the-domain-object","Creating the domain object",[669,7156,7158],{"className":4107,"code":7157,"language":4109,"meta":674,"style":674},"package com.therealdanvega.domain\n\nimport grails.persistence.Entity\n\n@Entity\nclass Post {\n\n String title\n String body\n String teaser\n String slug\n Date postedOn\n\n static mapping = {\n version false\n body type: 'text'\n teaser type: 'text'\n }\n\n}\n",[676,7159,7160,7167,7171,7184,7188,7196,7206,7210,7218,7225,7232,7239,7253,7257,7264,7271,7276,7281,7285,7289],{"__ignoreMap":674},[679,7161,7162,7164],{"class":681,"line":682},[679,7163,2543],{"class":685},[679,7165,7166],{"class":693}," com.therealdanvega.domain\n",[679,7168,7169],{"class":681,"line":790},[679,7170,889],{"emptyLinePlaceholder":797},[679,7172,7173,7175,7178,7181],{"class":681,"line":892},[679,7174,1999],{"class":6561},[679,7176,7177],{"class":693}," grails.persistence.",[679,7179,7180],{"class":6561},"E",[679,7182,7183],{"class":693},"ntity\n",[679,7185,7186],{"class":681,"line":901},[679,7187,889],{"emptyLinePlaceholder":797},[679,7189,7190,7192,7194],{"class":681,"line":909},[679,7191,4116],{"class":693},[679,7193,7180],{"class":6561},[679,7195,7183],{"class":693},[679,7197,7198,7200,7203],{"class":681,"line":918},[679,7199,877],{"class":6561},[679,7201,7202],{"class":6561}," P",[679,7204,7205],{"class":693},"ost {\n",[679,7207,7208],{"class":681,"line":935},[679,7209,889],{"emptyLinePlaceholder":797},[679,7211,7212,7215],{"class":681,"line":944},[679,7213,7214],{"class":6561}," S",[679,7216,7217],{"class":693},"tring title\n",[679,7219,7220,7222],{"class":681,"line":959},[679,7221,7214],{"class":6561},[679,7223,7224],{"class":693},"tring body\n",[679,7226,7227,7229],{"class":681,"line":964},[679,7228,7214],{"class":6561},[679,7230,7231],{"class":693},"tring teaser\n",[679,7233,7234,7236],{"class":681,"line":977},[679,7235,7214],{"class":6561},[679,7237,7238],{"class":693},"tring slug\n",[679,7240,7241,7244,7247,7250],{"class":681,"line":982},[679,7242,7243],{"class":6561}," D",[679,7245,7246],{"class":693},"ate posted",[679,7248,7249],{"class":6561},"O",[679,7251,7252],{"class":693},"n\n",[679,7254,7255],{"class":681,"line":988},[679,7256,889],{"emptyLinePlaceholder":797},[679,7258,7259,7261],{"class":681,"line":993},[679,7260,967],{"class":6561},[679,7262,7263],{"class":693}," mapping = {\n",[679,7265,7266,7269],{"class":681,"line":2129},[679,7267,7268],{"class":693}," version ",[679,7270,2649],{"class":6561},[679,7272,7273],{"class":681,"line":2140},[679,7274,7275],{"class":693}," body type: 'text'\n",[679,7277,7278],{"class":681,"line":2145},[679,7279,7280],{"class":693}," teaser type: 'text'\n",[679,7282,7283],{"class":681,"line":2154},[679,7284,985],{"class":693},[679,7286,7287],{"class":681,"line":2159},[679,7288,889],{"emptyLinePlaceholder":797},[679,7290,7291],{"class":681,"line":2164},[679,7292,996],{"class":693},[651,7294,7295],{},"When we create domain objects in Grails we use a convention of placing them in a specific location. In Spring Boot we don't have that convention so we need a way to mark this class as an domain class and we can do so by using the @Entity annotation. The default convention by GORM is to use the class name as the table name. If the class was separated by camel case \"BlogPosts\" the table name would be \"blog_posts\". You can override that by using the mapping closure but we are just going to leave it as is.",[651,7297,7298,7299,7303],{},"You also might of noticed that we didn't import the Date class and that is because Groovy imports that along with a bunch of others by default. Finally we have some specific information about the body and teaser. By default a String data type would be a ",[7300,7301,7302],"em",{},"varchar"," and we actually want these columns to hold a bunch of text.",[651,7305,7306],{},"In Spring Data we would do something similar and at this time we would now create a new repository. In GORM we don't have to do that and the domain object itself is going to give us the power to do basic CRUD, dynamic finders and so much more. I would read through the docs to learn more about what GORM can do but the good news for you is all we had to do was create that domain object to get it all.",[5909,7308,7310],{"id":7309},"running-the-application","Running the application",[651,7312,7313],{},"Before we go creating a controller we should test out our changes. Before we can do that lets open up our application.properties file and add the 2 lines of configuration.",[669,7315,7317],{"className":4107,"code":7316,"language":4109,"meta":674,"style":674},"spring.h2.console.enabled=true\nspring.h2.console.path=/console\n",[676,7318,7319,7328],{"__ignoreMap":674},[679,7320,7321,7324,7326],{"class":681,"line":682},[679,7322,7323],{"class":693},"spring.h2.console.enabled",[679,7325,686],{"class":685},[679,7327,5134],{"class":931},[679,7329,7330,7333,7336],{"class":681,"line":790},[679,7331,7332],{"class":693},"spring.h2.console.path",[679,7334,7335],{"class":685},"=/",[679,7337,7338],{"class":693},"console\n",[651,7340,7341,7342,7347],{},"This allows us to hit the H2 web console and see our brand new table created. Run the application and visit ",[812,7343,7346],{"href":7344,"rel":7345},"http://localhost:8080/console",[816],"http://localhost:8080/console ","to see the POST table created.",[651,7349,7350,7354],{},[660,7351],{"alt":7352,"src":7353},"post_table","./post_table.png"," ",[5909,7356,7358],{"id":7357},"accessing-the-data","Accessing the data",[651,7360,7361],{},"Now that we have our domain object we need to create a controller and some mappings to grab some data from the database. Enforcing some good standards here we are going to move our business logic (data retrieval) to a service and we are going to program to an interface.",[669,7363,7365],{"className":4107,"code":7364,"language":4109,"meta":674,"style":674},"package com.therealdanvega.service\n\nimport com.therealdanvega.domain.Post\n\ninterface PostService {\n\n ArrayList\u003CPost> list()\n\n Post read()\n}\n\npackage com.therealdanvega.service\n\nimport com.therealdanvega.domain.Post\nimport org.springframework.stereotype.Service\n\n@Service('postService')\nclass PostServiceImpl implements PostService {\n\n @Override\n ArrayList\u003CPost> list() {\n Post.list()\n }\n\n @Override\n Post read(int id) {\n Post.get(id)\n }\n\n}\n",[676,7366,7367,7374,7378,7391,7395,7410,7414,7433,7437,7445,7449,7453,7459,7463,7473,7485,7489,7503,7533,7537,7546,7561,7569,7573,7577,7585,7597,7604,7608,7612],{"__ignoreMap":674},[679,7368,7369,7371],{"class":681,"line":682},[679,7370,2543],{"class":685},[679,7372,7373],{"class":693}," com.therealdanvega.service\n",[679,7375,7376],{"class":681,"line":790},[679,7377,889],{"emptyLinePlaceholder":797},[679,7379,7380,7382,7385,7388],{"class":681,"line":892},[679,7381,1999],{"class":6561},[679,7383,7384],{"class":693}," com.therealdanvega.domain.",[679,7386,7387],{"class":6561},"P",[679,7389,7390],{"class":693},"ost\n",[679,7392,7393],{"class":681,"line":901},[679,7394,889],{"emptyLinePlaceholder":797},[679,7396,7397,7399,7401,7404,7407],{"class":681,"line":909},[679,7398,6630],{"class":6561},[679,7400,7202],{"class":6561},[679,7402,7403],{"class":693},"ost",[679,7405,7406],{"class":6561},"S",[679,7408,7409],{"class":693},"ervice {\n",[679,7411,7412],{"class":681,"line":918},[679,7413,889],{"emptyLinePlaceholder":797},[679,7415,7416,7419,7422,7425,7428,7430],{"class":681,"line":935},[679,7417,7418],{"class":6561}," A",[679,7420,7421],{"class":693},"rray",[679,7423,7424],{"class":6561},"L",[679,7426,7427],{"class":693},"ist\u003C",[679,7429,7387],{"class":6561},[679,7431,7432],{"class":693},"ost> list()\n",[679,7434,7435],{"class":681,"line":944},[679,7436,889],{"emptyLinePlaceholder":797},[679,7438,7439,7442],{"class":681,"line":959},[679,7440,7441],{"class":6561}," P",[679,7443,7444],{"class":693},"ost read()\n",[679,7446,7447],{"class":681,"line":964},[679,7448,996],{"class":693},[679,7450,7451],{"class":681,"line":977},[679,7452,889],{"emptyLinePlaceholder":797},[679,7454,7455,7457],{"class":681,"line":982},[679,7456,2543],{"class":6561},[679,7458,7373],{"class":693},[679,7460,7461],{"class":681,"line":988},[679,7462,889],{"emptyLinePlaceholder":797},[679,7464,7465,7467,7469,7471],{"class":681,"line":993},[679,7466,1999],{"class":6561},[679,7468,7384],{"class":693},[679,7470,7387],{"class":6561},[679,7472,7390],{"class":693},[679,7474,7475,7477,7480,7482],{"class":681,"line":2129},[679,7476,1999],{"class":6561},[679,7478,7479],{"class":693}," org.springframework.stereotype.",[679,7481,7406],{"class":6561},[679,7483,7484],{"class":693},"ervice\n",[679,7486,7487],{"class":681,"line":2140},[679,7488,889],{"emptyLinePlaceholder":797},[679,7490,7491,7493,7495,7498,7500],{"class":681,"line":2145},[679,7492,4116],{"class":693},[679,7494,7406],{"class":6561},[679,7496,7497],{"class":693},"ervice('post",[679,7499,7406],{"class":6561},[679,7501,7502],{"class":693},"ervice')\n",[679,7504,7505,7507,7509,7511,7513,7516,7519,7522,7525,7527,7529,7531],{"class":681,"line":2154},[679,7506,877],{"class":6561},[679,7508,7202],{"class":6561},[679,7510,7403],{"class":693},[679,7512,7406],{"class":6561},[679,7514,7515],{"class":693},"ervice",[679,7517,7518],{"class":6561},"I",[679,7520,7521],{"class":693},"mpl ",[679,7523,7524],{"class":6561},"implements",[679,7526,7202],{"class":6561},[679,7528,7403],{"class":693},[679,7530,7406],{"class":6561},[679,7532,7409],{"class":693},[679,7534,7535],{"class":681,"line":2159},[679,7536,889],{"emptyLinePlaceholder":797},[679,7538,7539,7541,7543],{"class":681,"line":2164},[679,7540,6872],{"class":693},[679,7542,7249],{"class":6561},[679,7544,7545],{"class":693},"verride\n",[679,7547,7548,7550,7552,7554,7556,7558],{"class":681,"line":3134},[679,7549,7418],{"class":6561},[679,7551,7421],{"class":693},[679,7553,7424],{"class":6561},[679,7555,7427],{"class":693},[679,7557,7387],{"class":6561},[679,7559,7560],{"class":693},"ost> list() {\n",[679,7562,7563,7566],{"class":681,"line":3139},[679,7564,7565],{"class":6561}," P",[679,7567,7568],{"class":693},"ost.list()\n",[679,7570,7571],{"class":681,"line":3144},[679,7572,985],{"class":693},[679,7574,7575],{"class":681,"line":3149},[679,7576,889],{"emptyLinePlaceholder":797},[679,7578,7579,7581,7583],{"class":681,"line":3169},[679,7580,6872],{"class":693},[679,7582,7249],{"class":6561},[679,7584,7545],{"class":693},[679,7586,7587,7589,7592,7594],{"class":681,"line":3185},[679,7588,7441],{"class":6561},[679,7590,7591],{"class":693},"ost read(",[679,7593,1078],{"class":6561},[679,7595,7596],{"class":693}," id) {\n",[679,7598,7599,7601],{"class":681,"line":3194},[679,7600,7565],{"class":6561},[679,7602,7603],{"class":693},"ost.get(id)\n",[679,7605,7606],{"class":681,"line":3199},[679,7607,985],{"class":693},[679,7609,7610],{"class":681,"line":3212},[679,7611,889],{"emptyLinePlaceholder":797},[679,7613,7614],{"class":681,"line":3217},[679,7615,996],{"class":693},[651,7617,7618],{},"Again if you're not familiar with GORM there is some dynamic programming going on with our domain object that gives us methods like",[5316,7620,7621,7624,7627,7630,7633,7636,7639],{},[5332,7622,7623],{},"list",[5332,7625,7626],{},"get",[5332,7628,7629],{},"save",[5332,7631,7632],{},"delete",[5332,7634,7635],{},"findWhere",[5332,7637,7638],{},"findByTitle",[5332,7640,7641],{},"etc...",[651,7643,7644],{},"Finally we create a controller with some mappings that simply call our service to get the data.",[669,7646,7648],{"className":4107,"code":7647,"language":4109,"meta":674,"style":674},"package com.therealdanvega.controller\n\nimport com.therealdanvega.service.PostService\nimport com.therealdanvega.service.PostServiceImpl\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.web.bind.annotation.PathVariable\nimport org.springframework.web.bind.annotation.RequestMapping\nimport org.springframework.web.bind.annotation.RestController\n\n@RestController\n@RequestMapping(\"/posts\")\nclass PostController {\n\n PostService postService\n\n @Autowired\n PostController(PostService postService) {\n this.postService = postService\n }\n\n @RequestMapping(\"/\")\n public String list(){\n postService.list()\n }\n\n @RequestMapping(\"/{id}\")\n public String get(@PathVariable(value = \"id\") int id){\n postService.get(id)\n }\n\n}\n",[676,7649,7650,7657,7661,7676,7695,7708,7726,7744,7761,7765,7777,7790,7803,7807,7822,7826,7834,7858,7875,7879,7883,7896,7906,7916,7920,7924,7937,7960,7969,7973,7977],{"__ignoreMap":674},[679,7651,7652,7654],{"class":681,"line":682},[679,7653,2543],{"class":685},[679,7655,7656],{"class":693}," com.therealdanvega.controller\n",[679,7658,7659],{"class":681,"line":790},[679,7660,889],{"emptyLinePlaceholder":797},[679,7662,7663,7665,7668,7670,7672,7674],{"class":681,"line":892},[679,7664,1999],{"class":6561},[679,7666,7667],{"class":693}," com.therealdanvega.service.",[679,7669,7387],{"class":6561},[679,7671,7403],{"class":693},[679,7673,7406],{"class":6561},[679,7675,7484],{"class":693},[679,7677,7678,7680,7682,7684,7686,7688,7690,7692],{"class":681,"line":901},[679,7679,1999],{"class":6561},[679,7681,7667],{"class":693},[679,7683,7387],{"class":6561},[679,7685,7403],{"class":693},[679,7687,7406],{"class":6561},[679,7689,7515],{"class":693},[679,7691,7518],{"class":6561},[679,7693,7694],{"class":693},"mpl\n",[679,7696,7697,7699,7702,7705],{"class":681,"line":909},[679,7698,1999],{"class":6561},[679,7700,7701],{"class":693}," org.springframework.beans.factory.annotation.",[679,7703,7704],{"class":6561},"A",[679,7706,7707],{"class":693},"utowired\n",[679,7709,7710,7712,7715,7717,7720,7723],{"class":681,"line":918},[679,7711,1999],{"class":6561},[679,7713,7714],{"class":693}," org.springframework.web.bind.annotation.",[679,7716,7387],{"class":6561},[679,7718,7719],{"class":693},"ath",[679,7721,7722],{"class":6561},"V",[679,7724,7725],{"class":693},"ariable\n",[679,7727,7728,7730,7732,7735,7738,7741],{"class":681,"line":935},[679,7729,1999],{"class":6561},[679,7731,7714],{"class":693},[679,7733,7734],{"class":6561},"R",[679,7736,7737],{"class":693},"equest",[679,7739,7740],{"class":6561},"M",[679,7742,7743],{"class":693},"apping\n",[679,7745,7746,7748,7750,7752,7755,7758],{"class":681,"line":944},[679,7747,1999],{"class":6561},[679,7749,7714],{"class":693},[679,7751,7734],{"class":6561},[679,7753,7754],{"class":693},"est",[679,7756,7757],{"class":6561},"C",[679,7759,7760],{"class":693},"ontroller\n",[679,7762,7763],{"class":681,"line":959},[679,7764,889],{"emptyLinePlaceholder":797},[679,7766,7767,7769,7771,7773,7775],{"class":681,"line":964},[679,7768,4116],{"class":693},[679,7770,7734],{"class":6561},[679,7772,7754],{"class":693},[679,7774,7757],{"class":6561},[679,7776,7760],{"class":693},[679,7778,7779,7781,7783,7785,7787],{"class":681,"line":977},[679,7780,4116],{"class":693},[679,7782,7734],{"class":6561},[679,7784,7737],{"class":693},[679,7786,7740],{"class":6561},[679,7788,7789],{"class":693},"apping(\"/posts\")\n",[679,7791,7792,7794,7796,7798,7800],{"class":681,"line":982},[679,7793,877],{"class":6561},[679,7795,7202],{"class":6561},[679,7797,7403],{"class":693},[679,7799,7757],{"class":6561},[679,7801,7802],{"class":693},"ontroller {\n",[679,7804,7805],{"class":681,"line":988},[679,7806,889],{"emptyLinePlaceholder":797},[679,7808,7809,7811,7813,7815,7818,7820],{"class":681,"line":993},[679,7810,7441],{"class":6561},[679,7812,7403],{"class":693},[679,7814,7406],{"class":6561},[679,7816,7817],{"class":693},"ervice post",[679,7819,7406],{"class":6561},[679,7821,7484],{"class":693},[679,7823,7824],{"class":681,"line":2129},[679,7825,889],{"emptyLinePlaceholder":797},[679,7827,7828,7830,7832],{"class":681,"line":2140},[679,7829,6872],{"class":693},[679,7831,7704],{"class":6561},[679,7833,7707],{"class":693},[679,7835,7836,7838,7840,7842,7845,7847,7849,7851,7853,7855],{"class":681,"line":2145},[679,7837,7441],{"class":6561},[679,7839,7403],{"class":693},[679,7841,7757],{"class":6561},[679,7843,7844],{"class":693},"ontroller(",[679,7846,7387],{"class":6561},[679,7848,7403],{"class":693},[679,7850,7406],{"class":6561},[679,7852,7817],{"class":693},[679,7854,7406],{"class":6561},[679,7856,7857],{"class":693},"ervice) {\n",[679,7859,7860,7863,7866,7868,7871,7873],{"class":681,"line":2154},[679,7861,7862],{"class":6561}," this",[679,7864,7865],{"class":693},".post",[679,7867,7406],{"class":6561},[679,7869,7870],{"class":693},"ervice = post",[679,7872,7406],{"class":6561},[679,7874,7484],{"class":693},[679,7876,7877],{"class":681,"line":2159},[679,7878,985],{"class":693},[679,7880,7881],{"class":681,"line":2164},[679,7882,889],{"emptyLinePlaceholder":797},[679,7884,7885,7887,7889,7891,7893],{"class":681,"line":3134},[679,7886,6872],{"class":693},[679,7888,7734],{"class":6561},[679,7890,7737],{"class":693},[679,7892,7740],{"class":6561},[679,7894,7895],{"class":693},"apping(\"/\")\n",[679,7897,7898,7900,7903],{"class":681,"line":3139},[679,7899,6089],{"class":6561},[679,7901,7902],{"class":6561}," S",[679,7904,7905],{"class":693},"tring list(){\n",[679,7907,7908,7911,7913],{"class":681,"line":3144},[679,7909,7910],{"class":693}," post",[679,7912,7406],{"class":6561},[679,7914,7915],{"class":693},"ervice.list()\n",[679,7917,7918],{"class":681,"line":3149},[679,7919,985],{"class":693},[679,7921,7922],{"class":681,"line":3169},[679,7923,889],{"emptyLinePlaceholder":797},[679,7925,7926,7928,7930,7932,7934],{"class":681,"line":3185},[679,7927,6872],{"class":693},[679,7929,7734],{"class":6561},[679,7931,7737],{"class":693},[679,7933,7740],{"class":6561},[679,7935,7936],{"class":693},"apping(\"/{id}\")\n",[679,7938,7939,7941,7943,7946,7948,7950,7952,7955,7957],{"class":681,"line":3194},[679,7940,6089],{"class":6561},[679,7942,7902],{"class":6561},[679,7944,7945],{"class":693},"tring get(@",[679,7947,7387],{"class":6561},[679,7949,7719],{"class":693},[679,7951,7722],{"class":6561},[679,7953,7954],{"class":693},"ariable(value = \"id\") ",[679,7956,1078],{"class":6561},[679,7958,7959],{"class":693}," id){\n",[679,7961,7962,7964,7966],{"class":681,"line":3199},[679,7963,7910],{"class":693},[679,7965,7406],{"class":6561},[679,7967,7968],{"class":693},"ervice.get(id)\n",[679,7970,7971],{"class":681,"line":3212},[679,7972,985],{"class":693},[679,7974,7975],{"class":681,"line":3217},[679,7976,889],{"emptyLinePlaceholder":797},[679,7978,7979],{"class":681,"line":3222},[679,7980,996],{"class":693},[651,7982,7983],{},"This would appear like everything we need to run our application right? I want you to go ahead and run this application now without looking below. What happened? Do you know why it happened?",[5909,7985,7987],{"id":7986},"the-hibernate-session","The Hibernate Session",[651,7989,7990],{},"If you ran the example we have been building you should of seen an error similar to this one.",[669,7992,7996],{"className":7993,"code":7994,"language":7995,"meta":674,"style":674},"language-shell shiki shiki-themes github-light github-dark github-light","org.hibernate.HibernateException: No Session found for current thread\n at org.grails.orm.hibernate.GrailsSessionContext.currentSession(GrailsSessionContext.java:117) ~\\[grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na\\]\n at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014) ~\\[hibernate-core-4.3.11.Final.jar:4.3.11.Final\\]\n at org.grails.orm.hibernate.GrailsHibernateTemplate.getSession(GrailsHibernateTemplate.java:225) ~\\[grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na\\]\n at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:183) ~\\[grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na\\]\n at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:140) ~\\[grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na\\]\n at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:110) ~\\[grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na\\]\n at org.grails.orm.hibernate.HibernateGormStaticApi.list(HibernateGormStaticApi.groovy:76) ~\\[grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na\\]\n at org.grails.orm.hibernate.HibernateGormStaticApi.list(HibernateGormStaticApi.groovy:75) ~\\[grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na\\]\n at org.grails.datastore.gorm.GormEntity$Trait$Helper.list(GormEntity.groovy:639) ~\\[grails-datastore-gorm-5.0.0.RC1.jar:na\\]\n at org.grails.datastore.gorm.GormEntity$Trait$Helper$list.call(Unknown Source) ~\\[na:na\\]\n at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~\\[groovy-2.4.4.jar:2.4.4\\]\n at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) ~\\[groovy-2.4.4.jar:2.4.4\\]\n at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) ~\\[groovy-2.4.4.jar:2.4.4\\]\n at com.therealdanvega.domain.Post.list(Post.groovy) ~\\[main/:na\\]\n\n...\n","shell",[676,7997,7998,8020,8045,8068,8090,8112,8134,8155,8177,8198,8227,8258,8281,8303,8324,8347,8351],{"__ignoreMap":674},[679,7999,8000,8003,8006,8009,8012,8014,8017],{"class":681,"line":682},[679,8001,8002],{"class":880},"org.hibernate.HibernateException:",[679,8004,8005],{"class":689}," No",[679,8007,8008],{"class":689}," Session",[679,8010,8011],{"class":689}," found",[679,8013,6818],{"class":689},[679,8015,8016],{"class":689}," current",[679,8018,8019],{"class":689}," thread\n",[679,8021,8022,8025,8028,8030,8033,8035,8038,8040,8043],{"class":681,"line":790},[679,8023,8024],{"class":880}," at",[679,8026,8027],{"class":689}," org.grails.orm.hibernate.GrailsSessionContext.currentSession",[679,8029,745],{"class":693},[679,8031,8032],{"class":880},"GrailsSessionContext.java:117",[679,8034,2378],{"class":693},[679,8036,8037],{"class":689},"~",[679,8039,4471],{"class":931},[679,8041,8042],{"class":689},"grails-datastore-gorm-hibernate4-5.0.0.RC1.jar:na",[679,8044,1720],{"class":931},[679,8046,8047,8049,8052,8054,8057,8059,8061,8063,8066],{"class":681,"line":892},[679,8048,8024],{"class":880},[679,8050,8051],{"class":689}," org.hibernate.internal.SessionFactoryImpl.getCurrentSession",[679,8053,745],{"class":693},[679,8055,8056],{"class":880},"SessionFactoryImpl.java:1014",[679,8058,2378],{"class":693},[679,8060,8037],{"class":689},[679,8062,4471],{"class":931},[679,8064,8065],{"class":689},"hibernate-core-4.3.11.Final.jar:4.3.11.Final",[679,8067,1720],{"class":931},[679,8069,8070,8072,8075,8077,8080,8082,8084,8086,8088],{"class":681,"line":901},[679,8071,8024],{"class":880},[679,8073,8074],{"class":689}," org.grails.orm.hibernate.GrailsHibernateTemplate.getSession",[679,8076,745],{"class":693},[679,8078,8079],{"class":880},"GrailsHibernateTemplate.java:225",[679,8081,2378],{"class":693},[679,8083,8037],{"class":689},[679,8085,4471],{"class":931},[679,8087,8042],{"class":689},[679,8089,1720],{"class":931},[679,8091,8092,8094,8097,8099,8102,8104,8106,8108,8110],{"class":681,"line":909},[679,8093,8024],{"class":880},[679,8095,8096],{"class":689}," org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute",[679,8098,745],{"class":693},[679,8100,8101],{"class":880},"GrailsHibernateTemplate.java:183",[679,8103,2378],{"class":693},[679,8105,8037],{"class":689},[679,8107,4471],{"class":931},[679,8109,8042],{"class":689},[679,8111,1720],{"class":931},[679,8113,8114,8116,8119,8121,8124,8126,8128,8130,8132],{"class":681,"line":918},[679,8115,8024],{"class":880},[679,8117,8118],{"class":689}," org.grails.orm.hibernate.GrailsHibernateTemplate.execute",[679,8120,745],{"class":693},[679,8122,8123],{"class":880},"GrailsHibernateTemplate.java:140",[679,8125,2378],{"class":693},[679,8127,8037],{"class":689},[679,8129,4471],{"class":931},[679,8131,8042],{"class":689},[679,8133,1720],{"class":931},[679,8135,8136,8138,8140,8142,8145,8147,8149,8151,8153],{"class":681,"line":935},[679,8137,8024],{"class":880},[679,8139,8118],{"class":689},[679,8141,745],{"class":693},[679,8143,8144],{"class":880},"GrailsHibernateTemplate.java:110",[679,8146,2378],{"class":693},[679,8148,8037],{"class":689},[679,8150,4471],{"class":931},[679,8152,8042],{"class":689},[679,8154,1720],{"class":931},[679,8156,8157,8159,8162,8164,8167,8169,8171,8173,8175],{"class":681,"line":944},[679,8158,8024],{"class":880},[679,8160,8161],{"class":689}," org.grails.orm.hibernate.HibernateGormStaticApi.list",[679,8163,745],{"class":693},[679,8165,8166],{"class":880},"HibernateGormStaticApi.groovy:76",[679,8168,2378],{"class":693},[679,8170,8037],{"class":689},[679,8172,4471],{"class":931},[679,8174,8042],{"class":689},[679,8176,1720],{"class":931},[679,8178,8179,8181,8183,8185,8188,8190,8192,8194,8196],{"class":681,"line":959},[679,8180,8024],{"class":880},[679,8182,8161],{"class":689},[679,8184,745],{"class":693},[679,8186,8187],{"class":880},"HibernateGormStaticApi.groovy:75",[679,8189,2378],{"class":693},[679,8191,8037],{"class":689},[679,8193,4471],{"class":931},[679,8195,8042],{"class":689},[679,8197,1720],{"class":931},[679,8199,8200,8202,8205,8208,8211,8213,8216,8218,8220,8222,8225],{"class":681,"line":964},[679,8201,8024],{"class":880},[679,8203,8204],{"class":689}," org.grails.datastore.gorm.GormEntity",[679,8206,8207],{"class":693},"$Trait$Helper",[679,8209,8210],{"class":689},".list",[679,8212,745],{"class":693},[679,8214,8215],{"class":880},"GormEntity.groovy:639",[679,8217,2378],{"class":693},[679,8219,8037],{"class":689},[679,8221,4471],{"class":931},[679,8223,8224],{"class":689},"grails-datastore-gorm-5.0.0.RC1.jar:na",[679,8226,1720],{"class":931},[679,8228,8229,8231,8233,8236,8239,8241,8244,8247,8249,8251,8253,8256],{"class":681,"line":977},[679,8230,8024],{"class":880},[679,8232,8204],{"class":689},[679,8234,8235],{"class":693},"$Trait$Helper$list",[679,8237,8238],{"class":689},".call",[679,8240,745],{"class":693},[679,8242,8243],{"class":880},"Unknown",[679,8245,8246],{"class":689}," Source",[679,8248,2378],{"class":693},[679,8250,8037],{"class":689},[679,8252,4471],{"class":931},[679,8254,8255],{"class":689},"na:na",[679,8257,1720],{"class":931},[679,8259,8260,8262,8265,8267,8270,8272,8274,8276,8279],{"class":681,"line":982},[679,8261,8024],{"class":880},[679,8263,8264],{"class":689}," org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall",[679,8266,745],{"class":693},[679,8268,8269],{"class":880},"CallSiteArray.java:48",[679,8271,2378],{"class":693},[679,8273,8037],{"class":689},[679,8275,4471],{"class":931},[679,8277,8278],{"class":689},"groovy-2.4.4.jar:2.4.4",[679,8280,1720],{"class":931},[679,8282,8283,8285,8288,8290,8293,8295,8297,8299,8301],{"class":681,"line":988},[679,8284,8024],{"class":880},[679,8286,8287],{"class":689}," org.codehaus.groovy.runtime.callsite.AbstractCallSite.call",[679,8289,745],{"class":693},[679,8291,8292],{"class":880},"AbstractCallSite.java:113",[679,8294,2378],{"class":693},[679,8296,8037],{"class":689},[679,8298,4471],{"class":931},[679,8300,8278],{"class":689},[679,8302,1720],{"class":931},[679,8304,8305,8307,8309,8311,8314,8316,8318,8320,8322],{"class":681,"line":993},[679,8306,8024],{"class":880},[679,8308,8287],{"class":689},[679,8310,745],{"class":693},[679,8312,8313],{"class":880},"AbstractCallSite.java:125",[679,8315,2378],{"class":693},[679,8317,8037],{"class":689},[679,8319,4471],{"class":931},[679,8321,8278],{"class":689},[679,8323,1720],{"class":931},[679,8325,8326,8328,8331,8333,8336,8338,8340,8342,8345],{"class":681,"line":2129},[679,8327,8024],{"class":880},[679,8329,8330],{"class":689}," com.therealdanvega.domain.Post.list",[679,8332,745],{"class":693},[679,8334,8335],{"class":880},"Post.groovy",[679,8337,2378],{"class":693},[679,8339,8037],{"class":689},[679,8341,4471],{"class":931},[679,8343,8344],{"class":689},"main/:na",[679,8346,1720],{"class":931},[679,8348,8349],{"class":681,"line":2140},[679,8350,889],{"emptyLinePlaceholder":797},[679,8352,8353],{"class":681,"line":2145},[679,8354,5741],{"class":931},[651,8356,8357],{},"The main takeaway from that error message is this",[651,8359,8360],{},"No Session found for current thread",[651,8362,8363],{},"This is because we currently don't have a Hibernate session to work with. In Grails we could of run Post.list() directly from our controller and it would of worked. So why does it work there an not here? I mean at the end of the day Grails 3 is a Spring Boot app, what funny business is going on behind the scenes? This is a good explanation by Burt Beckwith...",[1004,8365,8366],{},[651,8367,8368],{},"Grails registers a customized subclass of OpenSessionInViewInterceptor (it adds WebFlow awareness). So anything done in the context of a web request will have an open session and lazy-loaded references and collections will resolve.",[651,8370,8371],{},"This means that our request would have an open session and that is what gives us the ability to call our list method. We can do something similar but lets talk through our 2 options here.",[5259,8373,8375],{"id":8374},"transactional","@Transactional",[651,8377,8378],{},"We could manually write the logic in every single method to start a transaction, open a hibernate session, perform our business logic and then close the session and commit the transaction but that would be a huge pain in the butt. The @Transactional annotation gives us a shortcut of doing all that by using AOP. The first thing we need to do to is to enable Spring's annotation-driven transaction management capability by adding the following annotation to our main application class.",[651,8380,8381],{},"@EnableTransactionManagement",[651,8383,8384,8385,8387,8388,8394,8395,8397],{},"The ",[676,8386,8375],{}," annotation is metadata that specifies that an interface, class, or method must have transactional semantics; for example, \"",[679,8389,8391],{"style":8390},"emphasis",[7300,8392,8393],{},"start a brand new read-only transaction when this method is invoked, suspending any existing transaction","\". The default ",[676,8396,8375],{}," settings are as follows:",[5316,8399,8400,8406,8412,8415,8418],{},[5332,8401,8402,8403],{},"Propagation setting is ",[676,8404,8405],{},"PROPAGATION_REQUIRED.",[5332,8407,8408,8409],{},"Isolation level is ",[676,8410,8411],{},"ISOLATION_DEFAULT.",[5332,8413,8414],{},"Transaction is read/write.",[5332,8416,8417],{},"Transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.",[5332,8419,8420,8421,8424,8425,8428],{},"Any ",[676,8422,8423],{},"RuntimeException"," triggers rollback, and any checked ",[676,8426,8427],{},"Exception"," does not.",[651,8430,8431,8432,8437],{},"Those settings can be changed using attributes in the annotation. To learn more about those settings ",[812,8433,8436],{"href":8434,"rel":8435},"http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative-annotations",[816],"please read the documentation",". With that little knowledge we can go and apply the annotation to our service class.",[669,8439,8441],{"className":4107,"code":8440,"language":4109,"meta":674,"style":674},"package com.therealdanvega.service\n\nimport com.therealdanvega.domain.Post\nimport org.springframework.stereotype.Service\nimport org.springframework.transaction.annotation.Transactional\n\n\n@Service('postService')\n@Transactional\nclass PostServiceImpl implements PostService {\n\n @Override\n ArrayList\u003CPost> list() {\n Post.list()\n }\n\n @Override\n Post read(int id) {\n Post.get(id)\n }\n\n}\n",[676,8442,8443,8449,8453,8463,8473,8486,8490,8494,8506,8514,8540,8544,8552,8566,8572,8576,8580,8588,8598,8604,8608,8612],{"__ignoreMap":674},[679,8444,8445,8447],{"class":681,"line":682},[679,8446,2543],{"class":685},[679,8448,7373],{"class":693},[679,8450,8451],{"class":681,"line":790},[679,8452,889],{"emptyLinePlaceholder":797},[679,8454,8455,8457,8459,8461],{"class":681,"line":892},[679,8456,1999],{"class":6561},[679,8458,7384],{"class":693},[679,8460,7387],{"class":6561},[679,8462,7390],{"class":693},[679,8464,8465,8467,8469,8471],{"class":681,"line":901},[679,8466,1999],{"class":6561},[679,8468,7479],{"class":693},[679,8470,7406],{"class":6561},[679,8472,7484],{"class":693},[679,8474,8475,8477,8480,8483],{"class":681,"line":909},[679,8476,1999],{"class":6561},[679,8478,8479],{"class":693}," org.springframework.transaction.annotation.",[679,8481,8482],{"class":6561},"T",[679,8484,8485],{"class":693},"ransactional\n",[679,8487,8488],{"class":681,"line":918},[679,8489,889],{"emptyLinePlaceholder":797},[679,8491,8492],{"class":681,"line":935},[679,8493,889],{"emptyLinePlaceholder":797},[679,8495,8496,8498,8500,8502,8504],{"class":681,"line":944},[679,8497,4116],{"class":693},[679,8499,7406],{"class":6561},[679,8501,7497],{"class":693},[679,8503,7406],{"class":6561},[679,8505,7502],{"class":693},[679,8507,8508,8510,8512],{"class":681,"line":959},[679,8509,4116],{"class":693},[679,8511,8482],{"class":6561},[679,8513,8485],{"class":693},[679,8515,8516,8518,8520,8522,8524,8526,8528,8530,8532,8534,8536,8538],{"class":681,"line":964},[679,8517,877],{"class":6561},[679,8519,7202],{"class":6561},[679,8521,7403],{"class":693},[679,8523,7406],{"class":6561},[679,8525,7515],{"class":693},[679,8527,7518],{"class":6561},[679,8529,7521],{"class":693},[679,8531,7524],{"class":6561},[679,8533,7202],{"class":6561},[679,8535,7403],{"class":693},[679,8537,7406],{"class":6561},[679,8539,7409],{"class":693},[679,8541,8542],{"class":681,"line":977},[679,8543,889],{"emptyLinePlaceholder":797},[679,8545,8546,8548,8550],{"class":681,"line":982},[679,8547,6872],{"class":693},[679,8549,7249],{"class":6561},[679,8551,7545],{"class":693},[679,8553,8554,8556,8558,8560,8562,8564],{"class":681,"line":988},[679,8555,7418],{"class":6561},[679,8557,7421],{"class":693},[679,8559,7424],{"class":6561},[679,8561,7427],{"class":693},[679,8563,7387],{"class":6561},[679,8565,7560],{"class":693},[679,8567,8568,8570],{"class":681,"line":993},[679,8569,7565],{"class":6561},[679,8571,7568],{"class":693},[679,8573,8574],{"class":681,"line":2129},[679,8575,985],{"class":693},[679,8577,8578],{"class":681,"line":2140},[679,8579,889],{"emptyLinePlaceholder":797},[679,8581,8582,8584,8586],{"class":681,"line":2145},[679,8583,6872],{"class":693},[679,8585,7249],{"class":6561},[679,8587,7545],{"class":693},[679,8589,8590,8592,8594,8596],{"class":681,"line":2154},[679,8591,7441],{"class":6561},[679,8593,7591],{"class":693},[679,8595,1078],{"class":6561},[679,8597,7596],{"class":693},[679,8599,8600,8602],{"class":681,"line":2159},[679,8601,7565],{"class":6561},[679,8603,7603],{"class":693},[679,8605,8606],{"class":681,"line":2164},[679,8607,985],{"class":693},[679,8609,8610],{"class":681,"line":3134},[679,8611,889],{"emptyLinePlaceholder":797},[679,8613,8614],{"class":681,"line":3139},[679,8615,996],{"class":693},[651,8617,8618],{},"If you run the example now you should be able to visit the /posts mapping without error.",[5259,8620,8622],{"id":8621},"open-session-in-view","Open Session In View",[651,8624,8625,8626,8631,8632,8635,8636,8639],{},"Another option is to use the Open Session in View pattern. In Grails there is a subclass of the ",[812,8627,8630],{"href":8628,"rel":8629},"http://docs.spring.io/autorepo/docs/spring/current/javadoc-api/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.html",[816],"OpenSessionInViewInterceptor from Spring"," configured for us and that is why we don't have to worry about it.This is a Spring web request interceptor that binds a Hibernate ",[676,8633,8634],{},"Session"," to the thread for the entire processing of the request. The important part is that the session is available to us ",[2939,8637,8638],{},"In View."," If you aren't using a view technology than this doesn't make a lot of sense and some would say even if you are this isn't the best answer.",[651,8641,8642],{},"Take a post object that has a relationship to author. The author on the object is lazy loaded. This means that if we wrapped transactional to get our post and then tried to call post.getAuthor() in the view there would no longer be a session available to load that author object. This is where the need for it comes into play.",[651,8644,8645],{},"Another approach is you could add a new attribute to the model called author in your controller method and set it there eliminating the need for that call in the view. This is for you to decide and do a little reading up on. I am just going to present you with a solution if you decide to go that route. Create a new package called config and create a new class called WebMvcConfig.",[669,8647,8649],{"className":4107,"code":8648,"language":4109,"meta":674,"style":674},"package com.therealdanvega.config\n\nimport org.hibernate.SessionFactory\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.context.annotation.Configuration\nimport org.springframework.orm.hibernate4.support.OpenSessionInViewInterceptor\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter\n\n@Configuration\nclass WebMvcConfig extends WebMvcConfigurerAdapter {\n\n @Autowired\n SessionFactory sessionFactory\n\n @Override\n void addInterceptors(InterceptorRegistry registry) {\n OpenSessionInViewInterceptor openSessionInViewInterceptor = new OpenSessionInViewInterceptor()\n openSessionInViewInterceptor.setSessionFactory(sessionFactory)\n registry.addWebRequestInterceptor( openSessionInViewInterceptor )\n }\n\n}\n",[676,8650,8651,8658,8662,8680,8690,8702,8733,8750,8777,8781,8789,8827,8831,8839,8854,8858,8866,8887,8953,8989,9024,9028,9032],{"__ignoreMap":674},[679,8652,8653,8655],{"class":681,"line":682},[679,8654,2543],{"class":685},[679,8656,8657],{"class":693}," com.therealdanvega.config\n",[679,8659,8660],{"class":681,"line":790},[679,8661,889],{"emptyLinePlaceholder":797},[679,8663,8664,8666,8669,8671,8674,8677],{"class":681,"line":892},[679,8665,1999],{"class":6561},[679,8667,8668],{"class":693}," org.hibernate.",[679,8670,7406],{"class":6561},[679,8672,8673],{"class":693},"ession",[679,8675,8676],{"class":6561},"F",[679,8678,8679],{"class":693},"actory\n",[679,8681,8682,8684,8686,8688],{"class":681,"line":901},[679,8683,1999],{"class":6561},[679,8685,7701],{"class":693},[679,8687,7704],{"class":6561},[679,8689,7707],{"class":693},[679,8691,8692,8694,8697,8699],{"class":681,"line":909},[679,8693,1999],{"class":6561},[679,8695,8696],{"class":693}," org.springframework.context.annotation.",[679,8698,7757],{"class":6561},[679,8700,8701],{"class":693},"onfiguration\n",[679,8703,8704,8706,8709,8711,8714,8716,8718,8720,8723,8725,8728,8730],{"class":681,"line":918},[679,8705,1999],{"class":6561},[679,8707,8708],{"class":693}," org.springframework.orm.hibernate4.support.",[679,8710,7249],{"class":6561},[679,8712,8713],{"class":693},"pen",[679,8715,7406],{"class":6561},[679,8717,8673],{"class":693},[679,8719,7518],{"class":6561},[679,8721,8722],{"class":693},"n",[679,8724,7722],{"class":6561},[679,8726,8727],{"class":693},"iew",[679,8729,7518],{"class":6561},[679,8731,8732],{"class":693},"nterceptor\n",[679,8734,8735,8737,8740,8742,8745,8747],{"class":681,"line":935},[679,8736,1999],{"class":6561},[679,8738,8739],{"class":693}," org.springframework.web.servlet.config.annotation.",[679,8741,7518],{"class":6561},[679,8743,8744],{"class":693},"nterceptor",[679,8746,7734],{"class":6561},[679,8748,8749],{"class":693},"egistry\n",[679,8751,8752,8754,8756,8759,8762,8764,8767,8769,8772,8774],{"class":681,"line":944},[679,8753,1999],{"class":6561},[679,8755,8739],{"class":693},[679,8757,8758],{"class":6561},"W",[679,8760,8761],{"class":693},"eb",[679,8763,7740],{"class":6561},[679,8765,8766],{"class":693},"vc",[679,8768,7757],{"class":6561},[679,8770,8771],{"class":693},"onfigurer",[679,8773,7704],{"class":6561},[679,8775,8776],{"class":693},"dapter\n",[679,8778,8779],{"class":681,"line":959},[679,8780,889],{"emptyLinePlaceholder":797},[679,8782,8783,8785,8787],{"class":681,"line":964},[679,8784,4116],{"class":693},[679,8786,7757],{"class":6561},[679,8788,8701],{"class":693},[679,8790,8791,8793,8796,8798,8800,8802,8804,8807,8810,8812,8814,8816,8818,8820,8822,8824],{"class":681,"line":977},[679,8792,877],{"class":6561},[679,8794,8795],{"class":6561}," W",[679,8797,8761],{"class":693},[679,8799,7740],{"class":6561},[679,8801,8766],{"class":693},[679,8803,7757],{"class":6561},[679,8805,8806],{"class":693},"onfig ",[679,8808,8809],{"class":6561},"extends",[679,8811,8795],{"class":6561},[679,8813,8761],{"class":693},[679,8815,7740],{"class":6561},[679,8817,8766],{"class":693},[679,8819,7757],{"class":6561},[679,8821,8771],{"class":693},[679,8823,7704],{"class":6561},[679,8825,8826],{"class":693},"dapter {\n",[679,8828,8829],{"class":681,"line":982},[679,8830,889],{"emptyLinePlaceholder":797},[679,8832,8833,8835,8837],{"class":681,"line":988},[679,8834,6872],{"class":693},[679,8836,7704],{"class":6561},[679,8838,7707],{"class":693},[679,8840,8841,8843,8845,8847,8850,8852],{"class":681,"line":993},[679,8842,7214],{"class":6561},[679,8844,8673],{"class":693},[679,8846,8676],{"class":6561},[679,8848,8849],{"class":693},"actory session",[679,8851,8676],{"class":6561},[679,8853,8679],{"class":693},[679,8855,8856],{"class":681,"line":2129},[679,8857,889],{"emptyLinePlaceholder":797},[679,8859,8860,8862,8864],{"class":681,"line":2140},[679,8861,6872],{"class":693},[679,8863,7249],{"class":6561},[679,8865,7545],{"class":693},[679,8867,8868,8870,8873,8875,8878,8880,8882,8884],{"class":681,"line":2145},[679,8869,3314],{"class":6561},[679,8871,8872],{"class":693}," add",[679,8874,7518],{"class":6561},[679,8876,8877],{"class":693},"nterceptors(",[679,8879,7518],{"class":6561},[679,8881,8744],{"class":693},[679,8883,7734],{"class":6561},[679,8885,8886],{"class":693},"egistry registry) {\n",[679,8888,8889,8892,8894,8896,8898,8900,8902,8904,8906,8908,8911,8913,8915,8917,8919,8921,8923,8925,8928,8931,8934,8936,8938,8940,8942,8944,8946,8948,8950],{"class":681,"line":2154},[679,8890,8891],{"class":6561}," O",[679,8893,8713],{"class":693},[679,8895,7406],{"class":6561},[679,8897,8673],{"class":693},[679,8899,7518],{"class":6561},[679,8901,8722],{"class":693},[679,8903,7722],{"class":6561},[679,8905,8727],{"class":693},[679,8907,7518],{"class":6561},[679,8909,8910],{"class":693},"nterceptor open",[679,8912,7406],{"class":6561},[679,8914,8673],{"class":693},[679,8916,7518],{"class":6561},[679,8918,8722],{"class":693},[679,8920,7722],{"class":6561},[679,8922,8727],{"class":693},[679,8924,7518],{"class":6561},[679,8926,8927],{"class":693},"nterceptor = ",[679,8929,8930],{"class":6561},"new",[679,8932,8933],{"class":6561}," O",[679,8935,8713],{"class":693},[679,8937,7406],{"class":6561},[679,8939,8673],{"class":693},[679,8941,7518],{"class":6561},[679,8943,8722],{"class":693},[679,8945,7722],{"class":6561},[679,8947,8727],{"class":693},[679,8949,7518],{"class":6561},[679,8951,8952],{"class":693},"nterceptor()\n",[679,8954,8955,8958,8960,8962,8964,8966,8968,8970,8972,8975,8977,8979,8981,8984,8986],{"class":681,"line":2159},[679,8956,8957],{"class":693}," open",[679,8959,7406],{"class":6561},[679,8961,8673],{"class":693},[679,8963,7518],{"class":6561},[679,8965,8722],{"class":693},[679,8967,7722],{"class":6561},[679,8969,8727],{"class":693},[679,8971,7518],{"class":6561},[679,8973,8974],{"class":693},"nterceptor.set",[679,8976,7406],{"class":6561},[679,8978,8673],{"class":693},[679,8980,8676],{"class":6561},[679,8982,8983],{"class":693},"actory(session",[679,8985,8676],{"class":6561},[679,8987,8988],{"class":693},"actory)\n",[679,8990,8991,8994,8996,8998,9000,9002,9004,9007,9009,9011,9013,9015,9017,9019,9021],{"class":681,"line":2164},[679,8992,8993],{"class":693}," registry.add",[679,8995,8758],{"class":6561},[679,8997,8761],{"class":693},[679,8999,7734],{"class":6561},[679,9001,7737],{"class":693},[679,9003,7518],{"class":6561},[679,9005,9006],{"class":693},"nterceptor( open",[679,9008,7406],{"class":6561},[679,9010,8673],{"class":693},[679,9012,7518],{"class":6561},[679,9014,8722],{"class":693},[679,9016,7722],{"class":6561},[679,9018,8727],{"class":693},[679,9020,7518],{"class":6561},[679,9022,9023],{"class":693},"nterceptor )\n",[679,9025,9026],{"class":681,"line":3134},[679,9027,985],{"class":693},[679,9029,9030],{"class":681,"line":3139},[679,9031,889],{"emptyLinePlaceholder":797},[679,9033,9034],{"class":681,"line":3144},[679,9035,996],{"class":693},[651,9037,9038],{},"We are creating a new OpenSessionInViewInterceptor, setting the session factory and adding that as a web request interceptor. You could at this point remove the transactions and our program would again run correctly. Just a quick not though that this doesn't eliminate the need for transactions. The next problem you are going to come across is serializing / deserializing objects. We will leave that discussion for another day.",[5909,9040,9042],{"id":9041},"conclusion","Conclusion",[651,9044,9045,9046],{},"At this point I don't want to start a discussion on GORM vs Spring Data. To me they are both awesome and I think the more I use them both in Spring Boot the better equipped I will be to answer those questions. In the meantime I just wanted to show you that you can use GORM in Spring Boot and 1 of the main issues I came across setting it up. ",[812,9047,9050],{"href":9048,"rel":9049},"https://github.com/danvega/gorm-spring-boot-demo",[816],"You can grab the source code for this demo here.",[786,9052,9053],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":9055},[9056,9057,9058,9059,9060,9061],{"id":7108,"depth":892,"text":7109},{"id":7153,"depth":892,"text":7154},{"id":7309,"depth":892,"text":7310},{"id":7357,"depth":892,"text":7358},{"id":7986,"depth":892,"text":7987},{"id":9041,"depth":892,"text":9042},{"slug":9063,"published":797,"date":9064,"tags":9065,"cover":9066},"using-gorm-in-spring-boot","2015-11-25T11:43:13-05:00",[870,7055,4109],"./adult-back-view-data-1181345.jpg",{"title":246,"description":246},"blog/2015/11/25/using-gorm-in-spring-boot","erMSJ-hvAggqiPph5Jr99KkOs-0K9J2E4i4gV7uYwtQ",{"id":9071,"title":243,"body":9072,"description":9883,"extension":793,"meta":9884,"navigation":797,"path":244,"seo":9889,"stem":9890,"__hash__":9891},"content/blog/2016/01/13/sending-async-emails-in-spring.md",{"type":648,"value":9073,"toc":9881},[9074,9077,9082,9085,9090,9096,9099,9143,9146,9198,9201,9462,9475,9750,9758,9861,9869,9872,9878],[651,9075,9076],{},"I have a student taking my course that sent me the following question",[1004,9078,9079],{},[651,9080,9081],{},"It would be really cool to show how to send this email asynchronously. Because it's never a good idea to send any sort of notification synchronously.",[651,9083,9084],{},"I do agree with him in that there is no reason for us block program execution for something like sending an email. I am going to take this chance to walk you through sending email asynchronously using Spring Boot. Start off by creating a basic new Spring Boot project using the Spring Initializr from the web or your favorite IDE. We are going to select Web & Mail as dependencies.",[651,9086,9087],{},[660,9088],{"alt":674,"src":9089},"./email_async_1.png",[651,9091,9092,9095],{},[660,9093],{"alt":674,"src":9094},"./email_async_2.png"," ",[651,9097,9098],{},"By selecting web and mail as dependencies we should have these two starter dependencies included included in our build file.",[669,9100,9104],{"className":9101,"code":9102,"language":9103,"meta":674,"style":674},"language-xml shiki shiki-themes github-light github-dark github-light","\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-mail\u003C/artifactId>\n\u003C/dependency>\n\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n\u003C/dependency>\n","xml",[676,9105,9106,9111,9116,9121,9126,9130,9134,9139],{"__ignoreMap":674},[679,9107,9108],{"class":681,"line":682},[679,9109,9110],{},"\u003Cdependency>\n",[679,9112,9113],{"class":681,"line":790},[679,9114,9115],{}," \u003CgroupId>org.springframework.boot\u003C/groupId>\n",[679,9117,9118],{"class":681,"line":892},[679,9119,9120],{}," \u003CartifactId>spring-boot-starter-mail\u003C/artifactId>\n",[679,9122,9123],{"class":681,"line":901},[679,9124,9125],{},"\u003C/dependency>\n",[679,9127,9128],{"class":681,"line":909},[679,9129,9110],{},[679,9131,9132],{"class":681,"line":918},[679,9133,9115],{},[679,9135,9136],{"class":681,"line":935},[679,9137,9138],{}," \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n",[679,9140,9141],{"class":681,"line":944},[679,9142,9125],{},[651,9144,9145],{},"With the starter mail dependency in place we need to configure our mail server properties.",[669,9147,9149],{"className":5851,"code":9148,"language":5853,"meta":674,"style":674},"spring.mail.host = smtp.gmail.com\nspring.mail.username = username@gmail.com\nspring.mail.password = xxxxxxxxxxxxxxxxxx\nspring.mail.port=587\nspring.mail.properties.mail.smtp.starttls.enable = true\n",[676,9150,9151,9161,9171,9181,9189],{"__ignoreMap":674},[679,9152,9153,9156,9158],{"class":681,"line":682},[679,9154,9155],{"class":880},"spring.mail.host",[679,9157,6883],{"class":689},[679,9159,9160],{"class":689}," smtp.gmail.com\n",[679,9162,9163,9166,9168],{"class":681,"line":790},[679,9164,9165],{"class":880},"spring.mail.username",[679,9167,6883],{"class":689},[679,9169,9170],{"class":689}," username@gmail.com\n",[679,9172,9173,9176,9178],{"class":681,"line":892},[679,9174,9175],{"class":880},"spring.mail.password",[679,9177,6883],{"class":689},[679,9179,9180],{"class":689}," xxxxxxxxxxxxxxxxxx\n",[679,9182,9183,9186],{"class":681,"line":901},[679,9184,9185],{"class":880},"spring.mail.port",[679,9187,9188],{"class":689},"=587\n",[679,9190,9191,9194,9196],{"class":681,"line":909},[679,9192,9193],{"class":880},"spring.mail.properties.mail.smtp.starttls.enable",[679,9195,6883],{"class":689},[679,9197,2582],{"class":931},[651,9199,9200],{},"The first thing we are going to do is create a controller. This controller is going to have a request mapping of signup-success. This method is going to create a user (just a simple POJO) and then try to send a notification using our notification service.",[669,9202,9204],{"className":4107,"code":9203,"language":4109,"meta":674,"style":674},"@RestController\npublic class RegistrationController {\n\n private Logger logger = LoggerFactory.getLogger(RegistrationController.class);\n\n @Autowired\n private NotificationService notificationService;\n\n @RequestMapping(\"/signup-success\")\n public String signupSuccess(){\n\n // create user\n User user = new User();\n user.setFirstName(\"Dan\");\n user.setLastName(\"Vega\");\n user.setEmailAddress(\"dan@clecares.org\");\n\n // send a notification\n try {\n notificationService.sendNotificaitoin(user);\n }catch( Exception e ){\n // catch error\n logger.info(\"Error Sending Email: \" + e.getMessage());\n }\n\n return \"Thank you for registering with us.\";\n }\n\n}\n",[676,9205,9206,9213,9224,9228,9247,9251,9258,9265,9269,9283,9295,9299,9304,9318,9332,9346,9360,9364,9369,9376,9387,9403,9408,9432,9436,9440,9450,9454,9458],{"__ignoreMap":674},[679,9207,9208,9210],{"class":681,"line":682},[679,9209,4116],{"class":693},[679,9211,9212],{"class":685},"RestController\n",[679,9214,9215,9217,9219,9222],{"class":681,"line":790},[679,9216,6073],{"class":685},[679,9218,4512],{"class":685},[679,9220,9221],{"class":880}," RegistrationController",[679,9223,884],{"class":693},[679,9225,9226],{"class":681,"line":892},[679,9227,889],{"emptyLinePlaceholder":797},[679,9229,9230,9233,9236,9238,9241,9244],{"class":681,"line":901},[679,9231,9232],{"class":685}," private",[679,9234,9235],{"class":693}," Logger logger ",[679,9237,686],{"class":685},[679,9239,9240],{"class":693}," LoggerFactory.",[679,9242,9243],{"class":880},"getLogger",[679,9245,9246],{"class":693},"(RegistrationController.class);\n",[679,9248,9249],{"class":681,"line":909},[679,9250,889],{"emptyLinePlaceholder":797},[679,9252,9253,9255],{"class":681,"line":918},[679,9254,6872],{"class":693},[679,9256,9257],{"class":685},"Autowired\n",[679,9259,9260,9262],{"class":681,"line":935},[679,9261,9232],{"class":685},[679,9263,9264],{"class":693}," NotificationService notificationService;\n",[679,9266,9267],{"class":681,"line":944},[679,9268,889],{"emptyLinePlaceholder":797},[679,9270,9271,9273,9276,9278,9281],{"class":681,"line":959},[679,9272,6872],{"class":693},[679,9274,9275],{"class":685},"RequestMapping",[679,9277,745],{"class":693},[679,9279,9280],{"class":689},"\"/signup-success\"",[679,9282,1339],{"class":693},[679,9284,9285,9287,9290,9293],{"class":681,"line":964},[679,9286,6089],{"class":685},[679,9288,9289],{"class":693}," String ",[679,9291,9292],{"class":880},"signupSuccess",[679,9294,2041],{"class":693},[679,9296,9297],{"class":681,"line":977},[679,9298,889],{"emptyLinePlaceholder":797},[679,9300,9301],{"class":681,"line":982},[679,9302,9303],{"class":1400}," // create user\n",[679,9305,9306,9309,9311,9313,9315],{"class":681,"line":988},[679,9307,9308],{"class":693}," User user ",[679,9310,686],{"class":685},[679,9312,2054],{"class":685},[679,9314,881],{"class":880},[679,9316,9317],{"class":693},"();\n",[679,9319,9320,9323,9326,9328,9330],{"class":681,"line":993},[679,9321,9322],{"class":693}," user.",[679,9324,9325],{"class":880},"setFirstName",[679,9327,745],{"class":693},[679,9329,1414],{"class":689},[679,9331,1208],{"class":693},[679,9333,9334,9336,9339,9341,9344],{"class":681,"line":2129},[679,9335,9322],{"class":693},[679,9337,9338],{"class":880},"setLastName",[679,9340,745],{"class":693},[679,9342,9343],{"class":689},"\"Vega\"",[679,9345,1208],{"class":693},[679,9347,9348,9350,9353,9355,9358],{"class":681,"line":2140},[679,9349,9322],{"class":693},[679,9351,9352],{"class":880},"setEmailAddress",[679,9354,745],{"class":693},[679,9356,9357],{"class":689},"\"dan@clecares.org\"",[679,9359,1208],{"class":693},[679,9361,9362],{"class":681,"line":2145},[679,9363,889],{"emptyLinePlaceholder":797},[679,9365,9366],{"class":681,"line":2154},[679,9367,9368],{"class":1400}," // send a notification\n",[679,9370,9371,9374],{"class":681,"line":2159},[679,9372,9373],{"class":685}," try",[679,9375,884],{"class":693},[679,9377,9378,9381,9384],{"class":681,"line":2164},[679,9379,9380],{"class":693}," notificationService.",[679,9382,9383],{"class":880},"sendNotificaitoin",[679,9385,9386],{"class":693},"(user);\n",[679,9388,9389,9392,9395,9398,9401],{"class":681,"line":3134},[679,9390,9391],{"class":693}," }",[679,9393,9394],{"class":685},"catch",[679,9396,9397],{"class":693},"( Exception ",[679,9399,9400],{"class":2099},"e",[679,9402,2310],{"class":693},[679,9404,9405],{"class":681,"line":3139},[679,9406,9407],{"class":1400}," // catch error\n",[679,9409,9410,9413,9416,9418,9421,9423,9426,9429],{"class":681,"line":3144},[679,9411,9412],{"class":693}," logger.",[679,9414,9415],{"class":880},"info",[679,9417,745],{"class":693},[679,9419,9420],{"class":689},"\"Error Sending Email: \"",[679,9422,3059],{"class":685},[679,9424,9425],{"class":693}," e.",[679,9427,9428],{"class":880},"getMessage",[679,9430,9431],{"class":693},"());\n",[679,9433,9434],{"class":681,"line":3149},[679,9435,1290],{"class":693},[679,9437,9438],{"class":681,"line":3169},[679,9439,889],{"emptyLinePlaceholder":797},[679,9441,9442,9445,9448],{"class":681,"line":3185},[679,9443,9444],{"class":685}," return",[679,9446,9447],{"class":689}," \"Thank you for registering with us.\"",[679,9449,1186],{"class":693},[679,9451,9452],{"class":681,"line":3194},[679,9453,985],{"class":693},[679,9455,9456],{"class":681,"line":3199},[679,9457,889],{"emptyLinePlaceholder":797},[679,9459,9460],{"class":681,"line":3212},[679,9461,996],{"class":693},[651,9463,9464,9465,9470,9471,9474],{},"The Notification Service is where the real magic happens. At the start of our send notification method we are going to simulate a long process by using the sleep method. Notice that we have add the ",[812,9466,9469],{"href":9467,"rel":9468},"https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html",[816],"@Async annotation"," to the method that marks a method as a candidate for ",[7300,9472,9473],{},"asynchronous"," execution.",[669,9476,9478],{"className":4107,"code":9477,"language":4109,"meta":674,"style":674},"@Service\npublic class NotificationService {\n\n private JavaMailSender javaMailSender;\n\n @Autowired\n public NotificationService(JavaMailSender javaMailSender){\n this.javaMailSender = javaMailSender;\n }\n\n @Async\n public void sendNotificaitoin(User user) throws MailException, InterruptedException {\n\n System.out.println(\"Sleeping now...\");\n Thread.sleep(10000);\n\n System.out.println(\"Sending email...\");\n\n SimpleMailMessage mail = new SimpleMailMessage();\n mail.setTo(user.getEmailAddress());\n mail.setFrom(\"danvega@gmail.com\");\n mail.setSubject(\"Spring Boot is awesome!\");\n mail.setText(\"Why aren't you using Spring Boot?\");\n javaMailSender.send(mail);\n\n System.out.println(\"Email Sent!\");\n }\n\n}\n",[676,9479,9480,9487,9498,9502,9509,9513,9519,9534,9546,9550,9554,9561,9584,9588,9602,9617,9621,9634,9638,9652,9668,9682,9696,9710,9721,9725,9738,9742,9746],{"__ignoreMap":674},[679,9481,9482,9484],{"class":681,"line":682},[679,9483,4116],{"class":693},[679,9485,9486],{"class":685},"Service\n",[679,9488,9489,9491,9493,9496],{"class":681,"line":790},[679,9490,6073],{"class":685},[679,9492,4512],{"class":685},[679,9494,9495],{"class":880}," NotificationService",[679,9497,884],{"class":693},[679,9499,9500],{"class":681,"line":892},[679,9501,889],{"emptyLinePlaceholder":797},[679,9503,9504,9506],{"class":681,"line":901},[679,9505,9232],{"class":685},[679,9507,9508],{"class":693}," JavaMailSender javaMailSender;\n",[679,9510,9511],{"class":681,"line":909},[679,9512,889],{"emptyLinePlaceholder":797},[679,9514,9515,9517],{"class":681,"line":918},[679,9516,6872],{"class":693},[679,9518,9257],{"class":685},[679,9520,9521,9523,9525,9528,9531],{"class":681,"line":935},[679,9522,6089],{"class":685},[679,9524,9495],{"class":880},[679,9526,9527],{"class":693},"(JavaMailSender ",[679,9529,9530],{"class":2099},"javaMailSender",[679,9532,9533],{"class":693},"){\n",[679,9535,9536,9538,9541,9543],{"class":681,"line":944},[679,9537,7862],{"class":931},[679,9539,9540],{"class":693},".javaMailSender ",[679,9542,686],{"class":685},[679,9544,9545],{"class":693}," javaMailSender;\n",[679,9547,9548],{"class":681,"line":959},[679,9549,985],{"class":693},[679,9551,9552],{"class":681,"line":964},[679,9553,889],{"emptyLinePlaceholder":797},[679,9555,9556,9558],{"class":681,"line":977},[679,9557,6872],{"class":693},[679,9559,9560],{"class":685},"Async\n",[679,9562,9563,9565,9567,9570,9573,9576,9578,9581],{"class":681,"line":982},[679,9564,6089],{"class":685},[679,9566,6095],{"class":685},[679,9568,9569],{"class":880}," sendNotificaitoin",[679,9571,9572],{"class":693},"(User ",[679,9574,9575],{"class":2099},"user",[679,9577,2378],{"class":693},[679,9579,9580],{"class":685},"throws",[679,9582,9583],{"class":693}," MailException, InterruptedException {\n",[679,9585,9586],{"class":681,"line":988},[679,9587,889],{"emptyLinePlaceholder":797},[679,9589,9590,9593,9595,9597,9600],{"class":681,"line":993},[679,9591,9592],{"class":693}," System.out.",[679,9594,1729],{"class":880},[679,9596,745],{"class":693},[679,9598,9599],{"class":689},"\"Sleeping now...\"",[679,9601,1208],{"class":693},[679,9603,9604,9607,9610,9612,9615],{"class":681,"line":2129},[679,9605,9606],{"class":693}," Thread.",[679,9608,9609],{"class":880},"sleep",[679,9611,745],{"class":693},[679,9613,9614],{"class":931},"10000",[679,9616,1208],{"class":693},[679,9618,9619],{"class":681,"line":2140},[679,9620,889],{"emptyLinePlaceholder":797},[679,9622,9623,9625,9627,9629,9632],{"class":681,"line":2145},[679,9624,9592],{"class":693},[679,9626,1729],{"class":880},[679,9628,745],{"class":693},[679,9630,9631],{"class":689},"\"Sending email...\"",[679,9633,1208],{"class":693},[679,9635,9636],{"class":681,"line":2154},[679,9637,889],{"emptyLinePlaceholder":797},[679,9639,9640,9643,9645,9647,9650],{"class":681,"line":2159},[679,9641,9642],{"class":693}," SimpleMailMessage mail ",[679,9644,686],{"class":685},[679,9646,2054],{"class":685},[679,9648,9649],{"class":880}," SimpleMailMessage",[679,9651,9317],{"class":693},[679,9653,9654,9657,9660,9663,9666],{"class":681,"line":2164},[679,9655,9656],{"class":693}," mail.",[679,9658,9659],{"class":880},"setTo",[679,9661,9662],{"class":693},"(user.",[679,9664,9665],{"class":880},"getEmailAddress",[679,9667,9431],{"class":693},[679,9669,9670,9672,9675,9677,9680],{"class":681,"line":3134},[679,9671,9656],{"class":693},[679,9673,9674],{"class":880},"setFrom",[679,9676,745],{"class":693},[679,9678,9679],{"class":689},"\"danvega@gmail.com\"",[679,9681,1208],{"class":693},[679,9683,9684,9686,9689,9691,9694],{"class":681,"line":3139},[679,9685,9656],{"class":693},[679,9687,9688],{"class":880},"setSubject",[679,9690,745],{"class":693},[679,9692,9693],{"class":689},"\"Spring Boot is awesome!\"",[679,9695,1208],{"class":693},[679,9697,9698,9700,9703,9705,9708],{"class":681,"line":3144},[679,9699,9656],{"class":693},[679,9701,9702],{"class":880},"setText",[679,9704,745],{"class":693},[679,9706,9707],{"class":689},"\"Why aren't you using Spring Boot?\"",[679,9709,1208],{"class":693},[679,9711,9712,9715,9718],{"class":681,"line":3149},[679,9713,9714],{"class":693}," javaMailSender.",[679,9716,9717],{"class":880},"send",[679,9719,9720],{"class":693},"(mail);\n",[679,9722,9723],{"class":681,"line":3169},[679,9724,889],{"emptyLinePlaceholder":797},[679,9726,9727,9729,9731,9733,9736],{"class":681,"line":3185},[679,9728,9592],{"class":693},[679,9730,1729],{"class":880},[679,9732,745],{"class":693},[679,9734,9735],{"class":689},"\"Email Sent!\"",[679,9737,1208],{"class":693},[679,9739,9740],{"class":681,"line":3194},[679,9741,985],{"class":693},[679,9743,9744],{"class":681,"line":3199},[679,9745,889],{"emptyLinePlaceholder":797},[679,9747,9748],{"class":681,"line":3212},[679,9749,996],{"class":693},[651,9751,9752,9753,664],{},"We have one final task to make all of this work. Go to the main application class and add the ",[812,9754,9757],{"href":9755,"rel":9756},"https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html",[816],"@EnableAsync annotation",[669,9759,9761],{"className":4107,"code":9760,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.scheduling.annotation.EnableAsync;\n\n@SpringBootApplication\n@EnableAsync\npublic class SendingEmailAsyncApplication {\n\n public static void main(String\\[\\] args) {\n SpringApplication.run(SendingEmailAsyncApplication.class, args);\n }\n}\n",[676,9762,9763,9769,9773,9779,9785,9792,9796,9802,9809,9820,9824,9844,9853,9857],{"__ignoreMap":674},[679,9764,9765,9767],{"class":681,"line":682},[679,9766,2543],{"class":685},[679,9768,6039],{"class":693},[679,9770,9771],{"class":681,"line":790},[679,9772,889],{"emptyLinePlaceholder":797},[679,9774,9775,9777],{"class":681,"line":892},[679,9776,1999],{"class":685},[679,9778,6050],{"class":693},[679,9780,9781,9783],{"class":681,"line":901},[679,9782,1999],{"class":685},[679,9784,6057],{"class":693},[679,9786,9787,9789],{"class":681,"line":909},[679,9788,1999],{"class":685},[679,9790,9791],{"class":693}," org.springframework.scheduling.annotation.EnableAsync;\n",[679,9793,9794],{"class":681,"line":918},[679,9795,889],{"emptyLinePlaceholder":797},[679,9797,9798,9800],{"class":681,"line":935},[679,9799,4116],{"class":693},[679,9801,6068],{"class":685},[679,9803,9804,9806],{"class":681,"line":944},[679,9805,4116],{"class":693},[679,9807,9808],{"class":685},"EnableAsync\n",[679,9810,9811,9813,9815,9818],{"class":681,"line":959},[679,9812,6073],{"class":685},[679,9814,4512],{"class":685},[679,9816,9817],{"class":880}," SendingEmailAsyncApplication",[679,9819,884],{"class":693},[679,9821,9822],{"class":681,"line":964},[679,9823,889],{"emptyLinePlaceholder":797},[679,9825,9826,9828,9830,9832,9834,9836,9838,9840,9842],{"class":681,"line":977},[679,9827,6089],{"class":685},[679,9829,6092],{"class":685},[679,9831,6095],{"class":685},[679,9833,6098],{"class":880},[679,9835,745],{"class":693},[679,9837,4758],{"class":2099},[679,9839,6105],{"class":693},[679,9841,6108],{"class":2099},[679,9843,4390],{"class":693},[679,9845,9846,9848,9850],{"class":681,"line":982},[679,9847,6115],{"class":693},[679,9849,6118],{"class":880},[679,9851,9852],{"class":693},"(SendingEmailAsyncApplication.class, args);\n",[679,9854,9855],{"class":681,"line":988},[679,9856,985],{"class":693},[679,9858,9859],{"class":681,"line":993},[679,9860,996],{"class":693},[651,9862,9863,9864,9868],{},"Start the application and go to ",[812,9865,9866],{"href":9866,"rel":9867},"http://localhost:8080/signup-success",[816],". You will see the message \"Thank you for registering with us.\" right away and in the console you will see the Sleeping/Sending/Sent messages printed out to the console. If you aren't familiar with Async this is where the real power comes in. The program is not blocking a task like sending an email and returns execution to the user while it performs that task in the background.",[651,9870,9871],{},"You can grab the source for this application below.",[651,9873,9874],{},[812,9875,9876],{"href":9876,"rel":9877},"https://github.com/danvega/spring-boot-intro/tree/master/guides/sending-email-async",[816],[786,9879,9880],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":9882},[],"In this article I will walk you through how to send email asynchronously in Spring Boot.",{"slug":9885,"published":797,"date":9886,"tags":9887,"cover":9888},"sending-async-emails-in-spring","2016-01-13T10:30:20-05:00",[4109,7055],"./email-cover.jpg",{"title":243,"description":9883},"blog/2016/01/13/sending-async-emails-in-spring","kkitR5x21gL3SnIXmg-jcKvorUZM9fmx5NaO38H4Ie0",{"id":9893,"title":240,"body":9894,"description":10930,"extension":793,"meta":10931,"navigation":797,"path":241,"seo":10936,"stem":10937,"__hash__":10938},"content/blog/2016/01/14/spring-mvc-get-controller-method-name.md",{"type":648,"value":9895,"toc":10927},[9896,9905,9910,9913,9928,9931,10134,10137,10173,10176,10339,10342,10346,10349,10628,10631,10671,10674,10683,10686,10765,10768,10917,10924],[651,9897,9898,9899,9904],{},"The one thing I really love about my ",[812,9900,9903],{"href":9901,"rel":9902},"https://www.udemy.com/spring-boot-intro/?couponCode=NEW_YEAR_29",[816],"Spring Boot Introductory Course"," is that my students are always asking me questions. I had this question come in recently and I thought we should take this chance to discuss it.",[1004,9906,9907],{},[651,9908,9909],{},"Dan, Suppose you have header, footer and content templates. The header has the navigation bootstrap code. For example you have multiple tabs in the navigation i.e. \"Admin\" and \"Customers\". How do you make the tab visually active if you are on Customer page or vice versa when using templates? Thanks, AK",[651,9911,9912],{},"I have done a lot of Grails web development and If you know anything about that you will know that it's built on Spring MVC. Grails makes 2 very important variables available to your views",[669,9914,9916],{"className":4496,"code":9915,"language":4498,"meta":674,"style":674},"${controllerName}\n${actionName}\n",[676,9917,9918,9923],{"__ignoreMap":674},[679,9919,9920],{"class":681,"line":682},[679,9921,9922],{"class":693},"${controllerName}\n",[679,9924,9925],{"class":681,"line":790},[679,9926,9927],{"class":693},"${actionName}\n",[651,9929,9930],{},"If these two variables were available to us in Thymeleaf that would make our job really easy right? Well sadly they are not so we need to look at a few alternatives. First we could manually set these variables in every method that sends content to a view.",[669,9932,9934],{"className":4107,"code":9933,"language":4109,"meta":674,"style":674},"@Controller\n@RequestMapping(\"/admin/posts\")\npublic class AdminPostController {\n\n private PostService postService;\n\n @Autowired\n public AdminPostController(PostService postService) {\n this.postService = postService;\n }\n\n @RequestMapping(\"/\")\n public String list(Model model, HttpServletRequest request) {\n model.addAttribute(\"posts\", postService.list());\n model.addAttribute(\"controllerName\", \"AdminPost\");\n model.addAttribute(\"actionName\", \"list\");\n return \"admin/post/list\";\n }\n\n}\n",[676,9935,9936,9943,9956,9967,9971,9978,9982,9988,10002,10014,10018,10022,10035,10057,10077,10095,10113,10122,10126,10130],{"__ignoreMap":674},[679,9937,9938,9940],{"class":681,"line":682},[679,9939,4116],{"class":693},[679,9941,9942],{"class":685},"Controller\n",[679,9944,9945,9947,9949,9951,9954],{"class":681,"line":790},[679,9946,4116],{"class":693},[679,9948,9275],{"class":685},[679,9950,745],{"class":693},[679,9952,9953],{"class":689},"\"/admin/posts\"",[679,9955,1339],{"class":693},[679,9957,9958,9960,9962,9965],{"class":681,"line":892},[679,9959,6073],{"class":685},[679,9961,4512],{"class":685},[679,9963,9964],{"class":880}," AdminPostController",[679,9966,884],{"class":693},[679,9968,9969],{"class":681,"line":901},[679,9970,889],{"emptyLinePlaceholder":797},[679,9972,9973,9975],{"class":681,"line":909},[679,9974,9232],{"class":685},[679,9976,9977],{"class":693}," PostService postService;\n",[679,9979,9980],{"class":681,"line":918},[679,9981,889],{"emptyLinePlaceholder":797},[679,9983,9984,9986],{"class":681,"line":935},[679,9985,6872],{"class":693},[679,9987,9257],{"class":685},[679,9989,9990,9992,9994,9997,10000],{"class":681,"line":944},[679,9991,6089],{"class":685},[679,9993,9964],{"class":880},[679,9995,9996],{"class":693},"(PostService ",[679,9998,9999],{"class":2099},"postService",[679,10001,4390],{"class":693},[679,10003,10004,10006,10009,10011],{"class":681,"line":959},[679,10005,7862],{"class":931},[679,10007,10008],{"class":693},".postService ",[679,10010,686],{"class":685},[679,10012,10013],{"class":693}," postService;\n",[679,10015,10016],{"class":681,"line":964},[679,10017,985],{"class":693},[679,10019,10020],{"class":681,"line":977},[679,10021,889],{"emptyLinePlaceholder":797},[679,10023,10024,10026,10028,10030,10033],{"class":681,"line":982},[679,10025,6872],{"class":693},[679,10027,9275],{"class":685},[679,10029,745],{"class":693},[679,10031,10032],{"class":689},"\"/\"",[679,10034,1339],{"class":693},[679,10036,10037,10039,10041,10043,10046,10049,10052,10055],{"class":681,"line":988},[679,10038,6089],{"class":685},[679,10040,9289],{"class":693},[679,10042,7623],{"class":880},[679,10044,10045],{"class":693},"(Model ",[679,10047,10048],{"class":2099},"model",[679,10050,10051],{"class":693},", HttpServletRequest ",[679,10053,10054],{"class":2099},"request",[679,10056,4390],{"class":693},[679,10058,10059,10062,10065,10067,10070,10073,10075],{"class":681,"line":993},[679,10060,10061],{"class":693}," model.",[679,10063,10064],{"class":880},"addAttribute",[679,10066,745],{"class":693},[679,10068,10069],{"class":689},"\"posts\"",[679,10071,10072],{"class":693},", postService.",[679,10074,7623],{"class":880},[679,10076,9431],{"class":693},[679,10078,10079,10081,10083,10085,10088,10090,10093],{"class":681,"line":2129},[679,10080,10061],{"class":693},[679,10082,10064],{"class":880},[679,10084,745],{"class":693},[679,10086,10087],{"class":689},"\"controllerName\"",[679,10089,2797],{"class":693},[679,10091,10092],{"class":689},"\"AdminPost\"",[679,10094,1208],{"class":693},[679,10096,10097,10099,10101,10103,10106,10108,10111],{"class":681,"line":2140},[679,10098,10061],{"class":693},[679,10100,10064],{"class":880},[679,10102,745],{"class":693},[679,10104,10105],{"class":689},"\"actionName\"",[679,10107,2797],{"class":693},[679,10109,10110],{"class":689},"\"list\"",[679,10112,1208],{"class":693},[679,10114,10115,10117,10120],{"class":681,"line":2145},[679,10116,9444],{"class":685},[679,10118,10119],{"class":689}," \"admin/post/list\"",[679,10121,1186],{"class":693},[679,10123,10124],{"class":681,"line":2154},[679,10125,985],{"class":693},[679,10127,10128],{"class":681,"line":2159},[679,10129,889],{"emptyLinePlaceholder":797},[679,10131,10132],{"class":681,"line":2164},[679,10133,996],{"class":693},[651,10135,10136],{},"And we can check the controller name and if it's that \"section\" we can add the active class.",[669,10138,10140],{"className":4496,"code":10139,"language":4498,"meta":674,"style":674},"\u003Cli class=\"dropdown\" th:class=\"${controllerName == 'AdminPost'} ? 'dropdown active' : 'dropdown'\" sec:authorize=\"hasRole('ROLE\\_ADMIN')\">\n",[676,10141,10142],{"__ignoreMap":674},[679,10143,10144,10146,10148,10150,10152,10155,10158,10160,10163,10166,10168,10171],{"class":681,"line":682},[679,10145,4505],{"class":693},[679,10147,5332],{"class":4508},[679,10149,4512],{"class":880},[679,10151,686],{"class":693},[679,10153,10154],{"class":689},"\"dropdown\"",[679,10156,10157],{"class":880}," th:class",[679,10159,686],{"class":693},[679,10161,10162],{"class":689},"\"${controllerName == 'AdminPost'} ? 'dropdown active' : 'dropdown'\"",[679,10164,10165],{"class":880}," sec:authorize",[679,10167,686],{"class":693},[679,10169,10170],{"class":689},"\"hasRole('ROLE\\_ADMIN')\"",[679,10172,4519],{"class":693},[651,10174,10175],{},"While this works it seems like a lot of work to have to set those variables in every single method. The http servlet request is made available in the view template so we can also do something like this.",[669,10177,10179],{"className":4496,"code":10178,"language":4498,"meta":674,"style":674},"\u003Cli class=\"dropdown\" th:class=\"${#httpServletRequest.requestURI.contains('/admin/posts/')} ? 'dropdown active' : 'dropdown'\" sec:authorize=\"hasRole('ROLE\\_ADMIN')\">\n \u003Ca href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">Admin \u003Cspan class=\"caret\">\u003C/span>\u003C/a>\n \u003Cul class=\"dropdown-menu\">\n \u003Cli>\u003Ca href=\"/admin/posts/\">Posts\u003C/a>\u003C/li>\n \u003C/ul>\n\u003C/li>\n",[676,10180,10181,10208,10280,10295,10323,10331],{"__ignoreMap":674},[679,10182,10183,10185,10187,10189,10191,10193,10195,10197,10200,10202,10204,10206],{"class":681,"line":682},[679,10184,4505],{"class":693},[679,10186,5332],{"class":4508},[679,10188,4512],{"class":880},[679,10190,686],{"class":693},[679,10192,10154],{"class":689},[679,10194,10157],{"class":880},[679,10196,686],{"class":693},[679,10198,10199],{"class":689},"\"${#httpServletRequest.requestURI.contains('/admin/posts/')} ? 'dropdown active' : 'dropdown'\"",[679,10201,10165],{"class":880},[679,10203,686],{"class":693},[679,10205,10170],{"class":689},[679,10207,4519],{"class":693},[679,10209,10210,10212,10214,10216,10218,10220,10222,10224,10227,10230,10232,10234,10237,10239,10242,10245,10247,10250,10253,10255,10258,10261,10263,10265,10267,10270,10272,10274,10276,10278],{"class":681,"line":790},[679,10211,4524],{"class":693},[679,10213,812],{"class":4508},[679,10215,4550],{"class":880},[679,10217,686],{"class":693},[679,10219,5420],{"class":689},[679,10221,4512],{"class":880},[679,10223,686],{"class":693},[679,10225,10226],{"class":689},"\"dropdown-toggle\"",[679,10228,10229],{"class":880}," data-toggle",[679,10231,686],{"class":693},[679,10233,10154],{"class":689},[679,10235,10236],{"class":880}," role",[679,10238,686],{"class":693},[679,10240,10241],{"class":689},"\"button\"",[679,10243,10244],{"class":880}," aria-haspopup",[679,10246,686],{"class":693},[679,10248,10249],{"class":689},"\"true\"",[679,10251,10252],{"class":880}," aria-expanded",[679,10254,686],{"class":693},[679,10256,10257],{"class":689},"\"false\"",[679,10259,10260],{"class":693},">Admin \u003C",[679,10262,679],{"class":4508},[679,10264,4512],{"class":880},[679,10266,686],{"class":693},[679,10268,10269],{"class":689},"\"caret\"",[679,10271,4563],{"class":693},[679,10273,679],{"class":4508},[679,10275,4563],{"class":693},[679,10277,812],{"class":4508},[679,10279,4519],{"class":693},[679,10281,10282,10284,10286,10288,10290,10293],{"class":681,"line":892},[679,10283,4524],{"class":693},[679,10285,5316],{"class":4508},[679,10287,4512],{"class":880},[679,10289,686],{"class":693},[679,10291,10292],{"class":689},"\"dropdown-menu\"",[679,10294,4519],{"class":693},[679,10296,10297,10299,10301,10303,10305,10307,10309,10312,10315,10317,10319,10321],{"class":681,"line":901},[679,10298,4904],{"class":693},[679,10300,5332],{"class":4508},[679,10302,4545],{"class":693},[679,10304,812],{"class":4508},[679,10306,4550],{"class":880},[679,10308,686],{"class":693},[679,10310,10311],{"class":689},"\"/admin/posts/\"",[679,10313,10314],{"class":693},">Posts\u003C/",[679,10316,812],{"class":4508},[679,10318,4563],{"class":693},[679,10320,5332],{"class":4508},[679,10322,4519],{"class":693},[679,10324,10325,10327,10329],{"class":681,"line":909},[679,10326,4577],{"class":693},[679,10328,5316],{"class":4508},[679,10330,4519],{"class":693},[679,10332,10333,10335,10337],{"class":681,"line":918},[679,10334,4586],{"class":693},[679,10336,5332],{"class":4508},[679,10338,4519],{"class":693},[651,10340,10341],{},"While this will work some of the times trying to match on the URI isn't always a good choice and to me this just seems like a hacky solution.",[5909,10343,10345],{"id":10344},"my-solution","My Solution",[651,10347,10348],{},"There might be other solutions to this puzzle but this is what I came up with and I think it works out pretty well. I like the idea of the controller and action names being available in my view but I don't want to set them on every request manually. Thankfully we can create an interceptor to do this for us. We are extending the HandlerInterceptorAdapter which gives us the ability to override the post handle method. In this method I am going to set the controller and action(method) name and send it to the view using ModelAndView.",[669,10350,10352],{"className":4107,"code":10351,"language":4109,"meta":674,"style":674},"package com.therealdanvega.interceptor;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.ModelAndView;\nimport org.springframework.web.servlet.handler.HandlerInterceptorAdapter;\n\npublic class BaseInterceptor extends HandlerInterceptorAdapter {\n\n public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {\n String controllerName = \"\";\n String actionName = \"\";\n\n if( handler instanceof HandlerMethod ) {\n // there are cases where this handler isn't an instance of HandlerMethod, so the cast fails.\n HandlerMethod handlerMethod = (HandlerMethod) handler;\n //controllerName = handlerMethod.getBean().getClass().getSimpleName().replace(\"Controller\", \"\");\n controllerName = handlerMethod.getBeanType().getSimpleName().replace(\"Controller\", \"\");\n actionName = handlerMethod.getMethod().getName();\n }\n\n modelAndView.addObject(\"controllerName\", controllerName );\n modelAndView.addObject(\"actionName\", actionName );\n }\n\n}\n",[676,10353,10354,10361,10365,10372,10379,10383,10390,10397,10404,10408,10424,10428,10467,10478,10489,10493,10506,10511,10521,10526,10561,10580,10584,10588,10603,10616,10620,10624],{"__ignoreMap":674},[679,10355,10356,10358],{"class":681,"line":682},[679,10357,2543],{"class":685},[679,10359,10360],{"class":693}," com.therealdanvega.interceptor;\n",[679,10362,10363],{"class":681,"line":790},[679,10364,889],{"emptyLinePlaceholder":797},[679,10366,10367,10369],{"class":681,"line":892},[679,10368,1999],{"class":685},[679,10370,10371],{"class":693}," javax.servlet.http.HttpServletRequest;\n",[679,10373,10374,10376],{"class":681,"line":901},[679,10375,1999],{"class":685},[679,10377,10378],{"class":693}," javax.servlet.http.HttpServletResponse;\n",[679,10380,10381],{"class":681,"line":909},[679,10382,889],{"emptyLinePlaceholder":797},[679,10384,10385,10387],{"class":681,"line":918},[679,10386,1999],{"class":685},[679,10388,10389],{"class":693}," org.springframework.web.method.HandlerMethod;\n",[679,10391,10392,10394],{"class":681,"line":935},[679,10393,1999],{"class":685},[679,10395,10396],{"class":693}," org.springframework.web.servlet.ModelAndView;\n",[679,10398,10399,10401],{"class":681,"line":944},[679,10400,1999],{"class":685},[679,10402,10403],{"class":693}," org.springframework.web.servlet.handler.HandlerInterceptorAdapter;\n",[679,10405,10406],{"class":681,"line":959},[679,10407,889],{"emptyLinePlaceholder":797},[679,10409,10410,10412,10414,10417,10419,10422],{"class":681,"line":964},[679,10411,6073],{"class":685},[679,10413,4512],{"class":685},[679,10415,10416],{"class":880}," BaseInterceptor",[679,10418,2767],{"class":685},[679,10420,10421],{"class":880}," HandlerInterceptorAdapter",[679,10423,884],{"class":693},[679,10425,10426],{"class":681,"line":977},[679,10427,889],{"emptyLinePlaceholder":797},[679,10429,10430,10432,10434,10437,10440,10442,10445,10448,10451,10454,10457,10460,10462,10464],{"class":681,"line":982},[679,10431,6089],{"class":685},[679,10433,6095],{"class":685},[679,10435,10436],{"class":880}," postHandle",[679,10438,10439],{"class":693},"(HttpServletRequest ",[679,10441,10054],{"class":2099},[679,10443,10444],{"class":693},", HttpServletResponse ",[679,10446,10447],{"class":2099},"response",[679,10449,10450],{"class":693},", Object ",[679,10452,10453],{"class":2099},"handler",[679,10455,10456],{"class":693},", ModelAndView ",[679,10458,10459],{"class":2099},"modelAndView",[679,10461,2378],{"class":693},[679,10463,9580],{"class":685},[679,10465,10466],{"class":693}," Exception {\n",[679,10468,10469,10472,10474,10476],{"class":681,"line":988},[679,10470,10471],{"class":693}," String controllerName ",[679,10473,686],{"class":685},[679,10475,1183],{"class":689},[679,10477,1186],{"class":693},[679,10479,10480,10483,10485,10487],{"class":681,"line":993},[679,10481,10482],{"class":693}," String actionName ",[679,10484,686],{"class":685},[679,10486,1183],{"class":689},[679,10488,1186],{"class":693},[679,10490,10491],{"class":681,"line":2129},[679,10492,889],{"emptyLinePlaceholder":797},[679,10494,10495,10497,10500,10503],{"class":681,"line":2140},[679,10496,1249],{"class":685},[679,10498,10499],{"class":693},"( handler ",[679,10501,10502],{"class":685},"instanceof",[679,10504,10505],{"class":693}," HandlerMethod ) {\n",[679,10507,10508],{"class":681,"line":2145},[679,10509,10510],{"class":1400}," // there are cases where this handler isn't an instance of HandlerMethod, so the cast fails.\n",[679,10512,10513,10516,10518],{"class":681,"line":2154},[679,10514,10515],{"class":693}," HandlerMethod handlerMethod ",[679,10517,686],{"class":685},[679,10519,10520],{"class":693}," (HandlerMethod) handler;\n",[679,10522,10523],{"class":681,"line":2159},[679,10524,10525],{"class":1400}," //controllerName = handlerMethod.getBean().getClass().getSimpleName().replace(\"Controller\", \"\");\n",[679,10527,10528,10531,10533,10536,10539,10542,10545,10547,10550,10552,10555,10557,10559],{"class":681,"line":2164},[679,10529,10530],{"class":693}," controllerName ",[679,10532,686],{"class":685},[679,10534,10535],{"class":693}," handlerMethod.",[679,10537,10538],{"class":880},"getBeanType",[679,10540,10541],{"class":693},"().",[679,10543,10544],{"class":880},"getSimpleName",[679,10546,10541],{"class":693},[679,10548,10549],{"class":880},"replace",[679,10551,745],{"class":693},[679,10553,10554],{"class":689},"\"Controller\"",[679,10556,2797],{"class":693},[679,10558,3579],{"class":689},[679,10560,1208],{"class":693},[679,10562,10563,10566,10568,10570,10573,10575,10578],{"class":681,"line":3134},[679,10564,10565],{"class":693}," actionName ",[679,10567,686],{"class":685},[679,10569,10535],{"class":693},[679,10571,10572],{"class":880},"getMethod",[679,10574,10541],{"class":693},[679,10576,10577],{"class":880},"getName",[679,10579,9317],{"class":693},[679,10581,10582],{"class":681,"line":3139},[679,10583,1290],{"class":693},[679,10585,10586],{"class":681,"line":3144},[679,10587,889],{"emptyLinePlaceholder":797},[679,10589,10590,10593,10596,10598,10600],{"class":681,"line":3149},[679,10591,10592],{"class":693}," modelAndView.",[679,10594,10595],{"class":880},"addObject",[679,10597,745],{"class":693},[679,10599,10087],{"class":689},[679,10601,10602],{"class":693},", controllerName );\n",[679,10604,10605,10607,10609,10611,10613],{"class":681,"line":3169},[679,10606,10592],{"class":693},[679,10608,10595],{"class":880},[679,10610,745],{"class":693},[679,10612,10105],{"class":689},[679,10614,10615],{"class":693},", actionName );\n",[679,10617,10618],{"class":681,"line":3185},[679,10619,985],{"class":693},[679,10621,10622],{"class":681,"line":3194},[679,10623,889],{"emptyLinePlaceholder":797},[679,10625,10626],{"class":681,"line":3199},[679,10627,996],{"class":693},[651,10629,10630],{},"There was one issue I found doing this. At first I was setting the controller using the following code.",[669,10632,10634],{"className":4107,"code":10633,"language":4109,"meta":674,"style":674},"controllerName = handlerMethod.getBean().getClass().getSimpleName().replace(\"Controller\", \"\");\n",[676,10635,10636],{"__ignoreMap":674},[679,10637,10638,10641,10643,10645,10648,10650,10653,10655,10657,10659,10661,10663,10665,10667,10669],{"class":681,"line":682},[679,10639,10640],{"class":693},"controllerName ",[679,10642,686],{"class":685},[679,10644,10535],{"class":693},[679,10646,10647],{"class":880},"getBean",[679,10649,10541],{"class":693},[679,10651,10652],{"class":880},"getClass",[679,10654,10541],{"class":693},[679,10656,10544],{"class":880},[679,10658,10541],{"class":693},[679,10660,10549],{"class":880},[679,10662,745],{"class":693},[679,10664,10554],{"class":689},[679,10666,2797],{"class":693},[679,10668,3579],{"class":689},[679,10670,1208],{"class":693},[651,10672,10673],{},"This would work 99% of the time except when I was using Spring Security. When I was logged in Spring Security would wrap my controller with a proxy class and the name of my controller would look something like",[669,10675,10677],{"className":4107,"code":10676,"language":4109,"meta":674,"style":674},"AdminPostController$$EnhancerBySpringCGLIB$$3ef51261\n",[676,10678,10679],{"__ignoreMap":674},[679,10680,10681],{"class":681,"line":682},[679,10682,10676],{"class":693},[651,10684,10685],{},"So I switched it up and got the actual type of the class which gave me what I needed. Now that you have the Base Interceptor created you need to register it.",[669,10687,10689],{"className":4107,"code":10688,"language":4109,"meta":674,"style":674},"@Configuration\npublic class WebConfig extends WebMvcConfigurerAdapter {\n\n @Override\n public void addInterceptors(InterceptorRegistry registry) {\n registry.addInterceptor(new BaseInterceptor());\n }\n}\n",[676,10690,10691,10697,10713,10717,10724,10741,10757,10761],{"__ignoreMap":674},[679,10692,10693,10695],{"class":681,"line":682},[679,10694,4116],{"class":693},[679,10696,6212],{"class":685},[679,10698,10699,10701,10703,10706,10708,10711],{"class":681,"line":790},[679,10700,6073],{"class":685},[679,10702,4512],{"class":685},[679,10704,10705],{"class":880}," WebConfig",[679,10707,2767],{"class":685},[679,10709,10710],{"class":880}," WebMvcConfigurerAdapter",[679,10712,884],{"class":693},[679,10714,10715],{"class":681,"line":892},[679,10716,889],{"emptyLinePlaceholder":797},[679,10718,10719,10721],{"class":681,"line":901},[679,10720,6872],{"class":693},[679,10722,10723],{"class":685},"Override\n",[679,10725,10726,10728,10730,10733,10736,10739],{"class":681,"line":909},[679,10727,6089],{"class":685},[679,10729,6095],{"class":685},[679,10731,10732],{"class":880}," addInterceptors",[679,10734,10735],{"class":693},"(InterceptorRegistry ",[679,10737,10738],{"class":2099},"registry",[679,10740,4390],{"class":693},[679,10742,10743,10746,10749,10751,10753,10755],{"class":681,"line":918},[679,10744,10745],{"class":693}," registry.",[679,10747,10748],{"class":880},"addInterceptor",[679,10750,745],{"class":693},[679,10752,8930],{"class":685},[679,10754,10416],{"class":880},[679,10756,9431],{"class":693},[679,10758,10759],{"class":681,"line":935},[679,10760,985],{"class":693},[679,10762,10763],{"class":681,"line":944},[679,10764,996],{"class":693},[651,10766,10767],{},"That is all there was to it. Now in every view I had those two variables available. In my template I was able to do something like this",[669,10769,10771],{"className":4496,"code":10770,"language":4498,"meta":674,"style":674},"\u003Cli class=\"dropdown\" th:class=\"${controllerName == 'AdminPost'} ? 'dropdown active' : 'dropdown'\" sec:authorize=\"hasRole('ROLE\\_ADMIN')\">\n \u003Ca href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">Admin \u003Cspan class=\"caret\">\u003C/span>\u003C/a>\n \u003Cul class=\"dropdown-menu\">\n \u003Cli>\u003Ca href=\"/admin/posts/\">Posts\u003C/a>\u003C/li>\n \u003C/ul>\n\u003C/li>\n",[676,10772,10773,10799,10861,10875,10901,10909],{"__ignoreMap":674},[679,10774,10775,10777,10779,10781,10783,10785,10787,10789,10791,10793,10795,10797],{"class":681,"line":682},[679,10776,4505],{"class":693},[679,10778,5332],{"class":4508},[679,10780,4512],{"class":880},[679,10782,686],{"class":693},[679,10784,10154],{"class":689},[679,10786,10157],{"class":880},[679,10788,686],{"class":693},[679,10790,10162],{"class":689},[679,10792,10165],{"class":880},[679,10794,686],{"class":693},[679,10796,10170],{"class":689},[679,10798,4519],{"class":693},[679,10800,10801,10803,10805,10807,10809,10811,10813,10815,10817,10819,10821,10823,10825,10827,10829,10831,10833,10835,10837,10839,10841,10843,10845,10847,10849,10851,10853,10855,10857,10859],{"class":681,"line":790},[679,10802,4524],{"class":693},[679,10804,812],{"class":4508},[679,10806,4550],{"class":880},[679,10808,686],{"class":693},[679,10810,5420],{"class":689},[679,10812,4512],{"class":880},[679,10814,686],{"class":693},[679,10816,10226],{"class":689},[679,10818,10229],{"class":880},[679,10820,686],{"class":693},[679,10822,10154],{"class":689},[679,10824,10236],{"class":880},[679,10826,686],{"class":693},[679,10828,10241],{"class":689},[679,10830,10244],{"class":880},[679,10832,686],{"class":693},[679,10834,10249],{"class":689},[679,10836,10252],{"class":880},[679,10838,686],{"class":693},[679,10840,10257],{"class":689},[679,10842,10260],{"class":693},[679,10844,679],{"class":4508},[679,10846,4512],{"class":880},[679,10848,686],{"class":693},[679,10850,10269],{"class":689},[679,10852,4563],{"class":693},[679,10854,679],{"class":4508},[679,10856,4563],{"class":693},[679,10858,812],{"class":4508},[679,10860,4519],{"class":693},[679,10862,10863,10865,10867,10869,10871,10873],{"class":681,"line":892},[679,10864,4524],{"class":693},[679,10866,5316],{"class":4508},[679,10868,4512],{"class":880},[679,10870,686],{"class":693},[679,10872,10292],{"class":689},[679,10874,4519],{"class":693},[679,10876,10877,10879,10881,10883,10885,10887,10889,10891,10893,10895,10897,10899],{"class":681,"line":901},[679,10878,4904],{"class":693},[679,10880,5332],{"class":4508},[679,10882,4545],{"class":693},[679,10884,812],{"class":4508},[679,10886,4550],{"class":880},[679,10888,686],{"class":693},[679,10890,10311],{"class":689},[679,10892,10314],{"class":693},[679,10894,812],{"class":4508},[679,10896,4563],{"class":693},[679,10898,5332],{"class":4508},[679,10900,4519],{"class":693},[679,10902,10903,10905,10907],{"class":681,"line":909},[679,10904,4577],{"class":693},[679,10906,5316],{"class":4508},[679,10908,4519],{"class":693},[679,10910,10911,10913,10915],{"class":681,"line":918},[679,10912,4586],{"class":693},[679,10914,5332],{"class":4508},[679,10916,4519],{"class":693},[651,10918,10919,10920,10923],{},"I hope this helps and if you can think of any reasons ",[2939,10921,10922],{},"NOT"," to do this or have a more elegant solution please let me know below.",[786,10925,10926],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":10928},[10929],{"id":10344,"depth":892,"text":10345},"How to get the current controller and method name Spring MVC Get Controller & Method Name",{"slug":10932,"published":797,"date":10933,"tags":10934,"cover":10935},"spring-mvc-get-controller-method-name","2016-01-14T16:27:22-05:00",[4109,7055],"./controller-cover.jpg",{"title":240,"description":10930},"blog/2016/01/14/spring-mvc-get-controller-method-name","zh1d5BugSfYaTyjE9Wkkq2DBYfQ3oRiku1Iw7s_myDQ",{"id":10940,"title":237,"body":10941,"description":11025,"extension":793,"meta":11026,"navigation":797,"path":238,"seo":11032,"stem":11033,"__hash__":11034},"content/blog/2016/09/20/tech-elevator-meetup-summary.md",{"type":648,"value":10942,"toc":11023},[10943,10952,10958,10961],[651,10944,10945,10946,10951],{},"Last night I was lucky enough to be asked to ",[812,10947,10950],{"href":10948,"rel":10949},"http://www.meetup.com/Cleveland-learntocode/events/233766635/",[816],"speak on a panel at Tech Elevator",". The panel was made up of Senior Developers from the area and it was to a group of Jr. developers. The presentation was Lessons I've Learned in my programming career. It was great to talk to developers that are in the same spot I was in 15+ years ago and I really hope my advice was good for them to hear.",[651,10953,10954,10957],{},[660,10955],{"alt":674,"src":10956},"./14316849_10153742055740493_5473490718914398535_n.jpg"," ",[651,10959,10960],{},"We had a list of questions that the moderator was going to ask us as well as questions from the audience. These are the questions and I thought some of them were really good. I am actually going to try and create some you YouTube videos with my answers to these and get them posted. Thanks again to everyone who came out.",[5316,10962,10963,10966,10969,10972,10975,10978,10981,10984,10987,10990,10993,10996,10999,11002,11005,11008,11011,11014,11017,11020],{},[5332,10964,10965],{},"Why did you decide to be a developer?",[5332,10967,10968],{},"What was your first role as a developer? Talk about the career path and/or phases (junior dev to senior dev).",[5332,10970,10971],{},"Types of industries and how they compliment strengths students may have (they all have completed the StrengthsFinder assessment)",[5332,10973,10974],{},"Job titles and what they mean.",[5332,10976,10977],{},"What’s it like working for a startup vs corporate vs mid size?",[5332,10979,10980],{},"What’s the biggest misconception about being a developer?",[5332,10982,10983],{},"What’s one thing you would have done differently?",[5332,10985,10986],{},"What other dev-related roles within a company should students be considering?",[5332,10988,10989],{},"How can students get involved in the local tech community? Meetups, etc.",[5332,10991,10992],{},"How can students continue to learn and grow their skills?",[5332,10994,10995],{},"Where can a student be embraced by senior developers as a learner?",[5332,10997,10998],{},"What is the hardest thing about being a developer?",[5332,11000,11001],{},"What type of person does it take to be a developer?",[5332,11003,11004],{},"What’s the difference between a really great developer and an average developer?",[5332,11006,11007],{},"What advice would you give to someone just entering the field?",[5332,11009,11010],{},"What kind of things do you look for in a team/organization that you are considering joining?",[5332,11012,11013],{},"What are your favorite tools / technologies / platforms to work with and why?",[5332,11015,11016],{},"What are your favorite kinds of projects to work on?",[5332,11018,11019],{},"What are your least favorite kinds of projects to work on?",[5332,11021,11022],{},"What was the most important lesson that you have learned as a developer? How did you learn it?",{"title":674,"searchDepth":790,"depth":790,"links":11024},[],"A summary of the panel I was asked to be on at Tech Elevator.",{"slug":11027,"published":797,"date":11028,"tags":11029,"cover":11031},"tech-elevator-meetup-summary","2016-09-20T08:00:53-04:00",[11030],"programming","./highres_454339592.jpeg",{"title":237,"description":11025},"blog/2016/09/20/tech-elevator-meetup-summary","G2mmOPdbIhWOJruiJjaKCYlOqF1MvvNdOOPEY9Ckddk",{"id":11036,"title":234,"body":11037,"description":11123,"extension":793,"meta":11124,"navigation":797,"path":235,"seo":11129,"stem":11130,"__hash__":11131},"content/blog/2016/10/04/made-20000-sleep.md",{"type":648,"value":11038,"toc":11121},[11039,11042,11045,11054,11057,11060,11063,11066,11073,11079,11082,11089,11095,11098,11109,11112,11115,11118],[651,11040,11041],{},"About a year ago, I set out on a journey that has forever changed my life. The title of this article might be a little bit misleading but I had to get your attention so that I could share my story with you. My hope is that anyone in a similar situation will be inspired by this and take action now.",[651,11043,11044],{},"This story actually started about 2 years ago in November. I have always been someone who enjoys learning from a variety of sources. I enjoy reading books and articles on the web, listening to podcasts, going to conferences and even watching videos online.",[651,11046,11047,11048,11053],{},"I loved watching videos online even if I knew the subject because I felt like I would always pick up one little tip or trick that would always make it all worth it. I can remember using ",[812,11049,11052],{"href":11050,"rel":11051},"http://www.lynda.com",[816],"www.lynda.com"," in the past but the idea of an online course was still very new to me. While you can find individual videos on YouTube for free it is very hard to stitch all of these videos together to teach you an entire subject.",[651,11055,11056],{},"In November of 2014 Udemy was running a Black Friday promotion where courses were only $10 each. I ended up watching the promo videos, reading reviews and purchased 5 courses that day. I think over the next year I bought a few more courses and probably went through half of them. I would compare this to purchasing books in the sense that I get really excited to read a new one but sometimes they sit on the shelf for awhile.",[651,11058,11059],{},"When October 2015 rolled around Udemy started running a promotion for all new instructors. Anyone who wanted to be a first time instructor could get involved. If you followed their guidelines and created a new course they would select a few winners and promote you to all of the Facebook & Twitter followers. I can't quite remember the numbers but I do remember that it was a lot. So I started to think about and asked myself \"Why not me?\". I was a blogger, YouTuber and conference speaker. I really enjoy helping others and I always believed that my background made me a great instructor. I was never the smartest guy in the room and I know what problems beginners were facing.",[651,11061,11062],{},"Just coming back inspired from the Spring One Conference I decided I was going to create my first course on Spring Boot. I spent the next 45 days or so working my tail off. I didn't know a whole lot about course creation or video production but I was willing to learn. All of the videos for my first course were shot in my home office on my iPhone. It wasn't the easiest thing to do but it ended up turning out ok. I also knew that the promo video was going to be important so I spent some time on that as well. At the end of the day though students want to learn Spring Boot and they are purchasing this course for the content.",[651,11064,11065],{},"I didn't end up being selected by Udemy to be in their promotions but I was included in the Black Friday sale and that was the most important thing. With 2,300 students at the time of this writing and 528 ratings I couldn't be happier with the way things turned out. So many students leave positive reviews on a regular basis and it just makes me so happy to be helping others.",[651,11067,11068],{},[812,11069,11072],{"href":11070,"rel":11071},"https://www.udemy.com/spring-boot-intro/?couponCode=TRDV20K",[816],"https://www.udemy.com/spring-boot-intro/",[651,11074,11075],{},[812,11076,11077],{"href":11077,"rel":11078},"https://www.youtube.com/watch?v=sEvZf70qyv4",[816],[651,11080,11081],{},"When that course was complete I already started working on my next course. One of the first things I did was upgrade my camera to a Canon 70D. This camera shot amazing video but one of the main features was a flip out screen so I could actually see what I was recording. With some experience on my hands I felt like it was less about the production this time around and now I could focus on the content. I choose to teach a course on the Groovy Programming Language. I have been using this language for years and it was just something I was really passionate about. I ended up launching this course towards the end of May 2016 and at the current time of this article it has almost 420 students and 75 reviews. This course has gotten nothing but positive feedback and I really don't feel like I would change anything about it at this time.",[651,11083,11084],{},[812,11085,11088],{"href":11086,"rel":11087},"https://www.udemy.com/apache-groovy/?couponCode=TRDV20K",[816],"https://www.udemy.com/apache-groovy/",[651,11090,11091],{},[812,11092,11093],{"href":11093,"rel":11094},"https://www.youtube.com/watch?v=NBe0NrSqPrU",[816],[651,11096,11097],{},"So here we are on October 4th which is 1 year from when I decided to commit to this and about 10 1/2 months from the time my first course launched. As you can see from the image below I have made about $20,000 over that time period. There are instructors making a lot more than this but I wanted to post these very real numbers to prove a point to you. If you have a passion for helping others this can be you. It is not going to be easy and there will be times where you might want to give up but I promise you it is all worth it. I have a full time job that I love doing and all of this can be done on nights and weekends.",[651,11099,11100,11108],{},[812,11101,11104],{"href":11102,"rel":11103},"https://therealdanvega.com/wp-content/uploads/2016/10/udemy_milestone_20k_2.png",[816],[660,11105],{"alt":11106,"src":11107},"Udemy Milestone 20K","./udemy_milestone_20k_2-1024x688.png"," ",[651,11110,11111],{},"The reason for the title is where the passive income lifestyle comes into play. I won't lie there is a lot of up front work but the idea of waking up and seeing that you made money overnight is very cool. I also took a couple vacations this year and making money while you're lying on the beach is equally as rewarding.",[651,11113,11114],{},"You will have some work to do keeping the course updated and answering student questions but to be honest if you do the work up front there won't be a ton of work post launch. The time to take action is right now though. With the big Black Friday sale around the corner you will want to be a part of that.",[651,11116,11117],{},"I am currently working on course #3 with my friend John Thompson and we are really excited about this one. I am also branching out and already have course outlines completed for my next 2 courses which are going to be a little bit of a pivot for me, but an exciting one. I would love to hear your comments and questions so please leave them below or contact me.",[651,11119,11120],{},"This is just the start of my story and I have so much more to tell you about my experience. I have learned so much on my journey and I can't wait to share it with you. If you're interested in learning more about teaching online or anything else I am up to please sign up for my newsletter below.",{"title":674,"searchDepth":790,"depth":790,"links":11122},[],"In this article I hope to inspire you to take action now!",{"slug":11125,"published":797,"date":11126,"tags":11127,"cover":11107},"made-20000-sleep","2016-10-04T20:26:59-04:00",[11128],"udemy",{"title":234,"description":11123},"blog/2016/10/04/made-20000-sleep","V8Oaq04t4CuDeuScJNOtAOOJuwUZnsnoEEHqbXa5WXA",{"id":11133,"title":231,"body":11134,"description":231,"extension":793,"meta":11664,"navigation":797,"path":232,"seo":11669,"stem":11670,"__hash__":11671},"content/blog/2017/03/22/spring-boot-entity-scan.md",{"type":648,"value":11135,"toc":11655},[11136,11139,11143,11152,11156,11159,11163,11166,11171,11174,11180,11184,11187,11490,11493,11497,11506,11625,11628,11635,11637,11646,11652],[651,11137,11138],{},"I had a question from a student come up that a lot of new Spring developers come across. It has to do with creating components and in this case more specifically, entities. Let's take a look at the question and then dive into the solution.",[4542,11140,11142],{"id":11141},"the-problem","The Problem",[1004,11144,11145],{},[651,11146,11147,11148,11151],{},"Hey, I'm trying to get JPA to create the post and author tables and view them in the H2 console. I copied every step in the video and tried the suggestions to change the JDBC url to ",[11149,11150],"testdb",{},". I've also tried changing spring.datasource.name: to both test and testdb, all with no luck. When the console comes up, there are no tables. Any help would be most appreciated!! Here is the code in a repo, if that helps!",[4542,11153,11155],{"id":11154},"the-solution","The Solution",[651,11157,11158],{},"I had to dive into the student's code to figure out what was going on but usually, there is a pretty good explanation as to why something like this is happening. When entities aren't being picked up and database tables aren't being created there are a few places to look. The first thing I do is make sure we are connecting to the correct database and in this case, we were. Next, I like to take a look at the entities and make sure there are no issues with those. We could have issues with the code or it simply could be an issue with Spring not finding the entity based on its location. ",[5909,11160,11162],{"id":11161},"create-a-new-spring-boot-project","Create a new Spring Boot Project",[651,11164,11165],{},"To demonstrate the issue at hand we are going to create a brand new Spring Boot project. This will give us a chance to see what the problem is in a simplified version. Create a new project using the Spring Initializr and select the Web, JPA, and H2 dependencies. ",[651,11167,11168],{},[660,11169],{"alt":7077,"src":11170},"./entity-demo-starter-1024x649.png",[651,11172,11173],{},"When you are creating the project you will be asked to give your main package a name. I named mine com.therealdanvega and as you can see from the image below it created the main application class under that package. ",[651,11175,11176,9095],{},[660,11177],{"alt":11178,"src":11179},"Entry Scan Spring Boot","./entity-scan-project.png",[5909,11181,11183],{"id":11182},"create-an-entity","Create an Entity",[651,11185,11186],{},"In the project, I asked the student to create a package named domain and create a new entity there. The student did just that and here is what that class look likes. ",[669,11188,11190],{"className":4107,"code":11189,"language":4109,"meta":674,"style":674},"package domain;\n\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.Id;\n\n@Entity\npublic class Tweet {\n\n @Id @GeneratedValue\n private Long id;\n private String username;\n private String text;\n\n private Tweet() {}\n\n public Long getId() {\n return id;\n }\n\n public void setId(Long id) {\n this.id = id;\n }\n\n public String getUsername() {\n return username;\n }\n\n public void setUsername(String username) {\n this.username = username;\n }\n\n public String getText() {\n return text;\n }\n\n public void setText(String text) {\n this.text = text;\n }\n\n}\n",[676,11191,11192,11199,11203,11210,11217,11224,11228,11235,11246,11250,11262,11269,11276,11283,11287,11296,11300,11312,11319,11323,11327,11344,11355,11359,11363,11374,11381,11385,11389,11406,11417,11421,11425,11436,11443,11447,11451,11467,11478,11482,11486],{"__ignoreMap":674},[679,11193,11194,11196],{"class":681,"line":682},[679,11195,2543],{"class":685},[679,11197,11198],{"class":693}," domain;\n",[679,11200,11201],{"class":681,"line":790},[679,11202,889],{"emptyLinePlaceholder":797},[679,11204,11205,11207],{"class":681,"line":892},[679,11206,1999],{"class":685},[679,11208,11209],{"class":693}," javax.persistence.Entity;\n",[679,11211,11212,11214],{"class":681,"line":901},[679,11213,1999],{"class":685},[679,11215,11216],{"class":693}," javax.persistence.GeneratedValue;\n",[679,11218,11219,11221],{"class":681,"line":909},[679,11220,1999],{"class":685},[679,11222,11223],{"class":693}," javax.persistence.Id;\n",[679,11225,11226],{"class":681,"line":918},[679,11227,889],{"emptyLinePlaceholder":797},[679,11229,11230,11232],{"class":681,"line":935},[679,11231,4116],{"class":693},[679,11233,11234],{"class":685},"Entity\n",[679,11236,11237,11239,11241,11244],{"class":681,"line":944},[679,11238,6073],{"class":685},[679,11240,4512],{"class":685},[679,11242,11243],{"class":880}," Tweet",[679,11245,884],{"class":693},[679,11247,11248],{"class":681,"line":959},[679,11249,889],{"emptyLinePlaceholder":797},[679,11251,11252,11254,11257,11259],{"class":681,"line":964},[679,11253,6872],{"class":693},[679,11255,11256],{"class":685},"Id",[679,11258,6475],{"class":693},[679,11260,11261],{"class":685},"GeneratedValue\n",[679,11263,11264,11266],{"class":681,"line":977},[679,11265,9232],{"class":685},[679,11267,11268],{"class":693}," Long id;\n",[679,11270,11271,11273],{"class":681,"line":982},[679,11272,9232],{"class":685},[679,11274,11275],{"class":693}," String username;\n",[679,11277,11278,11280],{"class":681,"line":988},[679,11279,9232],{"class":685},[679,11281,11282],{"class":693}," String text;\n",[679,11284,11285],{"class":681,"line":993},[679,11286,889],{"emptyLinePlaceholder":797},[679,11288,11289,11291,11293],{"class":681,"line":2129},[679,11290,9232],{"class":685},[679,11292,11243],{"class":880},[679,11294,11295],{"class":693},"() {}\n",[679,11297,11298],{"class":681,"line":2140},[679,11299,889],{"emptyLinePlaceholder":797},[679,11301,11302,11304,11307,11310],{"class":681,"line":2145},[679,11303,6089],{"class":685},[679,11305,11306],{"class":693}," Long ",[679,11308,11309],{"class":880},"getId",[679,11311,2667],{"class":693},[679,11313,11314,11316],{"class":681,"line":2154},[679,11315,9444],{"class":685},[679,11317,11318],{"class":693}," id;\n",[679,11320,11321],{"class":681,"line":2159},[679,11322,985],{"class":693},[679,11324,11325],{"class":681,"line":2164},[679,11326,889],{"emptyLinePlaceholder":797},[679,11328,11329,11331,11333,11336,11339,11342],{"class":681,"line":3134},[679,11330,6089],{"class":685},[679,11332,6095],{"class":685},[679,11334,11335],{"class":880}," setId",[679,11337,11338],{"class":693},"(Long ",[679,11340,11341],{"class":2099},"id",[679,11343,4390],{"class":693},[679,11345,11346,11348,11351,11353],{"class":681,"line":3139},[679,11347,7862],{"class":931},[679,11349,11350],{"class":693},".id ",[679,11352,686],{"class":685},[679,11354,11318],{"class":693},[679,11356,11357],{"class":681,"line":3144},[679,11358,985],{"class":693},[679,11360,11361],{"class":681,"line":3149},[679,11362,889],{"emptyLinePlaceholder":797},[679,11364,11365,11367,11369,11372],{"class":681,"line":3169},[679,11366,6089],{"class":685},[679,11368,9289],{"class":693},[679,11370,11371],{"class":880},"getUsername",[679,11373,2667],{"class":693},[679,11375,11376,11378],{"class":681,"line":3185},[679,11377,9444],{"class":685},[679,11379,11380],{"class":693}," username;\n",[679,11382,11383],{"class":681,"line":3194},[679,11384,985],{"class":693},[679,11386,11387],{"class":681,"line":3199},[679,11388,889],{"emptyLinePlaceholder":797},[679,11390,11391,11393,11395,11398,11401,11404],{"class":681,"line":3212},[679,11392,6089],{"class":685},[679,11394,6095],{"class":685},[679,11396,11397],{"class":880}," setUsername",[679,11399,11400],{"class":693},"(String ",[679,11402,11403],{"class":2099},"username",[679,11405,4390],{"class":693},[679,11407,11408,11410,11413,11415],{"class":681,"line":3217},[679,11409,7862],{"class":931},[679,11411,11412],{"class":693},".username ",[679,11414,686],{"class":685},[679,11416,11380],{"class":693},[679,11418,11419],{"class":681,"line":3222},[679,11420,985],{"class":693},[679,11422,11423],{"class":681,"line":3227},[679,11424,889],{"emptyLinePlaceholder":797},[679,11426,11427,11429,11431,11434],{"class":681,"line":3232},[679,11428,6089],{"class":685},[679,11430,9289],{"class":693},[679,11432,11433],{"class":880},"getText",[679,11435,2667],{"class":693},[679,11437,11438,11440],{"class":681,"line":3499},[679,11439,9444],{"class":685},[679,11441,11442],{"class":693}," text;\n",[679,11444,11445],{"class":681,"line":3509},[679,11446,985],{"class":693},[679,11448,11449],{"class":681,"line":3516},[679,11450,889],{"emptyLinePlaceholder":797},[679,11452,11453,11455,11457,11460,11462,11465],{"class":681,"line":3531},[679,11454,6089],{"class":685},[679,11456,6095],{"class":685},[679,11458,11459],{"class":880}," setText",[679,11461,11400],{"class":693},[679,11463,11464],{"class":2099},"text",[679,11466,4390],{"class":693},[679,11468,11469,11471,11474,11476],{"class":681,"line":3536},[679,11470,7862],{"class":931},[679,11472,11473],{"class":693},".text ",[679,11475,686],{"class":685},[679,11477,11442],{"class":693},[679,11479,11480],{"class":681,"line":3541},[679,11481,985],{"class":693},[679,11483,11484],{"class":681,"line":3546},[679,11485,889],{"emptyLinePlaceholder":797},[679,11487,11488],{"class":681,"line":3551},[679,11489,996],{"class":693},[651,11491,11492],{},"If you know what you're looking for you can probably see the issue on line 1 of that code. The student created the domain package under the java folder and not inside of the main com.therealdanvega package. The student started the application and the table wasn't being created and as we will see this is expected behavior. ",[5909,11494,11496],{"id":11495},"entityscan-annotation","@EntityScan Annotation",[651,11498,11499,11500,11505],{},"The easy fix is to move the package and class under the main package and this will work as expected. Another way we can solve this problem is by telling Spring where to find our entities. We can do so by using the ",[812,11501,11504],{"href":11502,"rel":11503},"http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/domain/EntityScan.html",[816],"@EntityScan annotation",". We can use the base packages attribute and point to one or many packages to include when scanning for entities. We will add this annotation to our main application class. ",[669,11507,11509],{"className":4107,"code":11508,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.boot.autoconfigure.domain.EntityScan;\n\n@SpringBootApplication\n@EntityScan( basePackages = {\"domain\"} )\npublic class EntityDemoApplication {\n\n public static void main(String\\[\\] args) {\n SpringApplication.run(EntityDemoApplication.class, args);\n }\n}\n",[676,11510,11511,11517,11521,11527,11533,11540,11544,11550,11573,11584,11588,11608,11617,11621],{"__ignoreMap":674},[679,11512,11513,11515],{"class":681,"line":682},[679,11514,2543],{"class":685},[679,11516,6039],{"class":693},[679,11518,11519],{"class":681,"line":790},[679,11520,889],{"emptyLinePlaceholder":797},[679,11522,11523,11525],{"class":681,"line":892},[679,11524,1999],{"class":685},[679,11526,6050],{"class":693},[679,11528,11529,11531],{"class":681,"line":901},[679,11530,1999],{"class":685},[679,11532,6057],{"class":693},[679,11534,11535,11537],{"class":681,"line":909},[679,11536,1999],{"class":685},[679,11538,11539],{"class":693}," org.springframework.boot.autoconfigure.domain.EntityScan;\n",[679,11541,11542],{"class":681,"line":918},[679,11543,889],{"emptyLinePlaceholder":797},[679,11545,11546,11548],{"class":681,"line":935},[679,11547,4116],{"class":693},[679,11549,6068],{"class":685},[679,11551,11552,11554,11557,11559,11562,11564,11567,11570],{"class":681,"line":944},[679,11553,4116],{"class":693},[679,11555,11556],{"class":685},"EntityScan",[679,11558,1234],{"class":693},[679,11560,11561],{"class":931},"basePackages",[679,11563,6883],{"class":685},[679,11565,11566],{"class":693}," {",[679,11568,11569],{"class":689},"\"domain\"",[679,11571,11572],{"class":693},"} )\n",[679,11574,11575,11577,11579,11582],{"class":681,"line":959},[679,11576,6073],{"class":685},[679,11578,4512],{"class":685},[679,11580,11581],{"class":880}," EntityDemoApplication",[679,11583,884],{"class":693},[679,11585,11586],{"class":681,"line":964},[679,11587,889],{"emptyLinePlaceholder":797},[679,11589,11590,11592,11594,11596,11598,11600,11602,11604,11606],{"class":681,"line":977},[679,11591,6089],{"class":685},[679,11593,6092],{"class":685},[679,11595,6095],{"class":685},[679,11597,6098],{"class":880},[679,11599,745],{"class":693},[679,11601,4758],{"class":2099},[679,11603,6105],{"class":693},[679,11605,6108],{"class":2099},[679,11607,4390],{"class":693},[679,11609,11610,11612,11614],{"class":681,"line":982},[679,11611,6115],{"class":693},[679,11613,6118],{"class":880},[679,11615,11616],{"class":693},"(EntityDemoApplication.class, args);\n",[679,11618,11619],{"class":681,"line":988},[679,11620,985],{"class":693},[679,11622,11623],{"class":681,"line":993},[679,11624,996],{"class":693},[651,11626,11627],{},"Now if you start up the application and go to your H2 console you should see that the tweet table was created for you. ",[651,11629,11630,11634],{},[660,11631],{"alt":11632,"src":11633},"Tweet Class","./tweet_class.png"," ",[4542,11636,9042],{"id":9041},[651,11638,11639,11640,11645],{},"As you can see this is a pretty easy mistake to make. If you would like to check out this project you can find the ",[812,11641,11644],{"href":11642,"rel":11643},"https://github.com/cfaddict/entity-scan-demo",[816],"source code for it on Github",". I hope you found this tutorial helpful and with that, I will leave you with a question. ",[651,11647,11648,11651],{},[2939,11649,11650],{},"Question:"," _What is a common mistake you made in your first few Spring Boot projects? _",[786,11653,11654],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":11656},[11657,11658,11663],{"id":11141,"depth":790,"text":11142},{"id":11154,"depth":790,"text":11155,"children":11659},[11660,11661,11662],{"id":11161,"depth":892,"text":11162},{"id":11182,"depth":892,"text":11183},{"id":11495,"depth":892,"text":11496},{"id":9041,"depth":790,"text":9042},{"slug":11665,"published":797,"date":11666,"tags":11667,"cover":11668},"spring-boot-entity-scan","2017-03-22T08:00:44-04:00",[7055],"./entity-scan.jpg",{"title":231,"description":231},"blog/2017/03/22/spring-boot-entity-scan","_UOFE2cuQE6a81B2VGPpM_uqyWwDQB99429VBocxHAA",{"id":11673,"title":228,"body":11674,"description":228,"extension":793,"meta":12204,"navigation":797,"path":229,"seo":12209,"stem":12210,"__hash__":12211},"content/blog/2017/03/24/spring-boot-convention-based-error-pages.md",{"type":648,"value":11675,"toc":12198},[11676,11679,11682,11686,11700,11706,11710,11718,11721,11882,11888,11892,11895,12179,12185,12187,12190,12195],[651,11677,11678],{},"We have to remember that when we put applications out into the wild that things will go wrong. This can be in the form of a user going to a wrong URL or entering invalid data. As much as we like to always blame the users (it must be user error, right?) there are times when we as developers make mistakes.",[651,11680,11681],{},"What we need to understand is that it is ok for both the users and us to make mistakes as long as we account for them. When a user requests a URI that doesn't exist we need to show them a proper 404 (page not found) error page. We can even take this one step further and provide additional resources on this page to help them find what they are looking for. In this article, we will look at some improvements in Spring Boot 1.4 that help us define convention based error pages. In this article, we will look at some improvements in Spring Boot 1.4 that help us define convention based error pages.",[4542,11683,11685],{"id":11684},"custom-error-pages","Custom Error Pages",[651,11687,11688,11689,11694,11695,11699],{},"I have ",[812,11690,11693],{"href":11691,"rel":11692},"https://github.com/cfaddict/custom-errors/tree/master/src/main/java/com/therealdanvega/controller",[816],"created a very simple project"," that has a single home controller with no methods defined. If we launch that application and visit ",[812,11696,11697],{"href":11697,"rel":11698},"http://localhost:8080",[816]," we will get this default white label error page. This is of course because we have no mapping defined for \"/\".",[651,11701,11702],{},[660,11703],{"alt":11704,"src":11705},"White Label Error","./whitelabel_error_page.png",[4542,11707,11709],{"id":11708},"html-templates","HTML Templates",[651,11711,11712,11713,664],{},"What you are seeing above is the default, global error handler for the servlet container. As you can see the status code 404 (page not found) is being thrown. In previous versions of Spring Boot if we wanted a custom 404 page we had to create a custom error controller that implemented the ",[812,11714,11717],{"href":11715,"rel":11716},"http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/ErrorController.html",[816],"error controller interface",[651,11719,11720],{},"Now we can just create the templates and by convention place them in a folder. If we are just using static HTML files we can place them in the /src/main/resources/public/error directory. If we want to catch a specific error code like 404 we can create a 404.html. If we want to map all 4xx errors, simply create a 4xx.html. With that, I am going to drop in my custom 404.html.",[669,11722,11724],{"className":4496,"code":11723,"language":4498,"meta":674,"style":674},"\u003Chtml>\n \u003Chead>\n \u003Ctitle>404: Page Not Found\u003C/title>\n \u003Cstyle>\n body {\n background-color: #2a2627;\n margin: 20px;\n }\n h1 {\n color: #ffffff;\n }\n \u003C/style>\n \u003C/head>\n \u003Cbody>\n \u003Ch1>404: Page Not Found\u003C/h1>\n \u003C/body>\n\u003C/html>\n",[676,11725,11726,11734,11744,11758,11766,11773,11785,11800,11805,11812,11824,11828,11836,11845,11853,11866,11874],{"__ignoreMap":674},[679,11727,11728,11730,11732],{"class":681,"line":682},[679,11729,4505],{"class":693},[679,11731,4498],{"class":4508},[679,11733,4519],{"class":693},[679,11735,11736,11739,11742],{"class":681,"line":790},[679,11737,11738],{"class":693}," \u003C",[679,11740,11741],{"class":4508},"head",[679,11743,4519],{"class":693},[679,11745,11746,11748,11751,11754,11756],{"class":681,"line":892},[679,11747,4524],{"class":693},[679,11749,11750],{"class":4508},"title",[679,11752,11753],{"class":693},">404: Page Not Found\u003C/",[679,11755,11750],{"class":4508},[679,11757,4519],{"class":693},[679,11759,11760,11762,11764],{"class":681,"line":901},[679,11761,4524],{"class":693},[679,11763,786],{"class":4508},[679,11765,4519],{"class":693},[679,11767,11768,11771],{"class":681,"line":909},[679,11769,11770],{"class":4508}," body",[679,11772,884],{"class":693},[679,11774,11775,11778,11780,11783],{"class":681,"line":918},[679,11776,11777],{"class":931}," background-color",[679,11779,4282],{"class":693},[679,11781,11782],{"class":931},"#2a2627",[679,11784,1186],{"class":693},[679,11786,11787,11790,11792,11795,11798],{"class":681,"line":935},[679,11788,11789],{"class":931}," margin",[679,11791,4282],{"class":693},[679,11793,11794],{"class":931},"20",[679,11796,11797],{"class":685},"px",[679,11799,1186],{"class":693},[679,11801,11802],{"class":681,"line":944},[679,11803,11804],{"class":693}," }\n",[679,11806,11807,11810],{"class":681,"line":959},[679,11808,11809],{"class":4508}," h1",[679,11811,884],{"class":693},[679,11813,11814,11817,11819,11822],{"class":681,"line":964},[679,11815,11816],{"class":931}," color",[679,11818,4282],{"class":693},[679,11820,11821],{"class":931},"#ffffff",[679,11823,1186],{"class":693},[679,11825,11826],{"class":681,"line":977},[679,11827,11804],{"class":693},[679,11829,11830,11832,11834],{"class":681,"line":982},[679,11831,4577],{"class":693},[679,11833,786],{"class":4508},[679,11835,4519],{"class":693},[679,11837,11838,11841,11843],{"class":681,"line":988},[679,11839,11840],{"class":693}," \u003C/",[679,11842,11741],{"class":4508},[679,11844,4519],{"class":693},[679,11846,11847,11849,11851],{"class":681,"line":993},[679,11848,11738],{"class":693},[679,11850,3006],{"class":4508},[679,11852,4519],{"class":693},[679,11854,11855,11857,11860,11862,11864],{"class":681,"line":2129},[679,11856,4524],{"class":693},[679,11858,11859],{"class":4508},"h1",[679,11861,11753],{"class":693},[679,11863,11859],{"class":4508},[679,11865,4519],{"class":693},[679,11867,11868,11870,11872],{"class":681,"line":2140},[679,11869,11840],{"class":693},[679,11871,3006],{"class":4508},[679,11873,4519],{"class":693},[679,11875,11876,11878,11880],{"class":681,"line":2145},[679,11877,4586],{"class":693},[679,11879,4498],{"class":4508},[679,11881,4519],{"class":693},[651,11883,11884],{},[660,11885],{"alt":11886,"src":11887},"Custom Error","./custom_error-.png",[4542,11889,11891],{"id":11890},"dynamic-templates","Dynamic Templates",[651,11893,11894],{},"If you are using a templating engine in your application we can also take advantage of this convention based error handling. You will need to place your templates in the src/main/resources/templates/error directory. I have almost the same error template but because we are using Thymeleaf I can access the error object and display more information on the page.",[669,11896,11898],{"className":4496,"code":11897,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml\n lang=\"en\"\n xmlns=\"http://www.w3.org/1999/xhtml\"\n xmlns:th=\"http://www.thymleaf.org\"\n>\n \u003Chead>\n \u003Cmeta charset=\"utf-8\" />\n \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n \u003Ctitle>404: Page Not Found\u003C/title>\n \u003Cstyle>\n body {\n background-color: #2a2627;\n margin: 20px;\n }\n h1,\n p {\n color: #ffffff;\n }\n \u003C/style>\n \u003C/head>\n \u003Cbody>\n \u003Ch1>Page Not Found\u003C/h1>\n \u003Cp th:text=\"${error}\">Error Info\u003C/p>\n \u003C/body>\n\u003C/html>\n",[676,11899,11900,11913,11920,11930,11940,11950,11954,11962,11979,12003,12025,12037,12045,12051,12061,12073,12077,12084,12091,12101,12105,12113,12121,12129,12142,12163,12171],{"__ignoreMap":674},[679,11901,11902,11905,11908,11911],{"class":681,"line":682},[679,11903,11904],{"class":693},"\u003C!",[679,11906,11907],{"class":4508},"DOCTYPE",[679,11909,11910],{"class":880}," html",[679,11912,4519],{"class":693},[679,11914,11915,11917],{"class":681,"line":790},[679,11916,4505],{"class":693},[679,11918,11919],{"class":4508},"html\n",[679,11921,11922,11925,11927],{"class":681,"line":892},[679,11923,11924],{"class":880}," lang",[679,11926,686],{"class":693},[679,11928,11929],{"class":689},"\"en\"\n",[679,11931,11932,11935,11937],{"class":681,"line":901},[679,11933,11934],{"class":880}," xmlns",[679,11936,686],{"class":693},[679,11938,11939],{"class":689},"\"http://www.w3.org/1999/xhtml\"\n",[679,11941,11942,11945,11947],{"class":681,"line":909},[679,11943,11944],{"class":880}," xmlns:th",[679,11946,686],{"class":693},[679,11948,11949],{"class":689},"\"http://www.thymleaf.org\"\n",[679,11951,11952],{"class":681,"line":918},[679,11953,4519],{"class":693},[679,11955,11956,11958,11960],{"class":681,"line":935},[679,11957,11738],{"class":693},[679,11959,11741],{"class":4508},[679,11961,4519],{"class":693},[679,11963,11964,11966,11969,11972,11974,11977],{"class":681,"line":944},[679,11965,4524],{"class":693},[679,11967,11968],{"class":4508},"meta",[679,11970,11971],{"class":880}," charset",[679,11973,686],{"class":693},[679,11975,11976],{"class":689},"\"utf-8\"",[679,11978,5387],{"class":693},[679,11980,11981,11983,11985,11988,11990,11993,11996,11998,12001],{"class":681,"line":959},[679,11982,4524],{"class":693},[679,11984,11968],{"class":4508},[679,11986,11987],{"class":880}," http-equiv",[679,11989,686],{"class":693},[679,11991,11992],{"class":689},"\"X-UA-Compatible\"",[679,11994,11995],{"class":880}," content",[679,11997,686],{"class":693},[679,11999,12000],{"class":689},"\"IE=edge\"",[679,12002,5387],{"class":693},[679,12004,12005,12007,12009,12011,12013,12016,12018,12020,12023],{"class":681,"line":964},[679,12006,4524],{"class":693},[679,12008,11968],{"class":4508},[679,12010,5283],{"class":880},[679,12012,686],{"class":693},[679,12014,12015],{"class":689},"\"viewport\"",[679,12017,11995],{"class":880},[679,12019,686],{"class":693},[679,12021,12022],{"class":689},"\"width=device-width, initial-scale=1\"",[679,12024,5387],{"class":693},[679,12026,12027,12029,12031,12033,12035],{"class":681,"line":977},[679,12028,4524],{"class":693},[679,12030,11750],{"class":4508},[679,12032,11753],{"class":693},[679,12034,11750],{"class":4508},[679,12036,4519],{"class":693},[679,12038,12039,12041,12043],{"class":681,"line":982},[679,12040,4524],{"class":693},[679,12042,786],{"class":4508},[679,12044,4519],{"class":693},[679,12046,12047,12049],{"class":681,"line":988},[679,12048,11770],{"class":4508},[679,12050,884],{"class":693},[679,12052,12053,12055,12057,12059],{"class":681,"line":993},[679,12054,11777],{"class":931},[679,12056,4282],{"class":693},[679,12058,11782],{"class":931},[679,12060,1186],{"class":693},[679,12062,12063,12065,12067,12069,12071],{"class":681,"line":2129},[679,12064,11789],{"class":931},[679,12066,4282],{"class":693},[679,12068,11794],{"class":931},[679,12070,11797],{"class":685},[679,12072,1186],{"class":693},[679,12074,12075],{"class":681,"line":2140},[679,12076,11804],{"class":693},[679,12078,12079,12081],{"class":681,"line":2145},[679,12080,11809],{"class":4508},[679,12082,12083],{"class":693},",\n",[679,12085,12086,12089],{"class":681,"line":2154},[679,12087,12088],{"class":4508}," p",[679,12090,884],{"class":693},[679,12092,12093,12095,12097,12099],{"class":681,"line":2159},[679,12094,11816],{"class":931},[679,12096,4282],{"class":693},[679,12098,11821],{"class":931},[679,12100,1186],{"class":693},[679,12102,12103],{"class":681,"line":2164},[679,12104,11804],{"class":693},[679,12106,12107,12109,12111],{"class":681,"line":3134},[679,12108,4577],{"class":693},[679,12110,786],{"class":4508},[679,12112,4519],{"class":693},[679,12114,12115,12117,12119],{"class":681,"line":3139},[679,12116,11840],{"class":693},[679,12118,11741],{"class":4508},[679,12120,4519],{"class":693},[679,12122,12123,12125,12127],{"class":681,"line":3144},[679,12124,11738],{"class":693},[679,12126,3006],{"class":4508},[679,12128,4519],{"class":693},[679,12130,12131,12133,12135,12138,12140],{"class":681,"line":3149},[679,12132,4524],{"class":693},[679,12134,11859],{"class":4508},[679,12136,12137],{"class":693},">Page Not Found\u003C/",[679,12139,11859],{"class":4508},[679,12141,4519],{"class":693},[679,12143,12144,12146,12148,12151,12153,12156,12159,12161],{"class":681,"line":3169},[679,12145,4524],{"class":693},[679,12147,651],{"class":4508},[679,12149,12150],{"class":880}," th:text",[679,12152,686],{"class":693},[679,12154,12155],{"class":689},"\"${error}\"",[679,12157,12158],{"class":693},">Error Info\u003C/",[679,12160,651],{"class":4508},[679,12162,4519],{"class":693},[679,12164,12165,12167,12169],{"class":681,"line":3185},[679,12166,11840],{"class":693},[679,12168,3006],{"class":4508},[679,12170,4519],{"class":693},[679,12172,12173,12175,12177],{"class":681,"line":3194},[679,12174,4586],{"class":693},[679,12176,4498],{"class":4508},[679,12178,4519],{"class":693},[651,12180,12181],{},[660,12182],{"alt":12183,"src":12184},"Error Template","./custom_error_template.png",[4542,12186,9042],{"id":9041},[651,12188,12189],{},"If create an error template in both public and templates the dynamic template will win out. As you can see the improvements in Spring Boot 1.4 made it really easy for us to handle errors. As I said when I started this article, errors are going to happen. We just need to make sure that we are handling them and making the user experience a pleasant one.",[651,12191,12192,12194],{},[2939,12193,11650],{}," What problems are you facing when trying to handle errors in your Spring applications?",[786,12196,12197],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":12199},[12200,12201,12202,12203],{"id":11684,"depth":790,"text":11685},{"id":11708,"depth":790,"text":11709},{"id":11890,"depth":790,"text":11891},{"id":9041,"depth":790,"text":9042},{"slug":12205,"published":797,"date":12206,"tags":12207,"cover":12208},"spring-boot-convention-based-error-pages","2017-03-24T08:00:33-04:00",[7055],"./custom-error-cover.jpg",{"title":228,"description":228},"blog/2017/03/24/spring-boot-convention-based-error-pages","imBOqBbuREPzPCJiwGCHv_u_Yij9S47Uy70AIahENkA",{"id":12213,"title":225,"body":12214,"description":225,"extension":793,"meta":13467,"navigation":797,"path":226,"seo":13472,"stem":13473,"__hash__":13474},"content/blog/2017/03/27/spring-stereotype-annotations.md",{"type":648,"value":12215,"toc":13460},[12216,12219,12223,12232,12242,12245,12248,12259,12262,12266,12274,12320,12342,12363,12367,12376,12382,12387,12390,12395,12398,12450,12502,12505,12510,12517,12669,12675,12679,12688,12693,12696,13042,13045,13236,13239,13446,13448,13451,13457],[651,12217,12218],{},"When software developers get introduced to the Spring Framework there are some concepts that can be a little confusing. The good news is once we wrap our head around them they can become second nature. Spring has some specialized annotations called Stereotype Annotations. If that concept is new to you don’t worry, we are going to break everything down for you in this article.",[4542,12220,12222],{"id":12221},"stereotype-annotation-defined","Stereotype & Annotation Defined",[651,12224,12225,12226,12231],{},"Before we dive into what Spring Stereotype annotations are I think it's important for us to understand what these two words mean. The word stereotype is defined in the ",[812,12227,12230],{"href":12228,"rel":12229},"https://www.merriam-webster.com/dictionary/stereotype",[816],"Merriam-Webster dictionary"," as:",[1004,12233,12234],{},[651,12235,12236],{},[7300,12237,12238,12241],{},[2939,12239,12240],{},"stereotype: (noun)"," something conforming to a fixed or general pattern; especially : a standardized mental picture that is held in common by members of a group and that represents an oversimplified opinion, prejudiced attitude, or uncritical judgment",[651,12243,12244],{},"You have probably come across stereotypes in real life. Examples of this could be that all Irish people are drunks or all blondes are unintelligent. We know that this, of course, is not only wrong but also extremely hurtful. In real life, you shouldn’t judge someone based on characteristics or because they belong to a particular group. While this isn’t a good practice in real life it is something we will do this in Spring.",[651,12246,12247],{},"We know that a controller's role in MVC pattern is to direct traffic and route requests. We know that a service is a place where we can place all of our business logic. Finally, we know that a repository is a place for us to implement data access layers for various persistence stores. In a Spring MVC project, it is important for us to group classes based on their role in the application. The word annotation is defined in the Merriam-Webster dictionary as:",[1004,12249,12250],{},[651,12251,12252,12255,12256],{},[2939,12253,12254],{},"annotation: (noun)"," ",[7300,12257,12258],{},"a note added (as to a statute) by way of comment or explanation often furnishing summaries of relevant court decisions",[651,12260,12261],{},"In the real world, we use the word annotation as a note or comment to describe something further. In Java, we use them much as the same but there are some technical things going on behind the scenes. We describe a class in our project as being a controller by using the @Controller annotation. We know just by looking at that class that it is a Controller but so does the framework which will do some cool things to this class behind the scenes.",[4542,12263,12265],{"id":12264},"spring-stereotypes-and-how-to-use-them","Spring Stereotypes and how to use them",[651,12267,12268,12269,664],{},"Now that we understand what these words mean it might give us some insight as to what role they play in our Spring projects. When a class is annotated with one of the following Stereotypes Spring will automatically register them in the application context. This makes the class available for dependency injection in other classes and this become vital to building out our applications. These classes can all be found under the ",[812,12270,12273],{"href":12271,"rel":12272},"http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/stereotype/package-summary.html",[816],"org.springframework.stereotype package",[1031,12275,12276,12286],{},[1034,12277,12278],{},[1037,12279,12280,12283],{},[1040,12281,12282],{},"Annotation",[1040,12284,12285],{},"Description",[1050,12287,12288,12296,12304,12312],{},[1037,12289,12290,12293],{},[1055,12291,12292],{},"@Component",[1055,12294,12295],{},"Indicates that an annotated class is a \"component\"",[1037,12297,12298,12301],{},[1055,12299,12300],{},"@Controller",[1055,12302,12303],{},"Indicates that an annotated class is a \"Controller\" (e.g.",[1037,12305,12306,12309],{},[1055,12307,12308],{},"@Service",[1055,12310,12311],{},"Indicates that an annotated class is a \"Service\", originally defined by Domain-Driven Design (Evans, 2003) as \"an operation offered as an interface that stands alone in the model, with no encapsulated state.\"",[1037,12313,12314,12317],{},[1055,12315,12316],{},"@Repository",[1055,12318,12319],{},"Indicates that an annotated class is a \"Repository\", originally defined by Domain-Driven Design (Evans, 2003) as \"a mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects\".",[1004,12321,12322],{},[651,12323,12324,12325,12330,12331,12336,12337,12341],{},"You might have expected to see ",[812,12326,12329],{"href":12327,"rel":12328},"https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RestController.html",[816],"@RestController"," in this list but it was omitted on purpose. @RestController is a convenience annotation that is itself annotated with ",[812,12332,12300],{"href":12333,"rel":12334,"title":12335},"https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/stereotype/Controller.html",[816],"annotation in org.springframework.stereotype"," and [@ResponseBody](",[812,12338,12339],{"href":12339,"rel":12340},"https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/ResponseBody.html",[816],"\" annotation in org.springframework.web.bind.annotation\").",[651,12343,12344,12345,12347,12348,12350,12351,12353,12354,12356,12357,12359,12360,12362],{},"There is a reason that ",[676,12346,12292],{}," was at the top of this list. ",[676,12349,12292],{}," is a generic stereotype annotation for any Spring-managed component. This means that if you want to register a class in the application context you can use ",[676,12352,12292],{}," to do so. The other stereotypes (Controller, Service, Repository) are simply specializations of the ",[676,12355,12292],{}," class. In fact, if you look at their source code you will see they themselves are annotated with ",[676,12358,12292],{},". If this is true, why wouldn't we just annotate everything with ",[676,12361,12292],{},"? First, it helps us and other developers classify a particular class. We can look at a class with the @Controller annotation and understand right away what its purpose is. Second, as we will see in the demo, it also helps us define well crafted point-cut demarcations when we get into using AOP. This should all make a little more sense as we move through a demo, so let's get to it.",[4542,12364,12366],{"id":12365},"spring-stereotype-annotation-demo-project","Spring Stereotype Annotation Demo Project",[651,12368,12369,12370,12375],{},"Now that we know what classes are available to us I want to ",[812,12371,12374],{"href":12372,"rel":12373},"https://github.com/cfaddict/stereotype-annotation-tutorial",[816],"create a project"," that shows this off. In this project, we are going to select the AOP, Web & Actuator dependencies.",[651,12377,12378],{},[660,12379],{"alt":12380,"src":12381},"Spring Stereotype","./new_project_1-300x189.png",[651,12383,12384],{},[660,12385],{"alt":12380,"src":12386},"./new_project_2-300x189.png",[651,12388,12389],{},"I am going to keep this demo intentionally simple so we can focus on the issue at hand. No matter how small or large the project is going to be I like to break my classes up into packages. In this case, I am going to create a controller, service and aop package. I am going to create both a Stereotype Controller and Service in their respective packages but I am not going to annotate these classes just yet.",[651,12391,12392],{},[660,12393],{"alt":12380,"src":12394},"./stereotype_step1-300x203.png",[651,12396,12397],{},"If you try and run the project at this point it will run but it won't do much. If you hit the /beans actuator endpoint you won't see the controller or service registered in the application context. If we were to annotate these 2 classes with the component annotation they would now be registered beans. We could do that but this goes back to us defining (stereotyping) what these classes are going to be used for. I am going to update these classes with the @Controller & @Service annotation respectively.",[669,12399,12401],{"className":4107,"code":12400,"language":4109,"meta":674,"style":674},"package com.therealdanvega.controller;\n\nimport org.springframework.stereotype.Controller;\n\n@Controller\npublic class StereotypeController {\n\n}\n",[676,12402,12403,12410,12414,12421,12425,12431,12442,12446],{"__ignoreMap":674},[679,12404,12405,12407],{"class":681,"line":682},[679,12406,2543],{"class":685},[679,12408,12409],{"class":693}," com.therealdanvega.controller;\n",[679,12411,12412],{"class":681,"line":790},[679,12413,889],{"emptyLinePlaceholder":797},[679,12415,12416,12418],{"class":681,"line":892},[679,12417,1999],{"class":685},[679,12419,12420],{"class":693}," org.springframework.stereotype.Controller;\n",[679,12422,12423],{"class":681,"line":901},[679,12424,889],{"emptyLinePlaceholder":797},[679,12426,12427,12429],{"class":681,"line":909},[679,12428,4116],{"class":693},[679,12430,9942],{"class":685},[679,12432,12433,12435,12437,12440],{"class":681,"line":918},[679,12434,6073],{"class":685},[679,12436,4512],{"class":685},[679,12438,12439],{"class":880}," StereotypeController",[679,12441,884],{"class":693},[679,12443,12444],{"class":681,"line":935},[679,12445,889],{"emptyLinePlaceholder":797},[679,12447,12448],{"class":681,"line":944},[679,12449,996],{"class":693},[669,12451,12453],{"className":4107,"code":12452,"language":4109,"meta":674,"style":674},"package com.therealdanvega.service;\n\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class StereotypeService {\n\n}\n",[676,12454,12455,12462,12466,12473,12477,12483,12494,12498],{"__ignoreMap":674},[679,12456,12457,12459],{"class":681,"line":682},[679,12458,2543],{"class":685},[679,12460,12461],{"class":693}," com.therealdanvega.service;\n",[679,12463,12464],{"class":681,"line":790},[679,12465,889],{"emptyLinePlaceholder":797},[679,12467,12468,12470],{"class":681,"line":892},[679,12469,1999],{"class":685},[679,12471,12472],{"class":693}," org.springframework.stereotype.Service;\n",[679,12474,12475],{"class":681,"line":901},[679,12476,889],{"emptyLinePlaceholder":797},[679,12478,12479,12481],{"class":681,"line":909},[679,12480,4116],{"class":693},[679,12482,9486],{"class":685},[679,12484,12485,12487,12489,12492],{"class":681,"line":918},[679,12486,6073],{"class":685},[679,12488,4512],{"class":685},[679,12490,12491],{"class":880}," StereotypeService",[679,12493,884],{"class":693},[679,12495,12496],{"class":681,"line":935},[679,12497,889],{"emptyLinePlaceholder":797},[679,12499,12500],{"class":681,"line":944},[679,12501,996],{"class":693},[651,12503,12504],{},"If I run this project now and pull up the /beans actuator endpoint I will see these classes registered in the application context.",[651,12506,12507],{},[660,12508],{"alt":12380,"src":12509},"./stereotype_beans-1024x356.png",[651,12511,12512,12513,12516],{},"This means that these classes are now available for dependency injection. If we go into our controller we can auto wire the service into our controller using constructor injection. This is new to Spring 4.3 and we no longer need to annotate this with ",[676,12514,12515],{},"@Autowired"," if there is only a single constructor.",[669,12518,12520],{"className":4107,"code":12519,"language":4109,"meta":674,"style":674},"package com.therealdanvega.controller;\n\nimport com.therealdanvega.service.StereotypeService;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class StereotypeController {\n\n private StereotypeService stereotypeService;\n\n public StereotypeController(StereotypeService stereotypeService) {\n this.stereotypeService = stereotypeService;\n }\n\n @RequestMapping(\"/\")\n public String home(){\n return stereotypeService.sayHello();\n }\n\n}\n",[676,12521,12522,12528,12532,12539,12546,12553,12557,12563,12573,12577,12584,12588,12602,12614,12618,12622,12634,12645,12657,12661,12665],{"__ignoreMap":674},[679,12523,12524,12526],{"class":681,"line":682},[679,12525,2543],{"class":685},[679,12527,12409],{"class":693},[679,12529,12530],{"class":681,"line":790},[679,12531,889],{"emptyLinePlaceholder":797},[679,12533,12534,12536],{"class":681,"line":892},[679,12535,1999],{"class":685},[679,12537,12538],{"class":693}," com.therealdanvega.service.StereotypeService;\n",[679,12540,12541,12543],{"class":681,"line":901},[679,12542,1999],{"class":685},[679,12544,12545],{"class":693}," org.springframework.web.bind.annotation.RequestMapping;\n",[679,12547,12548,12550],{"class":681,"line":909},[679,12549,1999],{"class":685},[679,12551,12552],{"class":693}," org.springframework.web.bind.annotation.RestController;\n",[679,12554,12555],{"class":681,"line":918},[679,12556,889],{"emptyLinePlaceholder":797},[679,12558,12559,12561],{"class":681,"line":935},[679,12560,4116],{"class":693},[679,12562,9212],{"class":685},[679,12564,12565,12567,12569,12571],{"class":681,"line":944},[679,12566,6073],{"class":685},[679,12568,4512],{"class":685},[679,12570,12439],{"class":880},[679,12572,884],{"class":693},[679,12574,12575],{"class":681,"line":959},[679,12576,889],{"emptyLinePlaceholder":797},[679,12578,12579,12581],{"class":681,"line":964},[679,12580,9232],{"class":685},[679,12582,12583],{"class":693}," StereotypeService stereotypeService;\n",[679,12585,12586],{"class":681,"line":977},[679,12587,889],{"emptyLinePlaceholder":797},[679,12589,12590,12592,12594,12597,12600],{"class":681,"line":982},[679,12591,6089],{"class":685},[679,12593,12439],{"class":880},[679,12595,12596],{"class":693},"(StereotypeService ",[679,12598,12599],{"class":2099},"stereotypeService",[679,12601,4390],{"class":693},[679,12603,12604,12606,12609,12611],{"class":681,"line":988},[679,12605,7862],{"class":931},[679,12607,12608],{"class":693},".stereotypeService ",[679,12610,686],{"class":685},[679,12612,12613],{"class":693}," stereotypeService;\n",[679,12615,12616],{"class":681,"line":993},[679,12617,985],{"class":693},[679,12619,12620],{"class":681,"line":2129},[679,12621,889],{"emptyLinePlaceholder":797},[679,12623,12624,12626,12628,12630,12632],{"class":681,"line":2140},[679,12625,6872],{"class":693},[679,12627,9275],{"class":685},[679,12629,745],{"class":693},[679,12631,10032],{"class":689},[679,12633,1339],{"class":693},[679,12635,12636,12638,12640,12643],{"class":681,"line":2145},[679,12637,6089],{"class":685},[679,12639,9289],{"class":693},[679,12641,12642],{"class":880},"home",[679,12644,2041],{"class":693},[679,12646,12647,12649,12652,12655],{"class":681,"line":2154},[679,12648,9444],{"class":685},[679,12650,12651],{"class":693}," stereotypeService.",[679,12653,12654],{"class":880},"sayHello",[679,12656,9317],{"class":693},[679,12658,12659],{"class":681,"line":2159},[679,12660,985],{"class":693},[679,12662,12663],{"class":681,"line":2164},[679,12664,889],{"emptyLinePlaceholder":797},[679,12666,12667],{"class":681,"line":3134},[679,12668,996],{"class":693},[651,12670,12671,12672,12674],{},"This works great but one of the questions that constantly comes up is what benefit does marking our classes with the appropriate annotation have? Why not just mark everything with ",[676,12673,12292],{}," and make all of our lives a little easier. This is a great question and I will try to answer in the next section.",[4542,12676,12678],{"id":12677},"spring-aop","Spring AOP",[651,12680,12681,12682,12687],{},"If you're not familiar with Aspect Oriented Programming (AOP) don't worry at all. I am going to give you a very quick introduction and hopefully, it will make more sense as to why we are using different stereotypes in our application. If you would like to read up on AOP before we dive in ",[812,12683,12686],{"href":12684,"rel":12685},"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html",[816],"you can do so here",". AOP is one of those tools that we developers need to have in our toolbox. We might not always have the need to bring it out but like a good wrench, it's really nice to have. I think Wikipedia does a really good job of explaining AOP so let's dive in.",[1004,12689,12690],{},[651,12691,12692],{},"In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a \"pointcut\" specification, such as \"log all function calls when the function's name begins with 'set'\". This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code core to the functionality. AOP forms a basis for aspect-oriented software development.",[651,12694,12695],{},"If the business came to us and asked to add logging calls before each of the controller methods in our application were called, we could take the following approach.",[669,12697,12699],{"className":4107,"code":12698,"language":4109,"meta":674,"style":674},"package com.therealdanvega.controller;\n\nimport com.therealdanvega.service.StereotypeService;\nimport org.apache.log4j.Logger;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class StereotypeController {\n\n private static final Logger logger = Logger.getLogger(StereotypeController.class);\n\n private StereotypeService stereotypeService;\n\n public StereotypeController(StereotypeService stereotypeService) {\n this.stereotypeService = stereotypeService;\n }\n\n @RequestMapping(\"/\")\n public String home(){\n logger.info(\"StereotypeController.home method called\");\n return stereotypeService.sayHello();\n }\n\n @RequestMapping(\"/list\")\n public String list(){\n logger.info(\"StereotypeController.home method called\");\n return \"list\";\n }\n\n @RequestMapping(\"/add\")\n public String add(){\n logger.info(\"StereotypeController.add method called\");\n return \"add\";\n }\n\n @RequestMapping(\"/save\")\n public String save(){\n logger.info(\"StereotypeController.save method called\");\n return \"save\";\n }\n\n}\n",[676,12700,12701,12707,12711,12717,12724,12730,12736,12740,12746,12756,12760,12781,12785,12791,12795,12807,12817,12821,12825,12837,12847,12861,12871,12875,12879,12892,12902,12914,12923,12927,12931,12944,12955,12968,12977,12981,12985,12998,13008,13021,13030,13034,13038],{"__ignoreMap":674},[679,12702,12703,12705],{"class":681,"line":682},[679,12704,2543],{"class":685},[679,12706,12409],{"class":693},[679,12708,12709],{"class":681,"line":790},[679,12710,889],{"emptyLinePlaceholder":797},[679,12712,12713,12715],{"class":681,"line":892},[679,12714,1999],{"class":685},[679,12716,12538],{"class":693},[679,12718,12719,12721],{"class":681,"line":901},[679,12720,1999],{"class":685},[679,12722,12723],{"class":693}," org.apache.log4j.Logger;\n",[679,12725,12726,12728],{"class":681,"line":909},[679,12727,1999],{"class":685},[679,12729,12545],{"class":693},[679,12731,12732,12734],{"class":681,"line":918},[679,12733,1999],{"class":685},[679,12735,12552],{"class":693},[679,12737,12738],{"class":681,"line":935},[679,12739,889],{"emptyLinePlaceholder":797},[679,12741,12742,12744],{"class":681,"line":944},[679,12743,4116],{"class":693},[679,12745,9212],{"class":685},[679,12747,12748,12750,12752,12754],{"class":681,"line":959},[679,12749,6073],{"class":685},[679,12751,4512],{"class":685},[679,12753,12439],{"class":880},[679,12755,884],{"class":693},[679,12757,12758],{"class":681,"line":964},[679,12759,889],{"emptyLinePlaceholder":797},[679,12761,12762,12764,12766,12769,12771,12773,12776,12778],{"class":681,"line":977},[679,12763,9232],{"class":685},[679,12765,6092],{"class":685},[679,12767,12768],{"class":685}," final",[679,12770,9235],{"class":693},[679,12772,686],{"class":685},[679,12774,12775],{"class":693}," Logger.",[679,12777,9243],{"class":880},[679,12779,12780],{"class":693},"(StereotypeController.class);\n",[679,12782,12783],{"class":681,"line":982},[679,12784,889],{"emptyLinePlaceholder":797},[679,12786,12787,12789],{"class":681,"line":988},[679,12788,9232],{"class":685},[679,12790,12583],{"class":693},[679,12792,12793],{"class":681,"line":993},[679,12794,889],{"emptyLinePlaceholder":797},[679,12796,12797,12799,12801,12803,12805],{"class":681,"line":2129},[679,12798,6089],{"class":685},[679,12800,12439],{"class":880},[679,12802,12596],{"class":693},[679,12804,12599],{"class":2099},[679,12806,4390],{"class":693},[679,12808,12809,12811,12813,12815],{"class":681,"line":2140},[679,12810,7862],{"class":931},[679,12812,12608],{"class":693},[679,12814,686],{"class":685},[679,12816,12613],{"class":693},[679,12818,12819],{"class":681,"line":2145},[679,12820,985],{"class":693},[679,12822,12823],{"class":681,"line":2154},[679,12824,889],{"emptyLinePlaceholder":797},[679,12826,12827,12829,12831,12833,12835],{"class":681,"line":2159},[679,12828,6872],{"class":693},[679,12830,9275],{"class":685},[679,12832,745],{"class":693},[679,12834,10032],{"class":689},[679,12836,1339],{"class":693},[679,12838,12839,12841,12843,12845],{"class":681,"line":2164},[679,12840,6089],{"class":685},[679,12842,9289],{"class":693},[679,12844,12642],{"class":880},[679,12846,2041],{"class":693},[679,12848,12849,12852,12854,12856,12859],{"class":681,"line":3134},[679,12850,12851],{"class":693}," logger.",[679,12853,9415],{"class":880},[679,12855,745],{"class":693},[679,12857,12858],{"class":689},"\"StereotypeController.home method called\"",[679,12860,1208],{"class":693},[679,12862,12863,12865,12867,12869],{"class":681,"line":3139},[679,12864,9444],{"class":685},[679,12866,12651],{"class":693},[679,12868,12654],{"class":880},[679,12870,9317],{"class":693},[679,12872,12873],{"class":681,"line":3144},[679,12874,985],{"class":693},[679,12876,12877],{"class":681,"line":3149},[679,12878,889],{"emptyLinePlaceholder":797},[679,12880,12881,12883,12885,12887,12890],{"class":681,"line":3169},[679,12882,6872],{"class":693},[679,12884,9275],{"class":685},[679,12886,745],{"class":693},[679,12888,12889],{"class":689},"\"/list\"",[679,12891,1339],{"class":693},[679,12893,12894,12896,12898,12900],{"class":681,"line":3185},[679,12895,6089],{"class":685},[679,12897,9289],{"class":693},[679,12899,7623],{"class":880},[679,12901,2041],{"class":693},[679,12903,12904,12906,12908,12910,12912],{"class":681,"line":3194},[679,12905,12851],{"class":693},[679,12907,9415],{"class":880},[679,12909,745],{"class":693},[679,12911,12858],{"class":689},[679,12913,1208],{"class":693},[679,12915,12916,12918,12921],{"class":681,"line":3199},[679,12917,9444],{"class":685},[679,12919,12920],{"class":689}," \"list\"",[679,12922,1186],{"class":693},[679,12924,12925],{"class":681,"line":3212},[679,12926,985],{"class":693},[679,12928,12929],{"class":681,"line":3217},[679,12930,889],{"emptyLinePlaceholder":797},[679,12932,12933,12935,12937,12939,12942],{"class":681,"line":3222},[679,12934,6872],{"class":693},[679,12936,9275],{"class":685},[679,12938,745],{"class":693},[679,12940,12941],{"class":689},"\"/add\"",[679,12943,1339],{"class":693},[679,12945,12946,12948,12950,12953],{"class":681,"line":3227},[679,12947,6089],{"class":685},[679,12949,9289],{"class":693},[679,12951,12952],{"class":880},"add",[679,12954,2041],{"class":693},[679,12956,12957,12959,12961,12963,12966],{"class":681,"line":3232},[679,12958,12851],{"class":693},[679,12960,9415],{"class":880},[679,12962,745],{"class":693},[679,12964,12965],{"class":689},"\"StereotypeController.add method called\"",[679,12967,1208],{"class":693},[679,12969,12970,12972,12975],{"class":681,"line":3499},[679,12971,9444],{"class":685},[679,12973,12974],{"class":689}," \"add\"",[679,12976,1186],{"class":693},[679,12978,12979],{"class":681,"line":3509},[679,12980,985],{"class":693},[679,12982,12983],{"class":681,"line":3516},[679,12984,889],{"emptyLinePlaceholder":797},[679,12986,12987,12989,12991,12993,12996],{"class":681,"line":3531},[679,12988,6872],{"class":693},[679,12990,9275],{"class":685},[679,12992,745],{"class":693},[679,12994,12995],{"class":689},"\"/save\"",[679,12997,1339],{"class":693},[679,12999,13000,13002,13004,13006],{"class":681,"line":3536},[679,13001,6089],{"class":685},[679,13003,9289],{"class":693},[679,13005,7629],{"class":880},[679,13007,2041],{"class":693},[679,13009,13010,13012,13014,13016,13019],{"class":681,"line":3541},[679,13011,12851],{"class":693},[679,13013,9415],{"class":880},[679,13015,745],{"class":693},[679,13017,13018],{"class":689},"\"StereotypeController.save method called\"",[679,13020,1208],{"class":693},[679,13022,13023,13025,13028],{"class":681,"line":3546},[679,13024,9444],{"class":685},[679,13026,13027],{"class":689}," \"save\"",[679,13029,1186],{"class":693},[679,13031,13032],{"class":681,"line":3551},[679,13033,985],{"class":693},[679,13035,13036],{"class":681,"line":3557},[679,13037,889],{"emptyLinePlaceholder":797},[679,13039,13040],{"class":681,"line":3567},[679,13041,996],{"class":693},[651,13043,13044],{},"As you can tell this becomes quite repetitive and this is just a single class. Can you image the complexity and unwanted noise this would add to our application? Thanks to AOP we can remove this type of boilerplate code. In this example, I am creating an aspect that will log before each controller method in our application. Please don't focus too much on the code but rather the solution that this brings.",[669,13046,13048],{"className":4107,"code":13047,"language":4109,"meta":674,"style":674},"package com.therealdanvega.aop;\n\nimport org.apache.log4j.Logger;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.springframework.stereotype.Component;\n\n@Component\n@Aspect\npublic class ControllerAspect {\n\n private static final Logger logger = Logger.getLogger(ControllerAspect.class);\n\n @Before(\"within(com.therealdanvega.controller..\\*)\")\n private void before(JoinPoint joinPoint){\n String caller = joinPoint.getSignature().toShortString();\n logger.info(caller + \" method called.\");\n }\n\n}\n",[676,13049,13050,13057,13061,13067,13074,13081,13088,13095,13099,13106,13113,13124,13128,13147,13151,13171,13188,13208,13224,13228,13232],{"__ignoreMap":674},[679,13051,13052,13054],{"class":681,"line":682},[679,13053,2543],{"class":685},[679,13055,13056],{"class":693}," com.therealdanvega.aop;\n",[679,13058,13059],{"class":681,"line":790},[679,13060,889],{"emptyLinePlaceholder":797},[679,13062,13063,13065],{"class":681,"line":892},[679,13064,1999],{"class":685},[679,13066,12723],{"class":693},[679,13068,13069,13071],{"class":681,"line":901},[679,13070,1999],{"class":685},[679,13072,13073],{"class":693}," org.aspectj.lang.JoinPoint;\n",[679,13075,13076,13078],{"class":681,"line":909},[679,13077,1999],{"class":685},[679,13079,13080],{"class":693}," org.aspectj.lang.annotation.Aspect;\n",[679,13082,13083,13085],{"class":681,"line":918},[679,13084,1999],{"class":685},[679,13086,13087],{"class":693}," org.aspectj.lang.annotation.Before;\n",[679,13089,13090,13092],{"class":681,"line":935},[679,13091,1999],{"class":685},[679,13093,13094],{"class":693}," org.springframework.stereotype.Component;\n",[679,13096,13097],{"class":681,"line":944},[679,13098,889],{"emptyLinePlaceholder":797},[679,13100,13101,13103],{"class":681,"line":959},[679,13102,4116],{"class":693},[679,13104,13105],{"class":685},"Component\n",[679,13107,13108,13110],{"class":681,"line":964},[679,13109,4116],{"class":693},[679,13111,13112],{"class":685},"Aspect\n",[679,13114,13115,13117,13119,13122],{"class":681,"line":977},[679,13116,6073],{"class":685},[679,13118,4512],{"class":685},[679,13120,13121],{"class":880}," ControllerAspect",[679,13123,884],{"class":693},[679,13125,13126],{"class":681,"line":982},[679,13127,889],{"emptyLinePlaceholder":797},[679,13129,13130,13132,13134,13136,13138,13140,13142,13144],{"class":681,"line":988},[679,13131,9232],{"class":685},[679,13133,6092],{"class":685},[679,13135,12768],{"class":685},[679,13137,9235],{"class":693},[679,13139,686],{"class":685},[679,13141,12775],{"class":693},[679,13143,9243],{"class":880},[679,13145,13146],{"class":693},"(ControllerAspect.class);\n",[679,13148,13149],{"class":681,"line":993},[679,13150,889],{"emptyLinePlaceholder":797},[679,13152,13153,13155,13158,13160,13163,13166,13169],{"class":681,"line":2129},[679,13154,6872],{"class":693},[679,13156,13157],{"class":685},"Before",[679,13159,745],{"class":693},[679,13161,13162],{"class":689},"\"within(com.therealdanvega.controller..",[679,13164,13165],{"class":931},"\\*",[679,13167,13168],{"class":689},")\"",[679,13170,1339],{"class":693},[679,13172,13173,13175,13177,13180,13183,13186],{"class":681,"line":2140},[679,13174,9232],{"class":685},[679,13176,6095],{"class":685},[679,13178,13179],{"class":880}," before",[679,13181,13182],{"class":693},"(JoinPoint ",[679,13184,13185],{"class":2099},"joinPoint",[679,13187,9533],{"class":693},[679,13189,13190,13193,13195,13198,13201,13203,13206],{"class":681,"line":2145},[679,13191,13192],{"class":693}," String caller ",[679,13194,686],{"class":685},[679,13196,13197],{"class":693}," joinPoint.",[679,13199,13200],{"class":880},"getSignature",[679,13202,10541],{"class":693},[679,13204,13205],{"class":880},"toShortString",[679,13207,9317],{"class":693},[679,13209,13210,13212,13214,13217,13219,13222],{"class":681,"line":2154},[679,13211,12851],{"class":693},[679,13213,9415],{"class":880},[679,13215,13216],{"class":693},"(caller ",[679,13218,3065],{"class":685},[679,13220,13221],{"class":689}," \" method called.\"",[679,13223,1208],{"class":693},[679,13225,13226],{"class":681,"line":2159},[679,13227,985],{"class":693},[679,13229,13230],{"class":681,"line":2164},[679,13231,889],{"emptyLinePlaceholder":797},[679,13233,13234],{"class":681,"line":3134},[679,13235,996],{"class":693},[651,13237,13238],{},"In the previous example, we are targeting any class in our com.therealdanvega.controller package. What if we had @Controller & @RestControllers in this package. We can also target a particular set of classes or methods via annotations.",[669,13240,13242],{"className":4107,"code":13241,"language":4109,"meta":674,"style":674},"package com.therealdanvega.aop;\n\nimport org.apache.log4j.Logger;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.springframework.stereotype.Component;\n\n@Component\n@Aspect\npublic class ControllerAspect {\n\n private static final Logger logger = Logger.getLogger(ControllerAspect.class);\n\n @Pointcut(\"@target(org.springframework.web.bind.annotation.RestController)\")\n public void targetRestControllers(){}\n\n @Before(\"within(com.therealdanvega.controller..\\*) && targetRestControllers()\")\n private void before(JoinPoint joinPoint){\n String caller = joinPoint.getSignature().toShortString();\n logger.info(caller + \" method called.\");\n }\n\n}\n",[676,13243,13244,13250,13254,13260,13266,13272,13278,13285,13291,13295,13301,13307,13317,13321,13339,13343,13357,13369,13373,13390,13404,13420,13434,13438,13442],{"__ignoreMap":674},[679,13245,13246,13248],{"class":681,"line":682},[679,13247,2543],{"class":685},[679,13249,13056],{"class":693},[679,13251,13252],{"class":681,"line":790},[679,13253,889],{"emptyLinePlaceholder":797},[679,13255,13256,13258],{"class":681,"line":892},[679,13257,1999],{"class":685},[679,13259,12723],{"class":693},[679,13261,13262,13264],{"class":681,"line":901},[679,13263,1999],{"class":685},[679,13265,13073],{"class":693},[679,13267,13268,13270],{"class":681,"line":909},[679,13269,1999],{"class":685},[679,13271,13080],{"class":693},[679,13273,13274,13276],{"class":681,"line":918},[679,13275,1999],{"class":685},[679,13277,13087],{"class":693},[679,13279,13280,13282],{"class":681,"line":935},[679,13281,1999],{"class":685},[679,13283,13284],{"class":693}," org.aspectj.lang.annotation.Pointcut;\n",[679,13286,13287,13289],{"class":681,"line":944},[679,13288,1999],{"class":685},[679,13290,13094],{"class":693},[679,13292,13293],{"class":681,"line":959},[679,13294,889],{"emptyLinePlaceholder":797},[679,13296,13297,13299],{"class":681,"line":964},[679,13298,4116],{"class":693},[679,13300,13105],{"class":685},[679,13302,13303,13305],{"class":681,"line":977},[679,13304,4116],{"class":693},[679,13306,13112],{"class":685},[679,13308,13309,13311,13313,13315],{"class":681,"line":982},[679,13310,6073],{"class":685},[679,13312,4512],{"class":685},[679,13314,13121],{"class":880},[679,13316,884],{"class":693},[679,13318,13319],{"class":681,"line":988},[679,13320,889],{"emptyLinePlaceholder":797},[679,13322,13323,13325,13327,13329,13331,13333,13335,13337],{"class":681,"line":993},[679,13324,9232],{"class":685},[679,13326,6092],{"class":685},[679,13328,12768],{"class":685},[679,13330,9235],{"class":693},[679,13332,686],{"class":685},[679,13334,12775],{"class":693},[679,13336,9243],{"class":880},[679,13338,13146],{"class":693},[679,13340,13341],{"class":681,"line":2129},[679,13342,889],{"emptyLinePlaceholder":797},[679,13344,13345,13347,13350,13352,13355],{"class":681,"line":2140},[679,13346,6872],{"class":693},[679,13348,13349],{"class":685},"Pointcut",[679,13351,745],{"class":693},[679,13353,13354],{"class":689},"\"@target(org.springframework.web.bind.annotation.RestController)\"",[679,13356,1339],{"class":693},[679,13358,13359,13361,13363,13366],{"class":681,"line":2145},[679,13360,6089],{"class":685},[679,13362,6095],{"class":685},[679,13364,13365],{"class":880}," targetRestControllers",[679,13367,13368],{"class":693},"(){}\n",[679,13370,13371],{"class":681,"line":2154},[679,13372,889],{"emptyLinePlaceholder":797},[679,13374,13375,13377,13379,13381,13383,13385,13388],{"class":681,"line":2159},[679,13376,6872],{"class":693},[679,13378,13157],{"class":685},[679,13380,745],{"class":693},[679,13382,13162],{"class":689},[679,13384,13165],{"class":931},[679,13386,13387],{"class":689},") && targetRestControllers()\"",[679,13389,1339],{"class":693},[679,13391,13392,13394,13396,13398,13400,13402],{"class":681,"line":2164},[679,13393,9232],{"class":685},[679,13395,6095],{"class":685},[679,13397,13179],{"class":880},[679,13399,13182],{"class":693},[679,13401,13185],{"class":2099},[679,13403,9533],{"class":693},[679,13405,13406,13408,13410,13412,13414,13416,13418],{"class":681,"line":3134},[679,13407,13192],{"class":693},[679,13409,686],{"class":685},[679,13411,13197],{"class":693},[679,13413,13200],{"class":880},[679,13415,10541],{"class":693},[679,13417,13205],{"class":880},[679,13419,9317],{"class":693},[679,13421,13422,13424,13426,13428,13430,13432],{"class":681,"line":3139},[679,13423,12851],{"class":693},[679,13425,9415],{"class":880},[679,13427,13216],{"class":693},[679,13429,3065],{"class":685},[679,13431,13221],{"class":689},[679,13433,1208],{"class":693},[679,13435,13436],{"class":681,"line":3144},[679,13437,985],{"class":693},[679,13439,13440],{"class":681,"line":3149},[679,13441,889],{"emptyLinePlaceholder":797},[679,13443,13444],{"class":681,"line":3169},[679,13445,996],{"class":693},[4542,13447,9042],{"id":9041},[651,13449,13450],{},"I hope this article was able to help you understand the Spring Stereotype Annotations a little bit more.",[651,13452,13453,13454,13456],{},"_",[2939,13455,11650],{}," What concept in Spring do you find most difficult to grasp? _",[786,13458,13459],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":13461},[13462,13463,13464,13465,13466],{"id":12221,"depth":790,"text":12222},{"id":12264,"depth":790,"text":12265},{"id":12365,"depth":790,"text":12366},{"id":12677,"depth":790,"text":12678},{"id":9041,"depth":790,"text":9042},{"slug":13468,"published":797,"date":13469,"tags":13470,"cover":13471},"spring-stereotype-annotations","2017-03-27T08:00:15-04:00",[7055],"./stereotype_annotations.png",{"title":225,"description":225},"blog/2017/03/27/spring-stereotype-annotations","cugwnCfhZOym7qfyjsSDRMWq0gxf2XFsMTj7bD1ySjI",{"id":13476,"title":222,"body":13477,"description":222,"extension":793,"meta":15406,"navigation":797,"path":223,"seo":15411,"stem":15412,"__hash__":15413},"content/blog/2017/03/31/using-project-lombok-spring-boot.md",{"type":648,"value":13478,"toc":15398},[13479,13504,13508,13511,13520,13529,13533,13537,13546,13551,13556,13559,13663,13666,13672,13675,15254,15258,15267,15376,15379,15385,15387,15390,15396],[651,13480,13481,13482,13487,13488,13493,13494,13499,13500,13503],{},"In this article, we get to take a look at an awesome little project called ",[812,13483,13486],{"href":13484,"rel":13485},"https://projectlombok.org/",[816],"Project Lombok",". If you have been following me for awhile now then you already know ",[812,13489,13492],{"href":13490,"rel":13491},"http://courses.danvega.dev/p/the-complete-apache-groovy-developer-course",[816],"I am a huge fan"," of the ",[812,13495,13498],{"href":13496,"rel":13497},"http://groovy-lang.org/",[816],"Groovy Programming Language",". It takes a lot of the things I don't like in Java and makes them better. Like, way better! Groovy is also really ",[7300,13501,13502],{},"GOOD"," at cutting down the amount of ceremonious code you have to write. If you are working on a Java project and are looking for a way to cut down some of the noise in your code I have a great solution for you. In this article, we are going to look at Project Lombok and dive into what it is and how to use it in your next Spring Boot Application. ",[4542,13505,13507],{"id":13506},"what-is-project-lombok","What is Project Lombok?",[651,13509,13510],{},"As I said in the introduction to this article, one of the biggest complaints against Java is how much noise can be found in a single class. Project Lombok saw this as a problem and aims to reduce the noise of some of the worst offenders by replacing them with a simple set of annotations. One of the worst offenders in Java are classes that have a ton of fields in them and consist of a getter & setter for each property. I will refer to these \"types\" of classes as data classes going forward. IDEs have a come a long way and help us generate this code but every single time you make a change to that data class you will need to go back and regenerate that code or even worse, write it yourself. In this example below, straight from the documentation, all of the getters, setters and a to string method were created for us just by adding the @Data annotation to our Data class.",[651,13512,13513,13517,13518],{},[660,13514],{"alt":13515,"src":13516},"Project Lombok in Action","./lombok.png"," ",[7300,13519,13515],{},[651,13521,13522,13523,13528],{},"Pretty awesome right? Project Lombok consists of more than just this single annotation. If you want to learn about them please ",[812,13524,13527],{"href":13525,"rel":13526},"http://jnb.ociweb.com/jnb/jnbJan2010.html#intro",[816],"read through the documentation",". For now, we are just going to run through a basic demo. ",[4542,13530,13532],{"id":13531},"project-lombok-demo","Project Lombok Demo",[5909,13534,13536],{"id":13535},"before-project-lombok","Before Project Lombok",[651,13538,13539,13540,13545],{},"We are going to create a new Spring Boot application. If you want to grab the ",[812,13541,13544],{"href":13542,"rel":13543},"https://github.com/cfaddict/lombok",[816],"project source code you can do so here",". You can select Lombok right from the core section of the Spring Initilizr. ",[651,13547,13548],{},[660,13549],{"alt":13486,"src":13550},"./lombok_demo_1-300x189.png",[651,13552,13553],{},[660,13554],{"alt":13486,"src":13555},"./lombok_demo_2-300x189.png",[651,13557,13558],{},"I am going to create a new user object with a bunch of fields. ",[669,13560,13562],{"className":4107,"code":13561,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport java.util.Date;\n\npublic class User {\n\n private String first;\n private String middle;\n private String last;\n private String email;\n private Date dob;\n private String foo;\n private String bar;\n private String a,b,c;\n\n}\n",[676,13563,13564,13570,13574,13581,13585,13595,13599,13606,13613,13620,13627,13634,13641,13648,13655,13659],{"__ignoreMap":674},[679,13565,13566,13568],{"class":681,"line":682},[679,13567,2543],{"class":685},[679,13569,6039],{"class":693},[679,13571,13572],{"class":681,"line":790},[679,13573,889],{"emptyLinePlaceholder":797},[679,13575,13576,13578],{"class":681,"line":892},[679,13577,1999],{"class":685},[679,13579,13580],{"class":693}," java.util.Date;\n",[679,13582,13583],{"class":681,"line":901},[679,13584,889],{"emptyLinePlaceholder":797},[679,13586,13587,13589,13591,13593],{"class":681,"line":909},[679,13588,6073],{"class":685},[679,13590,4512],{"class":685},[679,13592,881],{"class":880},[679,13594,884],{"class":693},[679,13596,13597],{"class":681,"line":918},[679,13598,889],{"emptyLinePlaceholder":797},[679,13600,13601,13603],{"class":681,"line":935},[679,13602,9232],{"class":685},[679,13604,13605],{"class":693}," String first;\n",[679,13607,13608,13610],{"class":681,"line":944},[679,13609,9232],{"class":685},[679,13611,13612],{"class":693}," String middle;\n",[679,13614,13615,13617],{"class":681,"line":959},[679,13616,9232],{"class":685},[679,13618,13619],{"class":693}," String last;\n",[679,13621,13622,13624],{"class":681,"line":964},[679,13623,9232],{"class":685},[679,13625,13626],{"class":693}," String email;\n",[679,13628,13629,13631],{"class":681,"line":977},[679,13630,9232],{"class":685},[679,13632,13633],{"class":693}," Date dob;\n",[679,13635,13636,13638],{"class":681,"line":982},[679,13637,9232],{"class":685},[679,13639,13640],{"class":693}," String foo;\n",[679,13642,13643,13645],{"class":681,"line":988},[679,13644,9232],{"class":685},[679,13646,13647],{"class":693}," String bar;\n",[679,13649,13650,13652],{"class":681,"line":993},[679,13651,9232],{"class":685},[679,13653,13654],{"class":693}," String a,b,c;\n",[679,13656,13657],{"class":681,"line":2129},[679,13658,889],{"emptyLinePlaceholder":797},[679,13660,13661],{"class":681,"line":2140},[679,13662,996],{"class":693},[651,13664,13665],{},"That looks like a pretty clean class, but we aren't done yet. Now we need to create a getter & setter for each of those properties. We also would probably want to add a toString, equals and hashCode method to our class. If you didn't know this already, you can generate these using cmd + n on the mac (cntrl+n on a PC).",[651,13667,13668],{},[660,13669],{"alt":13670,"src":13671},"Project lombok","./lombok_demo_3-193x300.png",[651,13673,13674],{},"Now our class is correct but that is a whole bunch of unwanted code. Not to mention what a maintenance nightmare this is. Every single time this class changes we need to update the getters, setters, and methods by generating them over or even worse, actually writing code. ",[669,13676,13678],{"className":4107,"code":13677,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport java.util.Date;\n\npublic class User {\n\n private String first;\n private String middle;\n private String last;\n private String email;\n private Date dob;\n private String foo;\n private String bar;\n private String a,b,c;\n\n public String getFirst() {\n return first;\n }\n\n public void setFirst(String first) {\n this.first = first;\n }\n\n public String getMiddle() {\n return middle;\n }\n\n public void setMiddle(String middle) {\n this.middle = middle;\n }\n\n public String getLast() {\n return last;\n }\n\n public void setLast(String last) {\n this.last = last;\n }\n\n public String getEmail() {\n return email;\n }\n\n public void setEmail(String email) {\n this.email = email;\n }\n\n public Date getDob() {\n return dob;\n }\n\n public void setDob(Date dob) {\n this.dob = dob;\n }\n\n public String getFoo() {\n return foo;\n }\n\n public void setFoo(String foo) {\n this.foo = foo;\n }\n\n public String getBar() {\n return bar;\n }\n\n public void setBar(String bar) {\n this.bar = bar;\n }\n\n public String getA() {\n return a;\n }\n\n public void setA(String a) {\n this.a = a;\n }\n\n public String getB() {\n return b;\n }\n\n public void setB(String b) {\n this.b = b;\n }\n\n public String getC() {\n return c;\n }\n\n public void setC(String c) {\n this.c = c;\n }\n\n @Override\n public String toString() {\n return \"User{\" +\n \"first='\" + first + '\\'' +\n \", last='\" + last + '\\'' +\n \", email='\" + email + '\\'' +\n '}';\n }\n\n @Override\n public boolean equals(Object o) {\n if (this == o) return true;\n if (o == null || getClass() != o.getClass()) return false;\n\n User user = (User) o;\n\n if (!first.equals(user.first)) return false;\n if (middle != null ? !middle.equals(user.middle) : user.middle != null) return false;\n if (!last.equals(user.last)) return false;\n if (!email.equals(user.email)) return false;\n if (!dob.equals(user.dob)) return false;\n if (foo != null ? !foo.equals(user.foo) : user.foo != null) return false;\n if (bar != null ? !bar.equals(user.bar) : user.bar != null) return false;\n if (a != null ? !a.equals(user.a) : user.a != null) return false;\n if (b != null ? !b.equals(user.b) : user.b != null) return false;\n return c != null ? c.equals(user.c) : user.c == null;\n }\n\n @Override\n public int hashCode() {\n int result = first.hashCode();\n result = 31 * result + (middle != null ? middle.hashCode() : 0);\n result = 31 * result + last.hashCode();\n result = 31 * result + email.hashCode();\n result = 31 * result + dob.hashCode();\n result = 31 * result + (foo != null ? foo.hashCode() : 0);\n result = 31 * result + (bar != null ? bar.hashCode() : 0);\n result = 31 * result + (a != null ? a.hashCode() : 0);\n result = 31 * result + (b != null ? b.hashCode() : 0);\n result = 31 * result + (c != null ? c.hashCode() : 0);\n return result;\n }\n}\n",[676,13679,13680,13686,13690,13696,13700,13710,13714,13720,13726,13732,13738,13744,13750,13756,13762,13766,13777,13784,13788,13792,13808,13819,13823,13827,13838,13845,13849,13853,13869,13880,13884,13888,13899,13906,13910,13914,13930,13941,13945,13949,13960,13967,13971,13975,13991,14002,14006,14010,14022,14029,14033,14037,14054,14065,14069,14073,14084,14091,14095,14099,14115,14126,14130,14134,14145,14152,14156,14160,14176,14187,14191,14195,14206,14213,14217,14221,14236,14247,14251,14255,14266,14273,14277,14281,14297,14308,14312,14316,14327,14334,14338,14342,14358,14369,14373,14377,14383,14394,14404,14426,14446,14466,14473,14477,14481,14487,14505,14526,14562,14566,14575,14579,14602,14644,14666,14688,14710,14750,14790,14830,14870,14902,14906,14911,14918,14931,14950,14990,15012,15034,15056,15092,15128,15164,15200,15236,15244,15249],{"__ignoreMap":674},[679,13681,13682,13684],{"class":681,"line":682},[679,13683,2543],{"class":685},[679,13685,6039],{"class":693},[679,13687,13688],{"class":681,"line":790},[679,13689,889],{"emptyLinePlaceholder":797},[679,13691,13692,13694],{"class":681,"line":892},[679,13693,1999],{"class":685},[679,13695,13580],{"class":693},[679,13697,13698],{"class":681,"line":901},[679,13699,889],{"emptyLinePlaceholder":797},[679,13701,13702,13704,13706,13708],{"class":681,"line":909},[679,13703,6073],{"class":685},[679,13705,4512],{"class":685},[679,13707,881],{"class":880},[679,13709,884],{"class":693},[679,13711,13712],{"class":681,"line":918},[679,13713,889],{"emptyLinePlaceholder":797},[679,13715,13716,13718],{"class":681,"line":935},[679,13717,9232],{"class":685},[679,13719,13605],{"class":693},[679,13721,13722,13724],{"class":681,"line":944},[679,13723,9232],{"class":685},[679,13725,13612],{"class":693},[679,13727,13728,13730],{"class":681,"line":959},[679,13729,9232],{"class":685},[679,13731,13619],{"class":693},[679,13733,13734,13736],{"class":681,"line":964},[679,13735,9232],{"class":685},[679,13737,13626],{"class":693},[679,13739,13740,13742],{"class":681,"line":977},[679,13741,9232],{"class":685},[679,13743,13633],{"class":693},[679,13745,13746,13748],{"class":681,"line":982},[679,13747,9232],{"class":685},[679,13749,13640],{"class":693},[679,13751,13752,13754],{"class":681,"line":988},[679,13753,9232],{"class":685},[679,13755,13647],{"class":693},[679,13757,13758,13760],{"class":681,"line":993},[679,13759,9232],{"class":685},[679,13761,13654],{"class":693},[679,13763,13764],{"class":681,"line":2129},[679,13765,889],{"emptyLinePlaceholder":797},[679,13767,13768,13770,13772,13775],{"class":681,"line":2140},[679,13769,6089],{"class":685},[679,13771,9289],{"class":693},[679,13773,13774],{"class":880},"getFirst",[679,13776,2667],{"class":693},[679,13778,13779,13781],{"class":681,"line":2145},[679,13780,9444],{"class":685},[679,13782,13783],{"class":693}," first;\n",[679,13785,13786],{"class":681,"line":2154},[679,13787,985],{"class":693},[679,13789,13790],{"class":681,"line":2159},[679,13791,889],{"emptyLinePlaceholder":797},[679,13793,13794,13796,13798,13801,13803,13806],{"class":681,"line":2164},[679,13795,6089],{"class":685},[679,13797,6095],{"class":685},[679,13799,13800],{"class":880}," setFirst",[679,13802,11400],{"class":693},[679,13804,13805],{"class":2099},"first",[679,13807,4390],{"class":693},[679,13809,13810,13812,13815,13817],{"class":681,"line":3134},[679,13811,7862],{"class":931},[679,13813,13814],{"class":693},".first ",[679,13816,686],{"class":685},[679,13818,13783],{"class":693},[679,13820,13821],{"class":681,"line":3139},[679,13822,985],{"class":693},[679,13824,13825],{"class":681,"line":3144},[679,13826,889],{"emptyLinePlaceholder":797},[679,13828,13829,13831,13833,13836],{"class":681,"line":3149},[679,13830,6089],{"class":685},[679,13832,9289],{"class":693},[679,13834,13835],{"class":880},"getMiddle",[679,13837,2667],{"class":693},[679,13839,13840,13842],{"class":681,"line":3169},[679,13841,9444],{"class":685},[679,13843,13844],{"class":693}," middle;\n",[679,13846,13847],{"class":681,"line":3185},[679,13848,985],{"class":693},[679,13850,13851],{"class":681,"line":3194},[679,13852,889],{"emptyLinePlaceholder":797},[679,13854,13855,13857,13859,13862,13864,13867],{"class":681,"line":3199},[679,13856,6089],{"class":685},[679,13858,6095],{"class":685},[679,13860,13861],{"class":880}," setMiddle",[679,13863,11400],{"class":693},[679,13865,13866],{"class":2099},"middle",[679,13868,4390],{"class":693},[679,13870,13871,13873,13876,13878],{"class":681,"line":3212},[679,13872,7862],{"class":931},[679,13874,13875],{"class":693},".middle ",[679,13877,686],{"class":685},[679,13879,13844],{"class":693},[679,13881,13882],{"class":681,"line":3217},[679,13883,985],{"class":693},[679,13885,13886],{"class":681,"line":3222},[679,13887,889],{"emptyLinePlaceholder":797},[679,13889,13890,13892,13894,13897],{"class":681,"line":3227},[679,13891,6089],{"class":685},[679,13893,9289],{"class":693},[679,13895,13896],{"class":880},"getLast",[679,13898,2667],{"class":693},[679,13900,13901,13903],{"class":681,"line":3232},[679,13902,9444],{"class":685},[679,13904,13905],{"class":693}," last;\n",[679,13907,13908],{"class":681,"line":3499},[679,13909,985],{"class":693},[679,13911,13912],{"class":681,"line":3509},[679,13913,889],{"emptyLinePlaceholder":797},[679,13915,13916,13918,13920,13923,13925,13928],{"class":681,"line":3516},[679,13917,6089],{"class":685},[679,13919,6095],{"class":685},[679,13921,13922],{"class":880}," setLast",[679,13924,11400],{"class":693},[679,13926,13927],{"class":2099},"last",[679,13929,4390],{"class":693},[679,13931,13932,13934,13937,13939],{"class":681,"line":3531},[679,13933,7862],{"class":931},[679,13935,13936],{"class":693},".last ",[679,13938,686],{"class":685},[679,13940,13905],{"class":693},[679,13942,13943],{"class":681,"line":3536},[679,13944,985],{"class":693},[679,13946,13947],{"class":681,"line":3541},[679,13948,889],{"emptyLinePlaceholder":797},[679,13950,13951,13953,13955,13958],{"class":681,"line":3546},[679,13952,6089],{"class":685},[679,13954,9289],{"class":693},[679,13956,13957],{"class":880},"getEmail",[679,13959,2667],{"class":693},[679,13961,13962,13964],{"class":681,"line":3551},[679,13963,9444],{"class":685},[679,13965,13966],{"class":693}," email;\n",[679,13968,13969],{"class":681,"line":3557},[679,13970,985],{"class":693},[679,13972,13973],{"class":681,"line":3567},[679,13974,889],{"emptyLinePlaceholder":797},[679,13976,13977,13979,13981,13984,13986,13989],{"class":681,"line":3574},[679,13978,6089],{"class":685},[679,13980,6095],{"class":685},[679,13982,13983],{"class":880}," setEmail",[679,13985,11400],{"class":693},[679,13987,13988],{"class":2099},"email",[679,13990,4390],{"class":693},[679,13992,13993,13995,13998,14000],{"class":681,"line":3589},[679,13994,7862],{"class":931},[679,13996,13997],{"class":693},".email ",[679,13999,686],{"class":685},[679,14001,13966],{"class":693},[679,14003,14004],{"class":681,"line":3594},[679,14005,985],{"class":693},[679,14007,14008],{"class":681,"line":3602},[679,14009,889],{"emptyLinePlaceholder":797},[679,14011,14012,14014,14017,14020],{"class":681,"line":3608},[679,14013,6089],{"class":685},[679,14015,14016],{"class":693}," Date ",[679,14018,14019],{"class":880},"getDob",[679,14021,2667],{"class":693},[679,14023,14024,14026],{"class":681,"line":3619},[679,14025,9444],{"class":685},[679,14027,14028],{"class":693}," dob;\n",[679,14030,14031],{"class":681,"line":3624},[679,14032,985],{"class":693},[679,14034,14035],{"class":681,"line":3629},[679,14036,889],{"emptyLinePlaceholder":797},[679,14038,14039,14041,14043,14046,14049,14052],{"class":681,"line":3639},[679,14040,6089],{"class":685},[679,14042,6095],{"class":685},[679,14044,14045],{"class":880}," setDob",[679,14047,14048],{"class":693},"(Date ",[679,14050,14051],{"class":2099},"dob",[679,14053,4390],{"class":693},[679,14055,14056,14058,14061,14063],{"class":681,"line":3644},[679,14057,7862],{"class":931},[679,14059,14060],{"class":693},".dob ",[679,14062,686],{"class":685},[679,14064,14028],{"class":693},[679,14066,14067],{"class":681,"line":3649},[679,14068,985],{"class":693},[679,14070,14071],{"class":681,"line":3659},[679,14072,889],{"emptyLinePlaceholder":797},[679,14074,14075,14077,14079,14082],{"class":681,"line":3664},[679,14076,6089],{"class":685},[679,14078,9289],{"class":693},[679,14080,14081],{"class":880},"getFoo",[679,14083,2667],{"class":693},[679,14085,14086,14088],{"class":681,"line":3669},[679,14087,9444],{"class":685},[679,14089,14090],{"class":693}," foo;\n",[679,14092,14093],{"class":681,"line":3679},[679,14094,985],{"class":693},[679,14096,14097],{"class":681,"line":3684},[679,14098,889],{"emptyLinePlaceholder":797},[679,14100,14101,14103,14105,14108,14110,14113],{"class":681,"line":3689},[679,14102,6089],{"class":685},[679,14104,6095],{"class":685},[679,14106,14107],{"class":880}," setFoo",[679,14109,11400],{"class":693},[679,14111,14112],{"class":2099},"foo",[679,14114,4390],{"class":693},[679,14116,14117,14119,14122,14124],{"class":681,"line":3699},[679,14118,7862],{"class":931},[679,14120,14121],{"class":693},".foo ",[679,14123,686],{"class":685},[679,14125,14090],{"class":693},[679,14127,14128],{"class":681,"line":3704},[679,14129,985],{"class":693},[679,14131,14132],{"class":681,"line":3709},[679,14133,889],{"emptyLinePlaceholder":797},[679,14135,14136,14138,14140,14143],{"class":681,"line":3719},[679,14137,6089],{"class":685},[679,14139,9289],{"class":693},[679,14141,14142],{"class":880},"getBar",[679,14144,2667],{"class":693},[679,14146,14147,14149],{"class":681,"line":3724},[679,14148,9444],{"class":685},[679,14150,14151],{"class":693}," bar;\n",[679,14153,14154],{"class":681,"line":3729},[679,14155,985],{"class":693},[679,14157,14158],{"class":681,"line":3739},[679,14159,889],{"emptyLinePlaceholder":797},[679,14161,14162,14164,14166,14169,14171,14174],{"class":681,"line":3744},[679,14163,6089],{"class":685},[679,14165,6095],{"class":685},[679,14167,14168],{"class":880}," setBar",[679,14170,11400],{"class":693},[679,14172,14173],{"class":2099},"bar",[679,14175,4390],{"class":693},[679,14177,14178,14180,14183,14185],{"class":681,"line":3749},[679,14179,7862],{"class":931},[679,14181,14182],{"class":693},".bar ",[679,14184,686],{"class":685},[679,14186,14151],{"class":693},[679,14188,14189],{"class":681,"line":3759},[679,14190,985],{"class":693},[679,14192,14193],{"class":681,"line":3764},[679,14194,889],{"emptyLinePlaceholder":797},[679,14196,14197,14199,14201,14204],{"class":681,"line":3769},[679,14198,6089],{"class":685},[679,14200,9289],{"class":693},[679,14202,14203],{"class":880},"getA",[679,14205,2667],{"class":693},[679,14207,14208,14210],{"class":681,"line":3779},[679,14209,9444],{"class":685},[679,14211,14212],{"class":693}," a;\n",[679,14214,14215],{"class":681,"line":3784},[679,14216,985],{"class":693},[679,14218,14219],{"class":681,"line":3789},[679,14220,889],{"emptyLinePlaceholder":797},[679,14222,14223,14225,14227,14230,14232,14234],{"class":681,"line":3800},[679,14224,6089],{"class":685},[679,14226,6095],{"class":685},[679,14228,14229],{"class":880}," setA",[679,14231,11400],{"class":693},[679,14233,812],{"class":2099},[679,14235,4390],{"class":693},[679,14237,14238,14240,14243,14245],{"class":681,"line":3805},[679,14239,7862],{"class":931},[679,14241,14242],{"class":693},".a ",[679,14244,686],{"class":685},[679,14246,14212],{"class":693},[679,14248,14249],{"class":681,"line":3810},[679,14250,985],{"class":693},[679,14252,14253],{"class":681,"line":3820},[679,14254,889],{"emptyLinePlaceholder":797},[679,14256,14257,14259,14261,14264],{"class":681,"line":3825},[679,14258,6089],{"class":685},[679,14260,9289],{"class":693},[679,14262,14263],{"class":880},"getB",[679,14265,2667],{"class":693},[679,14267,14268,14270],{"class":681,"line":3830},[679,14269,9444],{"class":685},[679,14271,14272],{"class":693}," b;\n",[679,14274,14275],{"class":681,"line":3840},[679,14276,985],{"class":693},[679,14278,14279],{"class":681,"line":3845},[679,14280,889],{"emptyLinePlaceholder":797},[679,14282,14283,14285,14287,14290,14292,14295],{"class":681,"line":3850},[679,14284,6089],{"class":685},[679,14286,6095],{"class":685},[679,14288,14289],{"class":880}," setB",[679,14291,11400],{"class":693},[679,14293,14294],{"class":2099},"b",[679,14296,4390],{"class":693},[679,14298,14299,14301,14304,14306],{"class":681,"line":3855},[679,14300,7862],{"class":931},[679,14302,14303],{"class":693},".b ",[679,14305,686],{"class":685},[679,14307,14272],{"class":693},[679,14309,14310],{"class":681,"line":3860},[679,14311,985],{"class":693},[679,14313,14314],{"class":681,"line":3870},[679,14315,889],{"emptyLinePlaceholder":797},[679,14317,14318,14320,14322,14325],{"class":681,"line":3877},[679,14319,6089],{"class":685},[679,14321,9289],{"class":693},[679,14323,14324],{"class":880},"getC",[679,14326,2667],{"class":693},[679,14328,14329,14331],{"class":681,"line":3890},[679,14330,9444],{"class":685},[679,14332,14333],{"class":693}," c;\n",[679,14335,14336],{"class":681,"line":3896},[679,14337,985],{"class":693},[679,14339,14340],{"class":681,"line":3902},[679,14341,889],{"emptyLinePlaceholder":797},[679,14343,14344,14346,14348,14351,14353,14356],{"class":681,"line":3908},[679,14345,6089],{"class":685},[679,14347,6095],{"class":685},[679,14349,14350],{"class":880}," setC",[679,14352,11400],{"class":693},[679,14354,14355],{"class":2099},"c",[679,14357,4390],{"class":693},[679,14359,14360,14362,14365,14367],{"class":681,"line":3920},[679,14361,7862],{"class":931},[679,14363,14364],{"class":693},".c ",[679,14366,686],{"class":685},[679,14368,14333],{"class":693},[679,14370,14371],{"class":681,"line":3925},[679,14372,985],{"class":693},[679,14374,14375],{"class":681,"line":3932},[679,14376,889],{"emptyLinePlaceholder":797},[679,14378,14379,14381],{"class":681,"line":3941},[679,14380,6872],{"class":693},[679,14382,10723],{"class":685},[679,14384,14385,14387,14389,14392],{"class":681,"line":3946},[679,14386,6089],{"class":685},[679,14388,9289],{"class":693},[679,14390,14391],{"class":880},"toString",[679,14393,2667],{"class":693},[679,14395,14396,14398,14401],{"class":681,"line":3951},[679,14397,9444],{"class":685},[679,14399,14400],{"class":689}," \"User{\"",[679,14402,14403],{"class":685}," +\n",[679,14405,14406,14409,14411,14414,14416,14419,14422,14424],{"class":681,"line":3956},[679,14407,14408],{"class":689}," \"first='\"",[679,14410,3059],{"class":685},[679,14412,14413],{"class":693}," first ",[679,14415,3065],{"class":685},[679,14417,14418],{"class":689}," '",[679,14420,14421],{"class":931},"\\'",[679,14423,3056],{"class":689},[679,14425,14403],{"class":685},[679,14427,14428,14431,14433,14436,14438,14440,14442,14444],{"class":681,"line":3961},[679,14429,14430],{"class":689}," \", last='\"",[679,14432,3059],{"class":685},[679,14434,14435],{"class":693}," last ",[679,14437,3065],{"class":685},[679,14439,14418],{"class":689},[679,14441,14421],{"class":931},[679,14443,3056],{"class":689},[679,14445,14403],{"class":685},[679,14447,14448,14451,14453,14456,14458,14460,14462,14464],{"class":681,"line":3966},[679,14449,14450],{"class":689}," \", email='\"",[679,14452,3059],{"class":685},[679,14454,14455],{"class":693}," email ",[679,14457,3065],{"class":685},[679,14459,14418],{"class":689},[679,14461,14421],{"class":931},[679,14463,3056],{"class":689},[679,14465,14403],{"class":685},[679,14467,14468,14471],{"class":681,"line":3971},[679,14469,14470],{"class":689}," '}'",[679,14472,1186],{"class":693},[679,14474,14475],{"class":681,"line":3976},[679,14476,985],{"class":693},[679,14478,14479],{"class":681,"line":3981},[679,14480,889],{"emptyLinePlaceholder":797},[679,14482,14483,14485],{"class":681,"line":3987},[679,14484,6872],{"class":693},[679,14486,10723],{"class":685},[679,14488,14489,14491,14494,14497,14500,14503],{"class":681,"line":3992},[679,14490,6089],{"class":685},[679,14492,14493],{"class":685}," boolean",[679,14495,14496],{"class":880}," equals",[679,14498,14499],{"class":693},"(Object ",[679,14501,14502],{"class":2099},"o",[679,14504,4390],{"class":693},[679,14506,14507,14509,14511,14513,14516,14519,14521,14524],{"class":681,"line":3997},[679,14508,1249],{"class":685},[679,14510,4193],{"class":693},[679,14512,4732],{"class":931},[679,14514,14515],{"class":685}," ==",[679,14517,14518],{"class":693}," o) ",[679,14520,1307],{"class":685},[679,14522,14523],{"class":931}," true",[679,14525,1186],{"class":693},[679,14527,14528,14530,14533,14535,14537,14540,14543,14545,14547,14550,14552,14555,14557,14560],{"class":681,"line":4002},[679,14529,1249],{"class":685},[679,14531,14532],{"class":693}," (o ",[679,14534,2304],{"class":685},[679,14536,2307],{"class":931},[679,14538,14539],{"class":685}," ||",[679,14541,14542],{"class":880}," getClass",[679,14544,6700],{"class":693},[679,14546,1587],{"class":685},[679,14548,14549],{"class":693}," o.",[679,14551,10652],{"class":880},[679,14553,14554],{"class":693},"()) ",[679,14556,1307],{"class":685},[679,14558,14559],{"class":931}," false",[679,14561,1186],{"class":693},[679,14563,14564],{"class":681,"line":4007},[679,14565,889],{"emptyLinePlaceholder":797},[679,14567,14568,14570,14572],{"class":681,"line":4012},[679,14569,9308],{"class":693},[679,14571,686],{"class":685},[679,14573,14574],{"class":693}," (User) o;\n",[679,14576,14577],{"class":681,"line":4017},[679,14578,889],{"emptyLinePlaceholder":797},[679,14580,14581,14583,14585,14587,14590,14593,14596,14598,14600],{"class":681,"line":4022},[679,14582,1249],{"class":685},[679,14584,4193],{"class":693},[679,14586,1223],{"class":685},[679,14588,14589],{"class":693},"first.",[679,14591,14592],{"class":880},"equals",[679,14594,14595],{"class":693},"(user.first)) ",[679,14597,1307],{"class":685},[679,14599,14559],{"class":931},[679,14601,1186],{"class":693},[679,14603,14604,14606,14609,14611,14613,14616,14619,14622,14624,14627,14629,14632,14634,14636,14638,14640,14642],{"class":681,"line":4028},[679,14605,1249],{"class":685},[679,14607,14608],{"class":693}," (middle ",[679,14610,1587],{"class":685},[679,14612,2307],{"class":931},[679,14614,14615],{"class":685}," ?",[679,14617,14618],{"class":685}," !",[679,14620,14621],{"class":693},"middle.",[679,14623,14592],{"class":880},[679,14625,14626],{"class":693},"(user.middle) ",[679,14628,2391],{"class":685},[679,14630,14631],{"class":693}," user.middle ",[679,14633,1587],{"class":685},[679,14635,2307],{"class":931},[679,14637,2378],{"class":693},[679,14639,1307],{"class":685},[679,14641,14559],{"class":931},[679,14643,1186],{"class":693},[679,14645,14646,14648,14650,14652,14655,14657,14660,14662,14664],{"class":681,"line":4033},[679,14647,1249],{"class":685},[679,14649,4193],{"class":693},[679,14651,1223],{"class":685},[679,14653,14654],{"class":693},"last.",[679,14656,14592],{"class":880},[679,14658,14659],{"class":693},"(user.last)) ",[679,14661,1307],{"class":685},[679,14663,14559],{"class":931},[679,14665,1186],{"class":693},[679,14667,14668,14670,14672,14674,14677,14679,14682,14684,14686],{"class":681,"line":4038},[679,14669,1249],{"class":685},[679,14671,4193],{"class":693},[679,14673,1223],{"class":685},[679,14675,14676],{"class":693},"email.",[679,14678,14592],{"class":880},[679,14680,14681],{"class":693},"(user.email)) ",[679,14683,1307],{"class":685},[679,14685,14559],{"class":931},[679,14687,1186],{"class":693},[679,14689,14690,14692,14694,14696,14699,14701,14704,14706,14708],{"class":681,"line":4043},[679,14691,1249],{"class":685},[679,14693,4193],{"class":693},[679,14695,1223],{"class":685},[679,14697,14698],{"class":693},"dob.",[679,14700,14592],{"class":880},[679,14702,14703],{"class":693},"(user.dob)) ",[679,14705,1307],{"class":685},[679,14707,14559],{"class":931},[679,14709,1186],{"class":693},[679,14711,14712,14714,14717,14719,14721,14723,14725,14728,14730,14733,14735,14738,14740,14742,14744,14746,14748],{"class":681,"line":4048},[679,14713,1249],{"class":685},[679,14715,14716],{"class":693}," (foo ",[679,14718,1587],{"class":685},[679,14720,2307],{"class":931},[679,14722,14615],{"class":685},[679,14724,14618],{"class":685},[679,14726,14727],{"class":693},"foo.",[679,14729,14592],{"class":880},[679,14731,14732],{"class":693},"(user.foo) ",[679,14734,2391],{"class":685},[679,14736,14737],{"class":693}," user.foo ",[679,14739,1587],{"class":685},[679,14741,2307],{"class":931},[679,14743,2378],{"class":693},[679,14745,1307],{"class":685},[679,14747,14559],{"class":931},[679,14749,1186],{"class":693},[679,14751,14752,14754,14757,14759,14761,14763,14765,14768,14770,14773,14775,14778,14780,14782,14784,14786,14788],{"class":681,"line":4053},[679,14753,1249],{"class":685},[679,14755,14756],{"class":693}," (bar ",[679,14758,1587],{"class":685},[679,14760,2307],{"class":931},[679,14762,14615],{"class":685},[679,14764,14618],{"class":685},[679,14766,14767],{"class":693},"bar.",[679,14769,14592],{"class":880},[679,14771,14772],{"class":693},"(user.bar) ",[679,14774,2391],{"class":685},[679,14776,14777],{"class":693}," user.bar ",[679,14779,1587],{"class":685},[679,14781,2307],{"class":931},[679,14783,2378],{"class":693},[679,14785,1307],{"class":685},[679,14787,14559],{"class":931},[679,14789,1186],{"class":693},[679,14791,14792,14794,14797,14799,14801,14803,14805,14808,14810,14813,14815,14818,14820,14822,14824,14826,14828],{"class":681,"line":4058},[679,14793,1249],{"class":685},[679,14795,14796],{"class":693}," (a ",[679,14798,1587],{"class":685},[679,14800,2307],{"class":931},[679,14802,14615],{"class":685},[679,14804,14618],{"class":685},[679,14806,14807],{"class":693},"a.",[679,14809,14592],{"class":880},[679,14811,14812],{"class":693},"(user.a) ",[679,14814,2391],{"class":685},[679,14816,14817],{"class":693}," user.a ",[679,14819,1587],{"class":685},[679,14821,2307],{"class":931},[679,14823,2378],{"class":693},[679,14825,1307],{"class":685},[679,14827,14559],{"class":931},[679,14829,1186],{"class":693},[679,14831,14832,14834,14837,14839,14841,14843,14845,14848,14850,14853,14855,14858,14860,14862,14864,14866,14868],{"class":681,"line":4063},[679,14833,1249],{"class":685},[679,14835,14836],{"class":693}," (b ",[679,14838,1587],{"class":685},[679,14840,2307],{"class":931},[679,14842,14615],{"class":685},[679,14844,14618],{"class":685},[679,14846,14847],{"class":693},"b.",[679,14849,14592],{"class":880},[679,14851,14852],{"class":693},"(user.b) ",[679,14854,2391],{"class":685},[679,14856,14857],{"class":693}," user.b ",[679,14859,1587],{"class":685},[679,14861,2307],{"class":931},[679,14863,2378],{"class":693},[679,14865,1307],{"class":685},[679,14867,14559],{"class":931},[679,14869,1186],{"class":693},[679,14871,14872,14874,14877,14879,14881,14883,14886,14888,14891,14893,14896,14898,14900],{"class":681,"line":4068},[679,14873,9444],{"class":685},[679,14875,14876],{"class":693}," c ",[679,14878,1587],{"class":685},[679,14880,2307],{"class":931},[679,14882,14615],{"class":685},[679,14884,14885],{"class":693}," c.",[679,14887,14592],{"class":880},[679,14889,14890],{"class":693},"(user.c) ",[679,14892,2391],{"class":685},[679,14894,14895],{"class":693}," user.c ",[679,14897,2304],{"class":685},[679,14899,2307],{"class":931},[679,14901,1186],{"class":693},[679,14903,14904],{"class":681,"line":4073},[679,14905,985],{"class":693},[679,14907,14909],{"class":681,"line":14908},123,[679,14910,889],{"emptyLinePlaceholder":797},[679,14912,14914,14916],{"class":681,"line":14913},124,[679,14915,6872],{"class":693},[679,14917,10723],{"class":685},[679,14919,14921,14923,14926,14929],{"class":681,"line":14920},125,[679,14922,6089],{"class":685},[679,14924,14925],{"class":685}," int",[679,14927,14928],{"class":880}," hashCode",[679,14930,2667],{"class":693},[679,14932,14934,14937,14940,14942,14945,14948],{"class":681,"line":14933},126,[679,14935,14936],{"class":685}," int",[679,14938,14939],{"class":693}," result ",[679,14941,686],{"class":685},[679,14943,14944],{"class":693}," first.",[679,14946,14947],{"class":880},"hashCode",[679,14949,9317],{"class":693},[679,14951,14953,14956,14958,14961,14964,14966,14968,14970,14972,14974,14976,14979,14981,14983,14985,14988],{"class":681,"line":14952},127,[679,14954,14955],{"class":693}," result ",[679,14957,686],{"class":685},[679,14959,14960],{"class":931}," 31",[679,14962,14963],{"class":685}," *",[679,14965,14939],{"class":693},[679,14967,3065],{"class":685},[679,14969,14608],{"class":693},[679,14971,1587],{"class":685},[679,14973,2307],{"class":931},[679,14975,14615],{"class":685},[679,14977,14978],{"class":693}," middle.",[679,14980,14947],{"class":880},[679,14982,6700],{"class":693},[679,14984,2391],{"class":685},[679,14986,14987],{"class":931}," 0",[679,14989,1208],{"class":693},[679,14991,14993,14995,14997,14999,15001,15003,15005,15008,15010],{"class":681,"line":14992},128,[679,14994,14955],{"class":693},[679,14996,686],{"class":685},[679,14998,14960],{"class":931},[679,15000,14963],{"class":685},[679,15002,14939],{"class":693},[679,15004,3065],{"class":685},[679,15006,15007],{"class":693}," last.",[679,15009,14947],{"class":880},[679,15011,9317],{"class":693},[679,15013,15015,15017,15019,15021,15023,15025,15027,15030,15032],{"class":681,"line":15014},129,[679,15016,14955],{"class":693},[679,15018,686],{"class":685},[679,15020,14960],{"class":931},[679,15022,14963],{"class":685},[679,15024,14939],{"class":693},[679,15026,3065],{"class":685},[679,15028,15029],{"class":693}," email.",[679,15031,14947],{"class":880},[679,15033,9317],{"class":693},[679,15035,15037,15039,15041,15043,15045,15047,15049,15052,15054],{"class":681,"line":15036},130,[679,15038,14955],{"class":693},[679,15040,686],{"class":685},[679,15042,14960],{"class":931},[679,15044,14963],{"class":685},[679,15046,14939],{"class":693},[679,15048,3065],{"class":685},[679,15050,15051],{"class":693}," dob.",[679,15053,14947],{"class":880},[679,15055,9317],{"class":693},[679,15057,15059,15061,15063,15065,15067,15069,15071,15073,15075,15077,15079,15082,15084,15086,15088,15090],{"class":681,"line":15058},131,[679,15060,14955],{"class":693},[679,15062,686],{"class":685},[679,15064,14960],{"class":931},[679,15066,14963],{"class":685},[679,15068,14939],{"class":693},[679,15070,3065],{"class":685},[679,15072,14716],{"class":693},[679,15074,1587],{"class":685},[679,15076,2307],{"class":931},[679,15078,14615],{"class":685},[679,15080,15081],{"class":693}," foo.",[679,15083,14947],{"class":880},[679,15085,6700],{"class":693},[679,15087,2391],{"class":685},[679,15089,14987],{"class":931},[679,15091,1208],{"class":693},[679,15093,15095,15097,15099,15101,15103,15105,15107,15109,15111,15113,15115,15118,15120,15122,15124,15126],{"class":681,"line":15094},132,[679,15096,14955],{"class":693},[679,15098,686],{"class":685},[679,15100,14960],{"class":931},[679,15102,14963],{"class":685},[679,15104,14939],{"class":693},[679,15106,3065],{"class":685},[679,15108,14756],{"class":693},[679,15110,1587],{"class":685},[679,15112,2307],{"class":931},[679,15114,14615],{"class":685},[679,15116,15117],{"class":693}," bar.",[679,15119,14947],{"class":880},[679,15121,6700],{"class":693},[679,15123,2391],{"class":685},[679,15125,14987],{"class":931},[679,15127,1208],{"class":693},[679,15129,15131,15133,15135,15137,15139,15141,15143,15145,15147,15149,15151,15154,15156,15158,15160,15162],{"class":681,"line":15130},133,[679,15132,14955],{"class":693},[679,15134,686],{"class":685},[679,15136,14960],{"class":931},[679,15138,14963],{"class":685},[679,15140,14939],{"class":693},[679,15142,3065],{"class":685},[679,15144,14796],{"class":693},[679,15146,1587],{"class":685},[679,15148,2307],{"class":931},[679,15150,14615],{"class":685},[679,15152,15153],{"class":693}," a.",[679,15155,14947],{"class":880},[679,15157,6700],{"class":693},[679,15159,2391],{"class":685},[679,15161,14987],{"class":931},[679,15163,1208],{"class":693},[679,15165,15167,15169,15171,15173,15175,15177,15179,15181,15183,15185,15187,15190,15192,15194,15196,15198],{"class":681,"line":15166},134,[679,15168,14955],{"class":693},[679,15170,686],{"class":685},[679,15172,14960],{"class":931},[679,15174,14963],{"class":685},[679,15176,14939],{"class":693},[679,15178,3065],{"class":685},[679,15180,14836],{"class":693},[679,15182,1587],{"class":685},[679,15184,2307],{"class":931},[679,15186,14615],{"class":685},[679,15188,15189],{"class":693}," b.",[679,15191,14947],{"class":880},[679,15193,6700],{"class":693},[679,15195,2391],{"class":685},[679,15197,14987],{"class":931},[679,15199,1208],{"class":693},[679,15201,15203,15205,15207,15209,15211,15213,15215,15218,15220,15222,15224,15226,15228,15230,15232,15234],{"class":681,"line":15202},135,[679,15204,14955],{"class":693},[679,15206,686],{"class":685},[679,15208,14960],{"class":931},[679,15210,14963],{"class":685},[679,15212,14939],{"class":693},[679,15214,3065],{"class":685},[679,15216,15217],{"class":693}," (c ",[679,15219,1587],{"class":685},[679,15221,2307],{"class":931},[679,15223,14615],{"class":685},[679,15225,14885],{"class":693},[679,15227,14947],{"class":880},[679,15229,6700],{"class":693},[679,15231,2391],{"class":685},[679,15233,14987],{"class":931},[679,15235,1208],{"class":693},[679,15237,15239,15241],{"class":681,"line":15238},136,[679,15240,9444],{"class":685},[679,15242,15243],{"class":693}," result;\n",[679,15245,15247],{"class":681,"line":15246},137,[679,15248,985],{"class":693},[679,15250,15252],{"class":681,"line":15251},138,[679,15253,996],{"class":693},[5909,15255,15257],{"id":15256},"after-project-lombok","After Project Lombok",[651,15259,15260,15261,15266],{},"Now that we have Lombok in our application we can reduce this noise. Just add that @Data annotation to your class. This will create a getter and setter for each property defined in your class. It will also create a to string, equals and hash code method. You can get pretty granular control with some of the annotations so again I would ",[812,15262,15265],{"href":15263,"rel":15264},"http://jnb.ociweb.com/jnb/jnbJan2010.html",[816],"refer you to the documentation",". ",[669,15268,15270],{"className":4107,"code":15269,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport lombok.Data;\nimport java.util.Date;\n\n@Data\npublic class User {\n\n private String first;\n private String middle;\n private String last;\n private String email;\n private Date dob;\n private String foo;\n private String bar;\n private String a,b,c;\n\n}\n",[676,15271,15272,15278,15282,15289,15295,15299,15306,15316,15320,15326,15332,15338,15344,15350,15356,15362,15368,15372],{"__ignoreMap":674},[679,15273,15274,15276],{"class":681,"line":682},[679,15275,2543],{"class":685},[679,15277,6039],{"class":693},[679,15279,15280],{"class":681,"line":790},[679,15281,889],{"emptyLinePlaceholder":797},[679,15283,15284,15286],{"class":681,"line":892},[679,15285,1999],{"class":685},[679,15287,15288],{"class":693}," lombok.Data;\n",[679,15290,15291,15293],{"class":681,"line":901},[679,15292,1999],{"class":685},[679,15294,13580],{"class":693},[679,15296,15297],{"class":681,"line":909},[679,15298,889],{"emptyLinePlaceholder":797},[679,15300,15301,15303],{"class":681,"line":918},[679,15302,4116],{"class":693},[679,15304,15305],{"class":685},"Data\n",[679,15307,15308,15310,15312,15314],{"class":681,"line":935},[679,15309,6073],{"class":685},[679,15311,4512],{"class":685},[679,15313,881],{"class":880},[679,15315,884],{"class":693},[679,15317,15318],{"class":681,"line":944},[679,15319,889],{"emptyLinePlaceholder":797},[679,15321,15322,15324],{"class":681,"line":959},[679,15323,9232],{"class":685},[679,15325,13605],{"class":693},[679,15327,15328,15330],{"class":681,"line":964},[679,15329,9232],{"class":685},[679,15331,13612],{"class":693},[679,15333,15334,15336],{"class":681,"line":977},[679,15335,9232],{"class":685},[679,15337,13619],{"class":693},[679,15339,15340,15342],{"class":681,"line":982},[679,15341,9232],{"class":685},[679,15343,13626],{"class":693},[679,15345,15346,15348],{"class":681,"line":988},[679,15347,9232],{"class":685},[679,15349,13633],{"class":693},[679,15351,15352,15354],{"class":681,"line":993},[679,15353,9232],{"class":685},[679,15355,13640],{"class":693},[679,15357,15358,15360],{"class":681,"line":2129},[679,15359,9232],{"class":685},[679,15361,13647],{"class":693},[679,15363,15364,15366],{"class":681,"line":2140},[679,15365,9232],{"class":685},[679,15367,13654],{"class":693},[679,15369,15370],{"class":681,"line":2145},[679,15371,889],{"emptyLinePlaceholder":797},[679,15373,15374],{"class":681,"line":2154},[679,15375,996],{"class":693},[651,15377,15378],{},"The great thing about Lombok is that it integrates well with the IDE. I am using IntelliJ but this will work in Eclipse as well. If you look over at your project structure you can see that all of these methods now exist without us having to write them. We also don't have to guess as to whether it worked or not, we can clearly see that it did. Not to mention, look how clean our class looks! ",[651,15380,15381],{},[660,15382],{"alt":15383,"src":15384},"Lombok Demo","/images/blog/2017/03/31/lombok_demo_4-293x300.png",[4542,15386,9042],{"id":9041},[651,15388,15389],{},"There is absolutely no reason you shouldn't be using Project Lombok today. If it's not a brand new project you can simply drop in the dependency and go. I hope you found this tutorial useful and I want to leave you with a question to hopefully kick start a discussion. ",[651,15391,13453,15392,15395],{},[2939,15393,15394],{},"Question",": What are some of your favorite tools & libraries to help in the creation of better, cleaner code? _",[786,15397,13459],{},{"title":674,"searchDepth":790,"depth":790,"links":15399},[15400,15401,15405],{"id":13506,"depth":790,"text":13507},{"id":13531,"depth":790,"text":13532,"children":15402},[15403,15404],{"id":13535,"depth":892,"text":13536},{"id":15256,"depth":892,"text":15257},{"id":9041,"depth":790,"text":9042},{"slug":15407,"published":797,"date":15408,"tags":15409,"cover":15410},"using-project-lombok-spring-boot","2017-03-31T08:00:38-04:00",[4109,7055],"./2350-760x507.jpg",{"title":222,"description":222},"blog/2017/03/31/using-project-lombok-spring-boot","7IpD-qDJGl7QqHOUIDV_PwNDA5HyZDCG4v3mciiTiNY",{"id":15415,"title":219,"body":15416,"description":219,"extension":793,"meta":15428,"navigation":797,"path":220,"seo":15434,"stem":15435,"__hash__":15436},"content/blog/2017/04/03/2000-subscribers-youtube.md",{"type":648,"value":15417,"toc":15426},[15418],[651,15419,15420,15421,15425],{},"I was so excited to find out that I crossed 2,000 YouTube Subscribers last week! ",[812,15422,15423],{"href":15423,"rel":15424},"https://www.youtube.com/watch?v=40kw1-fSAbo",[816]," Just like my blog I am going to make a few changes on the YouTube channel. I am going to focus the content on mostly programming related topics. I also am going to work on a consistent schedule. With a consistent schedule between my blog and YouTube channel, I think the I can do some big things in 2017 and beyond. Thank you to everyone for the support over of the years. I very much appreciate it.",{"title":674,"searchDepth":790,"depth":790,"links":15427},[],{"slug":15429,"published":797,"date":15430,"tags":15431,"cover":15433},"2000-subscribers-youtube","2017-04-03T08:30:34-04:00",[15432],"YouTube","./200-subscribers.jpg",{"title":219,"description":219},"blog/2017/04/03/2000-subscribers-youtube","EDcyRypXSOwXN-ah4DnFlDK8qYIWnXZx2jk0FcxmRqI",{"id":15438,"title":216,"body":15439,"description":216,"extension":793,"meta":16184,"navigation":797,"path":217,"seo":16189,"stem":16190,"__hash__":16191},"content/blog/2017/04/05/java-equals.md",{"type":648,"value":15440,"toc":16177},[15441,15444,15446,15449,15713,15716,15722,15725,15779,15782,15786,15789,15902,15904,15907,16153,16156,16158,16167,16174],[651,15442,15443],{},"This is going to be a fun and personal post for me. The problem that we are going to look at in this article is something we are taught as young software developers. I saw this question on a facebook group that I am a part of and I made the same mistake at work debugging a critical ticket. If you want to find out what trips up newbies and veterans alike, continue reading this article. ",[4542,15445,11142],{"id":11141},[651,15447,15448],{},"The problem we are going to look at today has to do with comparing values in Java. This is language agnostic issue though as every language has its own way to deal with object values and references. We are going to start by looking at a question I saw in the facebook group I am in. Given the title and subject of this article, you might be able to easily find the problem. What is the output of this code and why is that output returned to us? ",[669,15450,15452],{"className":4107,"code":15451,"language":4109,"meta":674,"style":674},"import java.util.Scanner;\n\npublic class Main {\n\n public static void main(String\\[\\] args) {\n\n Scanner input = new Scanner(System.in);\n\n String answer;\n\n System.out.println(\"Canada is a country located in which continent?\");\n System.out.println(\"A - South America\");\n System.out.println(\"B - Asia\");\n System.out.println(\"C - North America\");\n System.out.println(\"D - Oceania\");\n System.out.println(\"F - Africa\");\n System.out.println(\"Please enter the letter of the correct answer.\");\n answer = input.nextLine();\n\n if (answer == \"C\") {\n System.out.print(\"You are correct\");\n } else {\n System.out.print(\"You are incorrect\");\n }\n\n }\n\n}\n",[676,15453,15454,15461,15465,15476,15480,15500,15504,15519,15523,15528,15532,15545,15558,15571,15584,15597,15610,15623,15638,15642,15656,15671,15680,15693,15697,15701,15705,15709],{"__ignoreMap":674},[679,15455,15456,15458],{"class":681,"line":682},[679,15457,1999],{"class":685},[679,15459,15460],{"class":693}," java.util.Scanner;\n",[679,15462,15463],{"class":681,"line":790},[679,15464,889],{"emptyLinePlaceholder":797},[679,15466,15467,15469,15471,15474],{"class":681,"line":892},[679,15468,6073],{"class":685},[679,15470,4512],{"class":685},[679,15472,15473],{"class":880}," Main",[679,15475,884],{"class":693},[679,15477,15478],{"class":681,"line":901},[679,15479,889],{"emptyLinePlaceholder":797},[679,15481,15482,15484,15486,15488,15490,15492,15494,15496,15498],{"class":681,"line":909},[679,15483,6089],{"class":685},[679,15485,6092],{"class":685},[679,15487,6095],{"class":685},[679,15489,6098],{"class":880},[679,15491,745],{"class":693},[679,15493,4758],{"class":2099},[679,15495,6105],{"class":693},[679,15497,6108],{"class":2099},[679,15499,4390],{"class":693},[679,15501,15502],{"class":681,"line":918},[679,15503,889],{"emptyLinePlaceholder":797},[679,15505,15506,15509,15511,15513,15516],{"class":681,"line":935},[679,15507,15508],{"class":693}," Scanner input ",[679,15510,686],{"class":685},[679,15512,2054],{"class":685},[679,15514,15515],{"class":880}," Scanner",[679,15517,15518],{"class":693},"(System.in);\n",[679,15520,15521],{"class":681,"line":944},[679,15522,889],{"emptyLinePlaceholder":797},[679,15524,15525],{"class":681,"line":959},[679,15526,15527],{"class":693}," String answer;\n",[679,15529,15530],{"class":681,"line":964},[679,15531,889],{"emptyLinePlaceholder":797},[679,15533,15534,15536,15538,15540,15543],{"class":681,"line":977},[679,15535,9592],{"class":693},[679,15537,1729],{"class":880},[679,15539,745],{"class":693},[679,15541,15542],{"class":689},"\"Canada is a country located in which continent?\"",[679,15544,1208],{"class":693},[679,15546,15547,15549,15551,15553,15556],{"class":681,"line":982},[679,15548,9592],{"class":693},[679,15550,1729],{"class":880},[679,15552,745],{"class":693},[679,15554,15555],{"class":689},"\"A - South America\"",[679,15557,1208],{"class":693},[679,15559,15560,15562,15564,15566,15569],{"class":681,"line":988},[679,15561,9592],{"class":693},[679,15563,1729],{"class":880},[679,15565,745],{"class":693},[679,15567,15568],{"class":689},"\"B - Asia\"",[679,15570,1208],{"class":693},[679,15572,15573,15575,15577,15579,15582],{"class":681,"line":993},[679,15574,9592],{"class":693},[679,15576,1729],{"class":880},[679,15578,745],{"class":693},[679,15580,15581],{"class":689},"\"C - North America\"",[679,15583,1208],{"class":693},[679,15585,15586,15588,15590,15592,15595],{"class":681,"line":2129},[679,15587,9592],{"class":693},[679,15589,1729],{"class":880},[679,15591,745],{"class":693},[679,15593,15594],{"class":689},"\"D - Oceania\"",[679,15596,1208],{"class":693},[679,15598,15599,15601,15603,15605,15608],{"class":681,"line":2140},[679,15600,9592],{"class":693},[679,15602,1729],{"class":880},[679,15604,745],{"class":693},[679,15606,15607],{"class":689},"\"F - Africa\"",[679,15609,1208],{"class":693},[679,15611,15612,15614,15616,15618,15621],{"class":681,"line":2145},[679,15613,9592],{"class":693},[679,15615,1729],{"class":880},[679,15617,745],{"class":693},[679,15619,15620],{"class":689},"\"Please enter the letter of the correct answer.\"",[679,15622,1208],{"class":693},[679,15624,15625,15628,15630,15633,15636],{"class":681,"line":2154},[679,15626,15627],{"class":693}," answer ",[679,15629,686],{"class":685},[679,15631,15632],{"class":693}," input.",[679,15634,15635],{"class":880},"nextLine",[679,15637,9317],{"class":693},[679,15639,15640],{"class":681,"line":2159},[679,15641,889],{"emptyLinePlaceholder":797},[679,15643,15644,15646,15649,15651,15654],{"class":681,"line":2164},[679,15645,1249],{"class":685},[679,15647,15648],{"class":693}," (answer ",[679,15650,2304],{"class":685},[679,15652,15653],{"class":689}," \"C\"",[679,15655,4390],{"class":693},[679,15657,15658,15661,15664,15666,15669],{"class":681,"line":3134},[679,15659,15660],{"class":693}," System.out.",[679,15662,15663],{"class":880},"print",[679,15665,745],{"class":693},[679,15667,15668],{"class":689},"\"You are correct\"",[679,15670,1208],{"class":693},[679,15672,15673,15676,15678],{"class":681,"line":3139},[679,15674,15675],{"class":693}," } ",[679,15677,2256],{"class":685},[679,15679,884],{"class":693},[679,15681,15682,15684,15686,15688,15691],{"class":681,"line":3144},[679,15683,15660],{"class":693},[679,15685,15663],{"class":880},[679,15687,745],{"class":693},[679,15689,15690],{"class":689},"\"You are incorrect\"",[679,15692,1208],{"class":693},[679,15694,15695],{"class":681,"line":3149},[679,15696,1290],{"class":693},[679,15698,15699],{"class":681,"line":3169},[679,15700,889],{"emptyLinePlaceholder":797},[679,15702,15703],{"class":681,"line":3185},[679,15704,985],{"class":693},[679,15706,15707],{"class":681,"line":3194},[679,15708,889],{"emptyLinePlaceholder":797},[679,15710,15711],{"class":681,"line":3199},[679,15712,996],{"class":693},[651,15714,15715],{},"If you run this code and enter \"C\" as your answer, the program will print out \"You are incorrect\". ",[651,15717,15718],{},[660,15719],{"alt":15720,"src":15721},"Java Equals Example","./java_equals-1.png",[651,15723,15724],{},"So what happened here? Why didn't the program print out \"You are correct\"? The problem with this program is with the following code. ",[669,15726,15728],{"className":4107,"code":15727,"language":4109,"meta":674,"style":674},"if (answer == \"C\") {\n System.out.print(\"You are correct\");\n} else {\n System.out.print(\"You are incorrect\");\n}\n",[676,15729,15730,15742,15755,15763,15775],{"__ignoreMap":674},[679,15731,15732,15734,15736,15738,15740],{"class":681,"line":682},[679,15733,1217],{"class":685},[679,15735,15648],{"class":693},[679,15737,2304],{"class":685},[679,15739,15653],{"class":689},[679,15741,4390],{"class":693},[679,15743,15744,15747,15749,15751,15753],{"class":681,"line":790},[679,15745,15746],{"class":693}," System.out.",[679,15748,15663],{"class":880},[679,15750,745],{"class":693},[679,15752,15668],{"class":689},[679,15754,1208],{"class":693},[679,15756,15757,15759,15761],{"class":681,"line":892},[679,15758,2253],{"class":693},[679,15760,2256],{"class":685},[679,15762,884],{"class":693},[679,15764,15765,15767,15769,15771,15773],{"class":681,"line":901},[679,15766,15746],{"class":693},[679,15768,15663],{"class":880},[679,15770,745],{"class":693},[679,15772,15690],{"class":689},[679,15774,1208],{"class":693},[679,15776,15777],{"class":681,"line":909},[679,15778,996],{"class":693},[651,15780,15781],{},"The variable answer is a String and we are using == to compare the value to \"C\". We know that if we compare the value \"C\" to \"C\" that they should be equal. In fact, they are equal but the problem lies with the operator. ",[5909,15783,15785],{"id":15784},"the-operator","The \"==\" Operator",[651,15787,15788],{},"In Java, we use the \"==\" operator to compare 2 objects to see if the refer to the same place in memory. When we create the variable answer and assign it a value that is a reference to that object. When we compare that object in memory to the value \"C\" those are not the same so \"==\" will return false. The following is an example of where 2 variables reference the same object. ",[669,15790,15792],{"className":4107,"code":15791,"language":4109,"meta":674,"style":674},"public class Main {\n\n public static void main(String\\[\\] args) {\n\n String name = \"Dan\";\n String myName = name;\n\n if( name == myName ){\n System.out.println(\"name is equal to myName\");\n }\n\n }\n\n}\n",[676,15793,15794,15804,15808,15828,15832,15844,15854,15858,15869,15882,15886,15890,15894,15898],{"__ignoreMap":674},[679,15795,15796,15798,15800,15802],{"class":681,"line":682},[679,15797,6073],{"class":685},[679,15799,4512],{"class":685},[679,15801,15473],{"class":880},[679,15803,884],{"class":693},[679,15805,15806],{"class":681,"line":790},[679,15807,889],{"emptyLinePlaceholder":797},[679,15809,15810,15812,15814,15816,15818,15820,15822,15824,15826],{"class":681,"line":892},[679,15811,6089],{"class":685},[679,15813,6092],{"class":685},[679,15815,6095],{"class":685},[679,15817,6098],{"class":880},[679,15819,745],{"class":693},[679,15821,4758],{"class":2099},[679,15823,6105],{"class":693},[679,15825,6108],{"class":2099},[679,15827,4390],{"class":693},[679,15829,15830],{"class":681,"line":901},[679,15831,889],{"emptyLinePlaceholder":797},[679,15833,15834,15837,15839,15842],{"class":681,"line":909},[679,15835,15836],{"class":693}," String name ",[679,15838,686],{"class":685},[679,15840,15841],{"class":689}," \"Dan\"",[679,15843,1186],{"class":693},[679,15845,15846,15849,15851],{"class":681,"line":918},[679,15847,15848],{"class":693}," String myName ",[679,15850,686],{"class":685},[679,15852,15853],{"class":693}," name;\n",[679,15855,15856],{"class":681,"line":935},[679,15857,889],{"emptyLinePlaceholder":797},[679,15859,15860,15862,15864,15866],{"class":681,"line":944},[679,15861,1249],{"class":685},[679,15863,1469],{"class":693},[679,15865,2304],{"class":685},[679,15867,15868],{"class":693}," myName ){\n",[679,15870,15871,15873,15875,15877,15880],{"class":681,"line":959},[679,15872,15660],{"class":693},[679,15874,1729],{"class":880},[679,15876,745],{"class":693},[679,15878,15879],{"class":689},"\"name is equal to myName\"",[679,15881,1208],{"class":693},[679,15883,15884],{"class":681,"line":964},[679,15885,1290],{"class":693},[679,15887,15888],{"class":681,"line":977},[679,15889,889],{"emptyLinePlaceholder":797},[679,15891,15892],{"class":681,"line":982},[679,15893,985],{"class":693},[679,15895,15896],{"class":681,"line":988},[679,15897,889],{"emptyLinePlaceholder":797},[679,15899,15900],{"class":681,"line":993},[679,15901,996],{"class":693},[4542,15903,11155],{"id":11154},[651,15905,15906],{},"If that is the case how can we fix our program? The String class actually contains a method called .equals. With that knowledge, we can fix our application by replacing that \"==\" with .equals. ",[669,15908,15910],{"className":4107,"code":15909,"language":4109,"meta":674,"style":674},"import java.util.Scanner;\n\npublic class Main {\n\n public static void main(String\\[\\] args) {\n\n Scanner input = new Scanner(System.in);\n\n String answer;\n\n System.out.println(\"Canada is a country located in which continent?\");\n System.out.println(\"A - South America\");\n System.out.println(\"B - Asia\");\n System.out.println(\"C - North America\");\n System.out.println(\"D - Oceania\");\n System.out.println(\"F - Africa\");\n System.out.println(\"Please enter the letter of the correct answer.\");\n answer = input.nextLine();\n\n if( answer.equals(\"C\") ) {\n System.out.print(\"You are correct\");\n } else {\n System.out.print(\"You are incorrect\");\n }\n\n }\n\n}\n",[676,15911,15912,15918,15922,15932,15936,15956,15960,15972,15976,15980,15984,15996,16008,16020,16032,16044,16056,16068,16080,16084,16101,16113,16121,16133,16137,16141,16145,16149],{"__ignoreMap":674},[679,15913,15914,15916],{"class":681,"line":682},[679,15915,1999],{"class":685},[679,15917,15460],{"class":693},[679,15919,15920],{"class":681,"line":790},[679,15921,889],{"emptyLinePlaceholder":797},[679,15923,15924,15926,15928,15930],{"class":681,"line":892},[679,15925,6073],{"class":685},[679,15927,4512],{"class":685},[679,15929,15473],{"class":880},[679,15931,884],{"class":693},[679,15933,15934],{"class":681,"line":901},[679,15935,889],{"emptyLinePlaceholder":797},[679,15937,15938,15940,15942,15944,15946,15948,15950,15952,15954],{"class":681,"line":909},[679,15939,6089],{"class":685},[679,15941,6092],{"class":685},[679,15943,6095],{"class":685},[679,15945,6098],{"class":880},[679,15947,745],{"class":693},[679,15949,4758],{"class":2099},[679,15951,6105],{"class":693},[679,15953,6108],{"class":2099},[679,15955,4390],{"class":693},[679,15957,15958],{"class":681,"line":918},[679,15959,889],{"emptyLinePlaceholder":797},[679,15961,15962,15964,15966,15968,15970],{"class":681,"line":935},[679,15963,15508],{"class":693},[679,15965,686],{"class":685},[679,15967,2054],{"class":685},[679,15969,15515],{"class":880},[679,15971,15518],{"class":693},[679,15973,15974],{"class":681,"line":944},[679,15975,889],{"emptyLinePlaceholder":797},[679,15977,15978],{"class":681,"line":959},[679,15979,15527],{"class":693},[679,15981,15982],{"class":681,"line":964},[679,15983,889],{"emptyLinePlaceholder":797},[679,15985,15986,15988,15990,15992,15994],{"class":681,"line":977},[679,15987,9592],{"class":693},[679,15989,1729],{"class":880},[679,15991,745],{"class":693},[679,15993,15542],{"class":689},[679,15995,1208],{"class":693},[679,15997,15998,16000,16002,16004,16006],{"class":681,"line":982},[679,15999,9592],{"class":693},[679,16001,1729],{"class":880},[679,16003,745],{"class":693},[679,16005,15555],{"class":689},[679,16007,1208],{"class":693},[679,16009,16010,16012,16014,16016,16018],{"class":681,"line":988},[679,16011,9592],{"class":693},[679,16013,1729],{"class":880},[679,16015,745],{"class":693},[679,16017,15568],{"class":689},[679,16019,1208],{"class":693},[679,16021,16022,16024,16026,16028,16030],{"class":681,"line":993},[679,16023,9592],{"class":693},[679,16025,1729],{"class":880},[679,16027,745],{"class":693},[679,16029,15581],{"class":689},[679,16031,1208],{"class":693},[679,16033,16034,16036,16038,16040,16042],{"class":681,"line":2129},[679,16035,9592],{"class":693},[679,16037,1729],{"class":880},[679,16039,745],{"class":693},[679,16041,15594],{"class":689},[679,16043,1208],{"class":693},[679,16045,16046,16048,16050,16052,16054],{"class":681,"line":2140},[679,16047,9592],{"class":693},[679,16049,1729],{"class":880},[679,16051,745],{"class":693},[679,16053,15607],{"class":689},[679,16055,1208],{"class":693},[679,16057,16058,16060,16062,16064,16066],{"class":681,"line":2145},[679,16059,9592],{"class":693},[679,16061,1729],{"class":880},[679,16063,745],{"class":693},[679,16065,15620],{"class":689},[679,16067,1208],{"class":693},[679,16069,16070,16072,16074,16076,16078],{"class":681,"line":2154},[679,16071,15627],{"class":693},[679,16073,686],{"class":685},[679,16075,15632],{"class":693},[679,16077,15635],{"class":880},[679,16079,9317],{"class":693},[679,16081,16082],{"class":681,"line":2159},[679,16083,889],{"emptyLinePlaceholder":797},[679,16085,16086,16088,16091,16093,16095,16098],{"class":681,"line":2164},[679,16087,1249],{"class":685},[679,16089,16090],{"class":693},"( answer.",[679,16092,14592],{"class":880},[679,16094,745],{"class":693},[679,16096,16097],{"class":689},"\"C\"",[679,16099,16100],{"class":693},") ) {\n",[679,16102,16103,16105,16107,16109,16111],{"class":681,"line":3134},[679,16104,15660],{"class":693},[679,16106,15663],{"class":880},[679,16108,745],{"class":693},[679,16110,15668],{"class":689},[679,16112,1208],{"class":693},[679,16114,16115,16117,16119],{"class":681,"line":3139},[679,16116,15675],{"class":693},[679,16118,2256],{"class":685},[679,16120,884],{"class":693},[679,16122,16123,16125,16127,16129,16131],{"class":681,"line":3144},[679,16124,15660],{"class":693},[679,16126,15663],{"class":880},[679,16128,745],{"class":693},[679,16130,15690],{"class":689},[679,16132,1208],{"class":693},[679,16134,16135],{"class":681,"line":3149},[679,16136,1290],{"class":693},[679,16138,16139],{"class":681,"line":3169},[679,16140,889],{"emptyLinePlaceholder":797},[679,16142,16143],{"class":681,"line":3185},[679,16144,985],{"class":693},[679,16146,16147],{"class":681,"line":3194},[679,16148,889],{"emptyLinePlaceholder":797},[679,16150,16151],{"class":681,"line":3199},[679,16152,996],{"class":693},[651,16154,16155],{},"Now instead of comparing objects, we are simply comparing values. That is why you will often hear programmers say \"compare by value, not reference\". We probably fix a couple other issues with this program but that is neither here nor there. ",[4542,16157,9042],{"id":9041},[651,16159,16160,16161,16166],{},"Remember when I told you that I made this same mistake at work? There is actually a good reason for this. I was working in a Java program and this gives me some problems from time to time because of my love for the ",[812,16162,16165],{"href":16163,"rel":16164},"http://bit.ly/2n5SBn6",[816],"Groovy Programming language",". I would say I am writing Groovy 90% of the time and because of that, I become used to the syntax. In Groovy we actually overload the \"==\" operator so that comparison would actually work. ",[651,16168,16169],{},[7300,16170,16171,16173],{},[2939,16172,11650],{}," What are some of the fundamental programming concepts that still trip you up once in awhile?",[786,16175,16176],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":16178},[16179,16182,16183],{"id":11141,"depth":790,"text":11142,"children":16180},[16181],{"id":15784,"depth":892,"text":15785},{"id":11154,"depth":790,"text":11155},{"id":9041,"depth":790,"text":9042},{"slug":16185,"published":797,"date":16186,"tags":16187,"cover":16188},"java-equals","2017-04-05T08:00:50-04:00",[4109],"./java_equals-760x428.png",{"title":216,"description":216},"blog/2017/04/05/java-equals","wm_vkAr56acPVZAewUMN6hUUqQ35WazU3duADdbrQMI",{"id":16193,"title":213,"body":16194,"description":213,"extension":793,"meta":17478,"navigation":797,"path":214,"seo":17485,"stem":17486,"__hash__":17487},"content/blog/2017/04/07/spring-boot-command-line-runner.md",{"type":648,"value":16195,"toc":17470},[16196,16199,16207,16210,16214,16221,16224,16230,16233,16237,16244,16251,16258,16356,16367,16406,16420,16520,16527,16728,16737,16743,16747,16759,16852,16859,17031,17035,17038,17241,17440,17443,17449,17451,17460,17467],[651,16197,16198],{},"A student asked me the other day what a Command Line Runner was and when can we use one. If you watch demos of Spring Boot features or applications you have probably seen this and said to yourself \"What the heck is that?\". If you take a look a look at the API Documentation for Command Line Runner you will get this elegant explanation.",[1004,16200,16201],{},[651,16202,16203,16204,16206],{},"Interface used to indicate that a bean should ",[7300,16205,6118],{}," when it is contained within a SpringApplication. Multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the Ordered interface or @Order annotation.",[651,16208,16209],{},"In this article, I will explain what a Command Line Runner is and what you might use it for in your next Spring Boot application.",[4542,16211,16213],{"id":16212},"command-line-runner","Command Line Runner",[651,16215,16216,16217,16220],{},"A command-line runner is an interface that you can implement to execute some code before the application starts up. If we define an implementation of the Command Line Runner interface you will need to override the ",[676,16218,16219],{},"run()"," method. This method will be executed after the application context is loaded and right before the Spring Application run method is completed.",[651,16222,16223],{},"Start by creating a new application with the Spring Initalizr and select the following dependencies:",[651,16225,16226],{},[660,16227],{"alt":16228,"src":16229},"Spring Boot Init","./spring-boot-init.png",[651,16231,16232],{},"Before we dive in and create one we should talk about some of the things you might want to use this for. Remember that this going to execute after the application context is loaded so you could use it to check if certain beans exist or what values of certain properties are. Another reason to use it is to load some data right before your application fires up. I will say that I have used a Command Line Runner for these purposes before but they were both in development or demo environments.",[5909,16234,16236],{"id":16235},"command-line-runner-demo","Command Line Runner Demo",[651,16238,16239,16240,16243],{},"In this demo, you are going to create a DataLoader class that is going to load some initial demo data into your application. For demo purposes you are creating a web application with JPA & H2. The first thing you ned to do is open up ",[676,16241,16242],{},"application.properties"," and add the following to establish a connection to the H2 database:",[669,16245,16249],{"className":16246,"code":16248,"language":11464},[16247],"language-text","spring.h2.console.enabled=true\nspring.datasource.generate-unique-name=false\nspring.datasource.name=images\n",[676,16250,16248],{"__ignoreMap":674},[651,16252,16253,16254,16257],{},"Next you will need to create a simple ",[676,16255,16256],{},"@Entity"," class named Image. This class will represent an image but the constructor simply takes a String for the image name. You aren't working any real images in this demo.",[669,16259,16261],{"className":4107,"code":16260,"language":4109,"meta":674,"style":674},"@Entity\n@Data\n@NoArgsConstructor\npublic class Image {\n\n @Id @GeneratedValue\n private Long id;\n private String name;\n\n public Image(String name) {\n this.name = name;\n }\n}\n",[676,16262,16263,16269,16275,16282,16293,16297,16307,16313,16320,16324,16337,16348,16352],{"__ignoreMap":674},[679,16264,16265,16267],{"class":681,"line":682},[679,16266,4116],{"class":693},[679,16268,11234],{"class":685},[679,16270,16271,16273],{"class":681,"line":790},[679,16272,4116],{"class":693},[679,16274,15305],{"class":685},[679,16276,16277,16279],{"class":681,"line":892},[679,16278,4116],{"class":693},[679,16280,16281],{"class":685},"NoArgsConstructor\n",[679,16283,16284,16286,16288,16291],{"class":681,"line":901},[679,16285,6073],{"class":685},[679,16287,4512],{"class":685},[679,16289,16290],{"class":880}," Image",[679,16292,884],{"class":693},[679,16294,16295],{"class":681,"line":909},[679,16296,889],{"emptyLinePlaceholder":797},[679,16298,16299,16301,16303,16305],{"class":681,"line":918},[679,16300,6872],{"class":693},[679,16302,11256],{"class":685},[679,16304,6475],{"class":693},[679,16306,11261],{"class":685},[679,16308,16309,16311],{"class":681,"line":935},[679,16310,9232],{"class":685},[679,16312,11268],{"class":693},[679,16314,16315,16317],{"class":681,"line":944},[679,16316,9232],{"class":685},[679,16318,16319],{"class":693}," String name;\n",[679,16321,16322],{"class":681,"line":959},[679,16323,889],{"emptyLinePlaceholder":797},[679,16325,16326,16328,16330,16332,16335],{"class":681,"line":964},[679,16327,6089],{"class":685},[679,16329,16290],{"class":880},[679,16331,11400],{"class":693},[679,16333,16334],{"class":2099},"name",[679,16336,4390],{"class":693},[679,16338,16339,16341,16344,16346],{"class":681,"line":977},[679,16340,7862],{"class":931},[679,16342,16343],{"class":693},".name ",[679,16345,686],{"class":685},[679,16347,15853],{"class":693},[679,16349,16350],{"class":681,"line":982},[679,16351,985],{"class":693},[679,16353,16354],{"class":681,"line":988},[679,16355,996],{"class":693},[651,16357,16358,16359,16362,16363,16366],{},"When your application starts up you want to load a couple of images into your database. You could place a SQL script in the resources folder, but who wants to write SQL from scratch? 🤦♂️ To persist data to the database create a new interface called ",[676,16360,16361],{},"ImageRepository"," and extend the ",[676,16364,16365],{},"CrudRepository"," interface which will provide you with CRUD methods.",[669,16368,16370],{"className":4107,"code":16369,"language":4109,"meta":674,"style":674},"public interface ImageRepository extends CrudRepository\u003CImage,Long> {\n\n}\n",[676,16371,16372,16398,16402],{"__ignoreMap":674},[679,16373,16374,16376,16378,16381,16383,16386,16388,16391,16393,16395],{"class":681,"line":682},[679,16375,6073],{"class":685},[679,16377,6994],{"class":685},[679,16379,16380],{"class":880}," ImageRepository",[679,16382,2767],{"class":685},[679,16384,16385],{"class":880}," CrudRepository",[679,16387,4505],{"class":693},[679,16389,16390],{"class":685},"Image",[679,16392,1202],{"class":693},[679,16394,1094],{"class":685},[679,16396,16397],{"class":693},"> {\n",[679,16399,16400],{"class":681,"line":790},[679,16401,889],{"emptyLinePlaceholder":797},[679,16403,16404],{"class":681,"line":892},[679,16405,996],{"class":693},[651,16407,16408,16409,16412,16413,16416,16417,16419],{},"Now that you have a way to persist data you can create a new class called ",[676,16410,16411],{},"DataLoader"," that will be responsible for loading the data. This class is going to implement the ",[676,16414,16415],{},"CommandLineRunner"," interface and override the run method. The other important thing to note here is that you need to annotate the class with ",[676,16418,12292],{}," or Spring will never find it and call the run method.",[669,16421,16423],{"className":4107,"code":16422,"language":4109,"meta":674,"style":674},"@Component\npublic class DataLoader implements CommandLineRunner {\n\n private final Logger logger = LoggerFactory.getLogger(DataLoader.class);\n\n @Override\n public void run(String... strings) throws Exception {\n logger.info(\"Loading data...\");\n }\n}\n",[676,16424,16425,16431,16447,16451,16468,16472,16478,16499,16512,16516],{"__ignoreMap":674},[679,16426,16427,16429],{"class":681,"line":682},[679,16428,4116],{"class":693},[679,16430,13105],{"class":685},[679,16432,16433,16435,16437,16440,16442,16445],{"class":681,"line":790},[679,16434,6073],{"class":685},[679,16436,4512],{"class":685},[679,16438,16439],{"class":880}," DataLoader",[679,16441,4661],{"class":685},[679,16443,16444],{"class":880}," CommandLineRunner",[679,16446,884],{"class":693},[679,16448,16449],{"class":681,"line":892},[679,16450,889],{"emptyLinePlaceholder":797},[679,16452,16453,16455,16457,16459,16461,16463,16465],{"class":681,"line":901},[679,16454,9232],{"class":685},[679,16456,12768],{"class":685},[679,16458,9235],{"class":693},[679,16460,686],{"class":685},[679,16462,9240],{"class":693},[679,16464,9243],{"class":880},[679,16466,16467],{"class":693},"(DataLoader.class);\n",[679,16469,16470],{"class":681,"line":909},[679,16471,889],{"emptyLinePlaceholder":797},[679,16473,16474,16476],{"class":681,"line":918},[679,16475,6872],{"class":693},[679,16477,10723],{"class":685},[679,16479,16480,16482,16484,16487,16490,16493,16495,16497],{"class":681,"line":935},[679,16481,6089],{"class":685},[679,16483,6095],{"class":685},[679,16485,16486],{"class":880}," run",[679,16488,16489],{"class":693},"(String... ",[679,16491,16492],{"class":2099},"strings",[679,16494,2378],{"class":693},[679,16496,9580],{"class":685},[679,16498,10466],{"class":693},[679,16500,16501,16503,16505,16507,16510],{"class":681,"line":944},[679,16502,12851],{"class":693},[679,16504,9415],{"class":880},[679,16506,745],{"class":693},[679,16508,16509],{"class":689},"\"Loading data...\"",[679,16511,1208],{"class":693},[679,16513,16514],{"class":681,"line":959},[679,16515,985],{"class":693},[679,16517,16518],{"class":681,"line":964},[679,16519,996],{"class":693},[651,16521,16522,16523,16526],{},"Now let's work on persisting some data into a database. In the run method you will create a list of 3 images with simple names. With a list of images you can call the repositories ",[676,16524,16525],{},"saveAll()"," method which takes an Iterable of entities.",[669,16528,16530],{"className":4107,"code":16529,"language":4109,"meta":674,"style":674},"@Component\npublic class DataLoader implements CommandLineRunner {\n\n private final Logger logger = LoggerFactory.getLogger(DataLoader.class);\n private final ImageRepository repository;\n\n public DataLoader(ImageRepository repository) {\n this.repository = repository;\n }\n\n @Override\n public void run(String... args) throws Exception {\n logger.info(\"Loading data...\");\n List\u003CImage> images = List.of(new Image(\"Image 1\"), new Image(\"Image 2\"), new Image(\"Image 3\"));\n repository.saveAll(images);\n }\n}\n",[676,16531,16532,16538,16552,16556,16572,16581,16585,16599,16611,16615,16619,16625,16643,16655,16709,16720,16724],{"__ignoreMap":674},[679,16533,16534,16536],{"class":681,"line":682},[679,16535,4116],{"class":693},[679,16537,13105],{"class":685},[679,16539,16540,16542,16544,16546,16548,16550],{"class":681,"line":790},[679,16541,6073],{"class":685},[679,16543,4512],{"class":685},[679,16545,16439],{"class":880},[679,16547,4661],{"class":685},[679,16549,16444],{"class":880},[679,16551,884],{"class":693},[679,16553,16554],{"class":681,"line":892},[679,16555,889],{"emptyLinePlaceholder":797},[679,16557,16558,16560,16562,16564,16566,16568,16570],{"class":681,"line":901},[679,16559,9232],{"class":685},[679,16561,12768],{"class":685},[679,16563,9235],{"class":693},[679,16565,686],{"class":685},[679,16567,9240],{"class":693},[679,16569,9243],{"class":880},[679,16571,16467],{"class":693},[679,16573,16574,16576,16578],{"class":681,"line":909},[679,16575,9232],{"class":685},[679,16577,12768],{"class":685},[679,16579,16580],{"class":693}," ImageRepository repository;\n",[679,16582,16583],{"class":681,"line":918},[679,16584,889],{"emptyLinePlaceholder":797},[679,16586,16587,16589,16591,16594,16597],{"class":681,"line":935},[679,16588,6089],{"class":685},[679,16590,16439],{"class":880},[679,16592,16593],{"class":693},"(ImageRepository ",[679,16595,16596],{"class":2099},"repository",[679,16598,4390],{"class":693},[679,16600,16601,16603,16606,16608],{"class":681,"line":944},[679,16602,7862],{"class":931},[679,16604,16605],{"class":693},".repository ",[679,16607,686],{"class":685},[679,16609,16610],{"class":693}," repository;\n",[679,16612,16613],{"class":681,"line":959},[679,16614,985],{"class":693},[679,16616,16617],{"class":681,"line":964},[679,16618,889],{"emptyLinePlaceholder":797},[679,16620,16621,16623],{"class":681,"line":977},[679,16622,6872],{"class":693},[679,16624,10723],{"class":685},[679,16626,16627,16629,16631,16633,16635,16637,16639,16641],{"class":681,"line":982},[679,16628,6089],{"class":685},[679,16630,6095],{"class":685},[679,16632,16486],{"class":880},[679,16634,16489],{"class":693},[679,16636,6108],{"class":2099},[679,16638,2378],{"class":693},[679,16640,9580],{"class":685},[679,16642,10466],{"class":693},[679,16644,16645,16647,16649,16651,16653],{"class":681,"line":988},[679,16646,12851],{"class":693},[679,16648,9415],{"class":880},[679,16650,745],{"class":693},[679,16652,16509],{"class":689},[679,16654,1208],{"class":693},[679,16656,16657,16660,16662,16665,16667,16670,16673,16675,16677,16679,16681,16684,16687,16689,16691,16693,16696,16698,16700,16702,16704,16707],{"class":681,"line":993},[679,16658,16659],{"class":693}," List\u003C",[679,16661,16390],{"class":685},[679,16663,16664],{"class":693},"> images ",[679,16666,686],{"class":685},[679,16668,16669],{"class":693}," List.",[679,16671,16672],{"class":880},"of",[679,16674,745],{"class":693},[679,16676,8930],{"class":685},[679,16678,16290],{"class":880},[679,16680,745],{"class":693},[679,16682,16683],{"class":689},"\"Image 1\"",[679,16685,16686],{"class":693},"), ",[679,16688,8930],{"class":685},[679,16690,16290],{"class":880},[679,16692,745],{"class":693},[679,16694,16695],{"class":689},"\"Image 2\"",[679,16697,16686],{"class":693},[679,16699,8930],{"class":685},[679,16701,16290],{"class":880},[679,16703,745],{"class":693},[679,16705,16706],{"class":689},"\"Image 3\"",[679,16708,1669],{"class":693},[679,16710,16711,16714,16717],{"class":681,"line":2129},[679,16712,16713],{"class":693}," repository.",[679,16715,16716],{"class":880},"saveAll",[679,16718,16719],{"class":693},"(images);\n",[679,16721,16722],{"class":681,"line":2140},[679,16723,985],{"class":693},[679,16725,16726],{"class":681,"line":2145},[679,16727,996],{"class":693},[651,16729,16730,16731,16736],{},"If you start the application and look at the ",[812,16732,16735],{"href":16733,"rel":16734},"http://localhost:8080/h2-console",[816],"H2 console"," you will see the new database rows.",[651,16738,16739],{},[660,16740],{"alt":16741,"src":16742},"H2 Console","./h2-console.png",[5909,16744,16746],{"id":16745},"command-line-runner-functional-interface","Command Line Runner Functional Interface",[651,16748,16749,16750,16752,16753,16755,16756,664],{},"If you look at the source code for the ",[676,16751,16415],{}," interface you will see that it has a single method called ",[676,16754,16219],{}," and is marked as a ",[676,16757,16758],{},"@FunctionalInterface",[669,16760,16762],{"className":4107,"code":16761,"language":4109,"meta":674,"style":674},"@FunctionalInterface\npublic interface CommandLineRunner {\n\n /**\n * Callback used to run the bean.\n * @param args incoming main method arguments\n * @throws Exception on error\n */\n void run(String... args) throws Exception;\n\n}\n",[676,16763,16764,16771,16781,16785,16790,16795,16809,16822,16827,16844,16848],{"__ignoreMap":674},[679,16765,16766,16768],{"class":681,"line":682},[679,16767,4116],{"class":693},[679,16769,16770],{"class":685},"FunctionalInterface\n",[679,16772,16773,16775,16777,16779],{"class":681,"line":790},[679,16774,6073],{"class":685},[679,16776,6994],{"class":685},[679,16778,16444],{"class":880},[679,16780,884],{"class":693},[679,16782,16783],{"class":681,"line":892},[679,16784,889],{"emptyLinePlaceholder":797},[679,16786,16787],{"class":681,"line":901},[679,16788,16789],{"class":1400}," /**\n",[679,16791,16792],{"class":681,"line":909},[679,16793,16794],{"class":1400}," * Callback used to run the bean.\n",[679,16796,16797,16800,16803,16806],{"class":681,"line":918},[679,16798,16799],{"class":1400}," * ",[679,16801,16802],{"class":685},"@param",[679,16804,16805],{"class":2099}," args",[679,16807,16808],{"class":1400}," incoming main method arguments\n",[679,16810,16811,16813,16816,16819],{"class":681,"line":935},[679,16812,16799],{"class":1400},[679,16814,16815],{"class":685},"@throws",[679,16817,16818],{"class":880}," Exception",[679,16820,16821],{"class":1400}," on error\n",[679,16823,16824],{"class":681,"line":944},[679,16825,16826],{"class":1400}," */\n",[679,16828,16829,16831,16833,16835,16837,16839,16841],{"class":681,"line":959},[679,16830,3314],{"class":685},[679,16832,16486],{"class":880},[679,16834,16489],{"class":693},[679,16836,6108],{"class":2099},[679,16838,2378],{"class":693},[679,16840,9580],{"class":685},[679,16842,16843],{"class":693}," Exception;\n",[679,16845,16846],{"class":681,"line":964},[679,16847,889],{"emptyLinePlaceholder":797},[679,16849,16850],{"class":681,"line":977},[679,16851,996],{"class":693},[651,16853,16854,16855,16858],{},"This means that this is a candidate for a lambda expression. This means that instead of creating a whole new class you can just create a method and annotate it with ",[676,16856,16857],{},"@Bean"," so that Spring will find it. The following Command Line Runner is in the main application class and does the same thing as the class you created before.",[669,16860,16862],{"className":4107,"code":16861,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n public CommandLineRunner runner(ImageRepository repository) {\n return args -> {\n List\u003CImage> images = List.of(new Image(\"Image 1\"), new Image(\"Image 2\"), new Image(\"Image 3\"));\n repository.saveAll(images);\n };\n }\n\n}\n",[676,16863,16864,16870,16881,16885,16906,16915,16919,16923,16930,16946,16958,17005,17014,17019,17023,17027],{"__ignoreMap":674},[679,16865,16866,16868],{"class":681,"line":682},[679,16867,4116],{"class":693},[679,16869,6068],{"class":685},[679,16871,16872,16874,16876,16879],{"class":681,"line":790},[679,16873,6073],{"class":685},[679,16875,4512],{"class":685},[679,16877,16878],{"class":880}," Application",[679,16880,884],{"class":693},[679,16882,16883],{"class":681,"line":892},[679,16884,889],{"emptyLinePlaceholder":797},[679,16886,16887,16889,16891,16893,16895,16897,16899,16902,16904],{"class":681,"line":901},[679,16888,6089],{"class":685},[679,16890,6092],{"class":685},[679,16892,6095],{"class":685},[679,16894,6098],{"class":880},[679,16896,745],{"class":693},[679,16898,4758],{"class":685},[679,16900,16901],{"class":693},"[] ",[679,16903,6108],{"class":2099},[679,16905,4390],{"class":693},[679,16907,16908,16910,16912],{"class":681,"line":909},[679,16909,6115],{"class":693},[679,16911,6118],{"class":880},[679,16913,16914],{"class":693},"(Application.class, args);\n",[679,16916,16917],{"class":681,"line":918},[679,16918,985],{"class":693},[679,16920,16921],{"class":681,"line":935},[679,16922,889],{"emptyLinePlaceholder":797},[679,16924,16925,16927],{"class":681,"line":944},[679,16926,6872],{"class":693},[679,16928,16929],{"class":685},"Bean\n",[679,16931,16932,16934,16937,16940,16942,16944],{"class":681,"line":959},[679,16933,6089],{"class":685},[679,16935,16936],{"class":693}," CommandLineRunner ",[679,16938,16939],{"class":880},"runner",[679,16941,16593],{"class":693},[679,16943,16596],{"class":2099},[679,16945,4390],{"class":693},[679,16947,16948,16950,16953,16956],{"class":681,"line":964},[679,16949,9444],{"class":685},[679,16951,16952],{"class":693}," args ",[679,16954,16955],{"class":685},"->",[679,16957,884],{"class":693},[679,16959,16960,16963,16965,16967,16969,16971,16973,16975,16977,16979,16981,16983,16985,16987,16989,16991,16993,16995,16997,16999,17001,17003],{"class":681,"line":977},[679,16961,16962],{"class":693}," List\u003C",[679,16964,16390],{"class":685},[679,16966,16664],{"class":693},[679,16968,686],{"class":685},[679,16970,16669],{"class":693},[679,16972,16672],{"class":880},[679,16974,745],{"class":693},[679,16976,8930],{"class":685},[679,16978,16290],{"class":880},[679,16980,745],{"class":693},[679,16982,16683],{"class":689},[679,16984,16686],{"class":693},[679,16986,8930],{"class":685},[679,16988,16290],{"class":880},[679,16990,745],{"class":693},[679,16992,16695],{"class":689},[679,16994,16686],{"class":693},[679,16996,8930],{"class":685},[679,16998,16290],{"class":880},[679,17000,745],{"class":693},[679,17002,16706],{"class":689},[679,17004,1669],{"class":693},[679,17006,17007,17010,17012],{"class":681,"line":982},[679,17008,17009],{"class":693}," repository.",[679,17011,16716],{"class":880},[679,17013,16719],{"class":693},[679,17015,17016],{"class":681,"line":988},[679,17017,17018],{"class":693}," };\n",[679,17020,17021],{"class":681,"line":993},[679,17022,985],{"class":693},[679,17024,17025],{"class":681,"line":2129},[679,17026,889],{"emptyLinePlaceholder":797},[679,17028,17029],{"class":681,"line":2140},[679,17030,996],{"class":693},[5909,17032,17034],{"id":17033},"multiple-command-line-runners","Multiple Command Line Runners",[651,17036,17037],{},"What if you wanted to create 2 Command Line Runners that did contained completely separate logic? No problem, just use the annotation Order to specify the order in which they should run.",[669,17039,17041],{"className":4107,"code":17040,"language":4109,"meta":674,"style":674},"@Component\n@Order(1)\npublic class AnotherDataLoader implements CommandLineRunner {\n\n private final Logger logger = LoggerFactory.getLogger(DataLoader.class);\n private final ImageRepository repository;\n\n public AnotherDataLoader(ImageRepository repository) {\n this.repository = repository;\n }\n\n @Override\n public void run(String... args) throws Exception {\n logger.info(\"Loading data from AnotherDataLoader...\");\n List\u003CImage> images = List.of(new Image(\"Image 4\"), new Image(\"Image 5\"), new Image(\"Image 6\"));\n repository.saveAll(images);\n }\n}\n",[676,17042,17043,17049,17062,17077,17081,17097,17105,17109,17121,17131,17135,17139,17145,17163,17176,17225,17233,17237],{"__ignoreMap":674},[679,17044,17045,17047],{"class":681,"line":682},[679,17046,4116],{"class":693},[679,17048,13105],{"class":685},[679,17050,17051,17053,17056,17058,17060],{"class":681,"line":790},[679,17052,4116],{"class":693},[679,17054,17055],{"class":685},"Order",[679,17057,745],{"class":693},[679,17059,1557],{"class":931},[679,17061,1339],{"class":693},[679,17063,17064,17066,17068,17071,17073,17075],{"class":681,"line":892},[679,17065,6073],{"class":685},[679,17067,4512],{"class":685},[679,17069,17070],{"class":880}," AnotherDataLoader",[679,17072,4661],{"class":685},[679,17074,16444],{"class":880},[679,17076,884],{"class":693},[679,17078,17079],{"class":681,"line":901},[679,17080,889],{"emptyLinePlaceholder":797},[679,17082,17083,17085,17087,17089,17091,17093,17095],{"class":681,"line":909},[679,17084,9232],{"class":685},[679,17086,12768],{"class":685},[679,17088,9235],{"class":693},[679,17090,686],{"class":685},[679,17092,9240],{"class":693},[679,17094,9243],{"class":880},[679,17096,16467],{"class":693},[679,17098,17099,17101,17103],{"class":681,"line":918},[679,17100,9232],{"class":685},[679,17102,12768],{"class":685},[679,17104,16580],{"class":693},[679,17106,17107],{"class":681,"line":935},[679,17108,889],{"emptyLinePlaceholder":797},[679,17110,17111,17113,17115,17117,17119],{"class":681,"line":944},[679,17112,6089],{"class":685},[679,17114,17070],{"class":880},[679,17116,16593],{"class":693},[679,17118,16596],{"class":2099},[679,17120,4390],{"class":693},[679,17122,17123,17125,17127,17129],{"class":681,"line":959},[679,17124,7862],{"class":931},[679,17126,16605],{"class":693},[679,17128,686],{"class":685},[679,17130,16610],{"class":693},[679,17132,17133],{"class":681,"line":964},[679,17134,985],{"class":693},[679,17136,17137],{"class":681,"line":977},[679,17138,889],{"emptyLinePlaceholder":797},[679,17140,17141,17143],{"class":681,"line":982},[679,17142,6872],{"class":693},[679,17144,10723],{"class":685},[679,17146,17147,17149,17151,17153,17155,17157,17159,17161],{"class":681,"line":988},[679,17148,6089],{"class":685},[679,17150,6095],{"class":685},[679,17152,16486],{"class":880},[679,17154,16489],{"class":693},[679,17156,6108],{"class":2099},[679,17158,2378],{"class":693},[679,17160,9580],{"class":685},[679,17162,10466],{"class":693},[679,17164,17165,17167,17169,17171,17174],{"class":681,"line":993},[679,17166,12851],{"class":693},[679,17168,9415],{"class":880},[679,17170,745],{"class":693},[679,17172,17173],{"class":689},"\"Loading data from AnotherDataLoader...\"",[679,17175,1208],{"class":693},[679,17177,17178,17180,17182,17184,17186,17188,17190,17192,17194,17196,17198,17201,17203,17205,17207,17209,17212,17214,17216,17218,17220,17223],{"class":681,"line":2129},[679,17179,16659],{"class":693},[679,17181,16390],{"class":685},[679,17183,16664],{"class":693},[679,17185,686],{"class":685},[679,17187,16669],{"class":693},[679,17189,16672],{"class":880},[679,17191,745],{"class":693},[679,17193,8930],{"class":685},[679,17195,16290],{"class":880},[679,17197,745],{"class":693},[679,17199,17200],{"class":689},"\"Image 4\"",[679,17202,16686],{"class":693},[679,17204,8930],{"class":685},[679,17206,16290],{"class":880},[679,17208,745],{"class":693},[679,17210,17211],{"class":689},"\"Image 5\"",[679,17213,16686],{"class":693},[679,17215,8930],{"class":685},[679,17217,16290],{"class":880},[679,17219,745],{"class":693},[679,17221,17222],{"class":689},"\"Image 6\"",[679,17224,1669],{"class":693},[679,17226,17227,17229,17231],{"class":681,"line":2140},[679,17228,16713],{"class":693},[679,17230,16716],{"class":880},[679,17232,16719],{"class":693},[679,17234,17235],{"class":681,"line":2145},[679,17236,985],{"class":693},[679,17238,17239],{"class":681,"line":2154},[679,17240,996],{"class":693},[669,17242,17244],{"className":4107,"code":17243,"language":4109,"meta":674,"style":674},"@Component\n@Order(2)\npublic class DataLoader implements CommandLineRunner {\n\n private final Logger logger = LoggerFactory.getLogger(DataLoader.class);\n private final ImageRepository repository;\n\n public DataLoader(ImageRepository repository) {\n this.repository = repository;\n }\n\n @Override\n public void run(String... args) throws Exception {\n logger.info(\"Loading data from DataLoader...\");\n List\u003CImage> images = List.of(new Image(\"Image 1\"), new Image(\"Image 2\"), new Image(\"Image 3\"));\n repository.saveAll(images);\n }\n}\n",[676,17245,17246,17252,17265,17279,17283,17299,17307,17311,17323,17333,17337,17341,17347,17365,17378,17424,17432,17436],{"__ignoreMap":674},[679,17247,17248,17250],{"class":681,"line":682},[679,17249,4116],{"class":693},[679,17251,13105],{"class":685},[679,17253,17254,17256,17258,17260,17263],{"class":681,"line":790},[679,17255,4116],{"class":693},[679,17257,17055],{"class":685},[679,17259,745],{"class":693},[679,17261,17262],{"class":931},"2",[679,17264,1339],{"class":693},[679,17266,17267,17269,17271,17273,17275,17277],{"class":681,"line":892},[679,17268,6073],{"class":685},[679,17270,4512],{"class":685},[679,17272,16439],{"class":880},[679,17274,4661],{"class":685},[679,17276,16444],{"class":880},[679,17278,884],{"class":693},[679,17280,17281],{"class":681,"line":901},[679,17282,889],{"emptyLinePlaceholder":797},[679,17284,17285,17287,17289,17291,17293,17295,17297],{"class":681,"line":909},[679,17286,9232],{"class":685},[679,17288,12768],{"class":685},[679,17290,9235],{"class":693},[679,17292,686],{"class":685},[679,17294,9240],{"class":693},[679,17296,9243],{"class":880},[679,17298,16467],{"class":693},[679,17300,17301,17303,17305],{"class":681,"line":918},[679,17302,9232],{"class":685},[679,17304,12768],{"class":685},[679,17306,16580],{"class":693},[679,17308,17309],{"class":681,"line":935},[679,17310,889],{"emptyLinePlaceholder":797},[679,17312,17313,17315,17317,17319,17321],{"class":681,"line":944},[679,17314,6089],{"class":685},[679,17316,16439],{"class":880},[679,17318,16593],{"class":693},[679,17320,16596],{"class":2099},[679,17322,4390],{"class":693},[679,17324,17325,17327,17329,17331],{"class":681,"line":959},[679,17326,7862],{"class":931},[679,17328,16605],{"class":693},[679,17330,686],{"class":685},[679,17332,16610],{"class":693},[679,17334,17335],{"class":681,"line":964},[679,17336,985],{"class":693},[679,17338,17339],{"class":681,"line":977},[679,17340,889],{"emptyLinePlaceholder":797},[679,17342,17343,17345],{"class":681,"line":982},[679,17344,6872],{"class":693},[679,17346,10723],{"class":685},[679,17348,17349,17351,17353,17355,17357,17359,17361,17363],{"class":681,"line":988},[679,17350,6089],{"class":685},[679,17352,6095],{"class":685},[679,17354,16486],{"class":880},[679,17356,16489],{"class":693},[679,17358,6108],{"class":2099},[679,17360,2378],{"class":693},[679,17362,9580],{"class":685},[679,17364,10466],{"class":693},[679,17366,17367,17369,17371,17373,17376],{"class":681,"line":993},[679,17368,12851],{"class":693},[679,17370,9415],{"class":880},[679,17372,745],{"class":693},[679,17374,17375],{"class":689},"\"Loading data from DataLoader...\"",[679,17377,1208],{"class":693},[679,17379,17380,17382,17384,17386,17388,17390,17392,17394,17396,17398,17400,17402,17404,17406,17408,17410,17412,17414,17416,17418,17420,17422],{"class":681,"line":2129},[679,17381,16659],{"class":693},[679,17383,16390],{"class":685},[679,17385,16664],{"class":693},[679,17387,686],{"class":685},[679,17389,16669],{"class":693},[679,17391,16672],{"class":880},[679,17393,745],{"class":693},[679,17395,8930],{"class":685},[679,17397,16290],{"class":880},[679,17399,745],{"class":693},[679,17401,16683],{"class":689},[679,17403,16686],{"class":693},[679,17405,8930],{"class":685},[679,17407,16290],{"class":880},[679,17409,745],{"class":693},[679,17411,16695],{"class":689},[679,17413,16686],{"class":693},[679,17415,8930],{"class":685},[679,17417,16290],{"class":880},[679,17419,745],{"class":693},[679,17421,16706],{"class":689},[679,17423,1669],{"class":693},[679,17425,17426,17428,17430],{"class":681,"line":2140},[679,17427,16713],{"class":693},[679,17429,16716],{"class":880},[679,17431,16719],{"class":693},[679,17433,17434],{"class":681,"line":2145},[679,17435,985],{"class":693},[679,17437,17438],{"class":681,"line":2154},[679,17439,996],{"class":693},[651,17441,17442],{},"If we look in the console we can see that Another Database Loader ran first.",[651,17444,17445],{},[660,17446],{"alt":17447,"src":17448},"Command Line Runner @Order Annotation","./command-line-runner-order.png",[4542,17450,9042],{"id":9041},[651,17452,17453,17454,17459],{},"I hope this helped clear up some confusion on what a Command Line Runner is and how to use it. If you want to checkout the code for this demo you will find it over on ",[812,17455,17458],{"href":17456,"rel":17457},"https://github.com/danvega/command-line-runner",[816],"Github",". If you're already using a Command Line Runner in your applications I would like to leave you with this question to kick start a discussion.",[651,17461,17462],{},[7300,17463,17464,17466],{},[2939,17465,11650],{}," What are some other uses for a Command Line Runner?",[786,17468,17469],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":17471},[17472,17477],{"id":16212,"depth":790,"text":16213,"children":17473},[17474,17475,17476],{"id":16235,"depth":892,"text":16236},{"id":16745,"depth":892,"text":16746},{"id":17033,"depth":892,"text":17034},{"id":9041,"depth":790,"text":9042},{"slug":17479,"published":797,"date":17480,"updatedOn":17481,"tags":17482,"cover":17483,"video":17484},"spring-boot-command-line-runner","2017-04-07T08:00:43-04:00","2022-05-11T11:00:00-05:00",[7055],"./spring-boot-command-line-runner-thumbnail.png","https://www.youtube.com/embed/gBN8x4yN5Ks",{"title":213,"description":213},"blog/2017/04/07/spring-boot-command-line-runner","G9LLR3ayA_fm-hkuX7m7Ya5ZCHizxnOi9vYo3CrluQ4",{"id":17489,"title":210,"body":17490,"description":17670,"extension":793,"meta":17671,"navigation":797,"path":211,"seo":17676,"stem":17677,"__hash__":17678},"content/blog/2017/04/10/inserting-a-groovy-date-into-a-time-stamp-column.md",{"type":648,"value":17491,"toc":17668},[17492,17495,17514,17517,17585,17588,17657,17660,17665],[651,17493,17494],{},"Working with Dates in any language is one of those core fundamentals you need to know right away. Lucky for us, Groovy makes it super simple to work with dates. I am working on a project where I am using straight SQL to insert a record into a database using Groovy. It's pretty darn easy in most languages to grab the current date/time and in Java, you can do so just by creating a new instance of the Date class.",[669,17496,17498],{"className":4107,"code":17497,"language":4109,"meta":674,"style":674},"Date now = new Date();\n",[676,17499,17500],{"__ignoreMap":674},[679,17501,17502,17505,17507,17509,17512],{"class":681,"line":682},[679,17503,17504],{"class":693},"Date now ",[679,17506,686],{"class":685},[679,17508,2054],{"class":685},[679,17510,17511],{"class":880}," Date",[679,17513,9317],{"class":693},[651,17515,17516],{},"The problem with this (and the same goes for other languages) is that you can't insert that value into a timestamp column. I need to stick this date/time into a timestamp column and to do so in most languages you need to format this so it matches up to what a timestamp column expects. With this simple Java example, we need to bring in another package to do formatting, create a formatting object with the correct pattern and then format our date. Not the hardest thing in the world to do but certainly something I don't care to do.",[669,17518,17520],{"className":4107,"code":17519,"language":4109,"meta":674,"style":674},"import java.text.SimpleDateFormat;\n\nDate now = new Date()\nSimpleDateFormat timestamp = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n\n// 2017-04-10 08:00:00\nprintln timestamp.format(now)\n",[676,17521,17522,17529,17533,17546,17565,17569,17574],{"__ignoreMap":674},[679,17523,17524,17526],{"class":681,"line":682},[679,17525,1999],{"class":685},[679,17527,17528],{"class":693}," java.text.SimpleDateFormat;\n",[679,17530,17531],{"class":681,"line":790},[679,17532,889],{"emptyLinePlaceholder":797},[679,17534,17535,17537,17539,17541,17543],{"class":681,"line":892},[679,17536,17504],{"class":693},[679,17538,686],{"class":685},[679,17540,2054],{"class":685},[679,17542,17511],{"class":880},[679,17544,17545],{"class":693},"()\n",[679,17547,17548,17551,17553,17555,17558,17560,17563],{"class":681,"line":901},[679,17549,17550],{"class":693},"SimpleDateFormat timestamp ",[679,17552,686],{"class":685},[679,17554,2054],{"class":685},[679,17556,17557],{"class":880}," SimpleDateFormat",[679,17559,745],{"class":693},[679,17561,17562],{"class":689},"\"yyyy-MM-dd HH:mm:ss\"",[679,17564,1208],{"class":693},[679,17566,17567],{"class":681,"line":909},[679,17568,889],{"emptyLinePlaceholder":797},[679,17570,17571],{"class":681,"line":918},[679,17572,17573],{"class":1400},"// 2017-04-10 08:00:00\n",[679,17575,17576,17579,17582],{"class":681,"line":935},[679,17577,17578],{"class":693},"println timestamp.",[679,17580,17581],{"class":880},"format",[679,17583,17584],{"class":693},"(now)\n",[651,17586,17587],{},"Luckily I am using Groovy on this project. If you haven't already played around with Groovy it seems to remove the annoyances of Java by adding on to the API. Groovy adds a convenient method to all Date objects for converting a date to a timestamp. If you print out the class names you will also see that it's not just converting it into a formatted string but an actual timestamp object. No extra libraries needed and I don't have to remember what a timestamp format looks like.",[669,17589,17591],{"className":4107,"code":17590,"language":4109,"meta":674,"style":674},"def now = new Date()\ndef timestamp = now.toTimestamp()\n\nprintln now // Mond Apr 10 08:00:00 EST 2017\nprintln now.class.name // java.util.Date\nprintln timestamp // 2017-04-10 08:00:00.00\nprintln timestamp.class.name // java.sql.Timestamp\n",[676,17592,17593,17606,17621,17625,17633,17641,17649],{"__ignoreMap":674},[679,17594,17595,17598,17600,17602,17604],{"class":681,"line":682},[679,17596,17597],{"class":693},"def now ",[679,17599,686],{"class":685},[679,17601,2054],{"class":685},[679,17603,17511],{"class":880},[679,17605,17545],{"class":693},[679,17607,17608,17611,17613,17616,17619],{"class":681,"line":790},[679,17609,17610],{"class":693},"def timestamp ",[679,17612,686],{"class":685},[679,17614,17615],{"class":693}," now.",[679,17617,17618],{"class":880},"toTimestamp",[679,17620,17545],{"class":693},[679,17622,17623],{"class":681,"line":892},[679,17624,889],{"emptyLinePlaceholder":797},[679,17626,17627,17630],{"class":681,"line":901},[679,17628,17629],{"class":693},"println now ",[679,17631,17632],{"class":1400},"// Mond Apr 10 08:00:00 EST 2017\n",[679,17634,17635,17638],{"class":681,"line":909},[679,17636,17637],{"class":693},"println now.class.name ",[679,17639,17640],{"class":1400},"// java.util.Date\n",[679,17642,17643,17646],{"class":681,"line":918},[679,17644,17645],{"class":693},"println timestamp ",[679,17647,17648],{"class":1400},"// 2017-04-10 08:00:00.00\n",[679,17650,17651,17654],{"class":681,"line":935},[679,17652,17653],{"class":693},"println timestamp.class.name ",[679,17655,17656],{"class":1400},"// java.sql.Timestamp\n",[651,17658,17659],{},"To me, this is just one in the land of many examples of how Groovy makes programming in Java fun.",[651,17661,13453,17662,17664],{},[2939,17663,11650],{}," What are some of the other ways Groovy makes your life easier? _",[786,17666,17667],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":17669},[],"Inserting a Groovy Date into a Timestamp Column",{"slug":17672,"published":797,"date":17673,"tags":17674,"cover":17675},"inserting-a-groovy-date-into-a-time-stamp-column","2017-04-10T08:00:06-04:00",[870],"./GroovyDate-760x428.png",{"title":210,"description":17670},"blog/2017/04/10/inserting-a-groovy-date-into-a-time-stamp-column","F28dbyg9EMiN1EIjDHYAItLqfcyT-SEWZK9ebBCBOnU",{"id":17680,"title":207,"body":17681,"description":207,"extension":793,"meta":17831,"navigation":797,"path":208,"seo":17836,"stem":17837,"__hash__":17838},"content/blog/2017/04/17/java-9.md",{"type":648,"value":17682,"toc":17824},[17683,17686,17690,17710,17722,17729,17736,17740,17743,17766,17775,17779,17782,17788,17792,17795,17801,17803,17817],[651,17684,17685],{},"I don't know about you but I love to play around with pre-releases of software. There is something really fun being one of the first to use a new feature and learn how the new release is going to make our lives as developers a little bit easier. Java 9 is coming soon and I am really looking forward to some of the new features. Java 9 has had a couple of delays to date but as I am writing this article (April 2017), it is set to be released in July 2017. There are so many great features in Java 9 and you don't have to wait until July to start checking them out today. In this article, we will go through a quick tutorial on where to get Java 9 and how to get yourself setup. ",[4542,17687,17689],{"id":17688},"installing-java-9","Installing Java 9",[651,17691,17692,17693,17697,17698,17703,17704,17709],{},"The first thing we need to do if we want to use Java 9 is to install it. You will head over to ",[812,17694,17695],{"href":17695,"rel":17696},"https://jdk9.java.net",[816]," and bookmark this site as it contains a bunch of great information and links about Java 9. From there you are going to click on ",[2939,17699,17700],{},[7300,17701,17702],{},"Downloads"," in the upper left-hand menu. The build may be a different one if you're reading this at a later date but that is OK. Accept the user agreement and then download the correct version of the ",[7300,17705,17706],{},[2939,17707,17708],{},"JDK"," for your platform. The Installation is pretty straight forward so just follow the instructions. ",[651,17711,17712,17716,17717,17721],{},[660,17713],{"alt":17714,"src":17715},"Java 9 Downloads","./2017-04-17_09-02-12-1024x448.png"," If you follow me at all you might know what a huge fan of ",[812,17718,17720],{"href":17719},"./http://sdkman.io/","SDKMan"," I am. What you might not know is that you can use SDKMan to manage your Java installs as well now. It is now easier than ever to install and switch between versions of Java. If we run the command \"sdk java list\" we can see that there is a new version for me to install. ",[651,17723,17724,17728],{},[660,17725],{"alt":17726,"src":17727},"SDK Install Java 9","./2017-04-17_09-10-58-1024x498.png"," Now I can run the install command and SDK will pull down that version, make it my default and add the appropriate environment variables. ",[651,17730,17731,17735],{},[660,17732],{"alt":17733,"src":17734},"Installing Java 9 from SDKMan","./2017-04-17_09-16-10-1024x498.png"," It seriously couldn't be any easier! ",[4542,17737,17739],{"id":17738},"java-9-new-features","Java 9 New Features",[651,17741,17742],{},"Now that you have Java 9 installed what are the new features we can start to play with? If you have heard any news yet about Java 9 you have probably heard about Modularity. The most significant JDK 9 enhancement is the Java Platform Module System, which divides the JDK into a set of modules. This isn't the only new feature though, here are a list of my favorites. ",[5316,17744,17745,17748,17751,17754,17757,17760,17763],{},[5332,17746,17747],{},"Module System",[5332,17749,17750],{},"Unified JVM Logging",[5332,17752,17753],{},"Java 9 REPL (JShell)",[5332,17755,17756],{},"HTTP 2 Client",[5332,17758,17759],{},"Stream API Improvements",[5332,17761,17762],{},"Reactive Streams",[5332,17764,17765],{},"Optional Class Improvements",[651,17767,17768,17769,17774],{},"You can read all about these new features on the ",[812,17770,17773],{"href":17771,"rel":17772},"http://openjdk.java.net/projects/jdk9/",[816],"JDK 9 Project Page",". You can also subscribe to this blog as I will be writing up articles and creating video tutorials for YouTube about some of my favorite new features. ",[4542,17776,17778],{"id":17777},"java-9-intellij","Java 9 & IntelliJ ",[651,17780,17781],{},"IntelliJ 2017 brings up Java 9 support. When you open an existing project or create a brand new you just want to make sure you are using the correct version of Java. In your project go to File > Project Settings. Make sure that you are using the Java 9-ea (Early Access) SDK and that the language level is set to Java 9 as well. ",[651,17783,17784],{},[660,17785],{"alt":17786,"src":17787},"IntelliJ Java 9 Support","./2017-04-17_10-02-29-1024x602.png",[4542,17789,17791],{"id":17790},"getting-started-with-java-9-screencast","Getting Started with Java 9 Screencast",[651,17793,17794],{},"I created a screencast that goes through the information we mentioned in this article. If you're not subscribed to my YouTube channel please do so now so that you can get these videos before they hit my blog. ",[651,17796,17797],{},[812,17798,17799],{"href":17799,"rel":17800},"https://www.youtube.com/watch?v=D-O7eDbdcKg",[816],[4542,17802,9042],{"id":9041},[651,17804,17805,17806,17811,17812,15266],{},"This is a great time to start playing with the new features in Java 9. I am working on a curriculum for a ",[2939,17807,17808],{},[7300,17809,17810],{},"What's new in Java 9 Course",". If you would be interested in learning more about that please ",[812,17813,17816],{"href":17814,"rel":17815},"https://danvega.dev/java-9",[816],"head over to the course landing page",[651,17818,17819],{},[7300,17820,17821,17823],{},[2939,17822,11650],{}," What are you most looking forward to in Java 9?",{"title":674,"searchDepth":790,"depth":790,"links":17825},[17826,17827,17828,17829,17830],{"id":17688,"depth":790,"text":17689},{"id":17738,"depth":790,"text":17739},{"id":17777,"depth":790,"text":17778},{"id":17790,"depth":790,"text":17791},{"id":9041,"depth":790,"text":9042},{"slug":17832,"published":797,"date":17833,"tags":17834,"cover":17835},"java-9","2017-04-17T11:05:58-04:00",[4109],"./Java9-760x428.png",{"title":207,"description":207},"blog/2017/04/17/java-9","foePXzQevd0CABAfyi3RPWhe5KgRQtDskMDyhiY5E3k",{"id":17840,"title":204,"body":17841,"description":204,"extension":793,"meta":18032,"navigation":797,"path":205,"seo":18036,"stem":18037,"__hash__":18038},"content/blog/2017/04/19/what-is-jhipster.md",{"type":648,"value":17842,"toc":18022},[17843,17868,17871,17882,17886,17889,17900,17904,17917,17937,17940,17946,17950,17953,17959,17963,17966,17972,17976,17979,17984,17992,17996,17999,18005,18012,18014,18017],[651,17844,17845,17846,17851,17852,17857,17858,17861,17862,17867],{},"In today's post, I want to talk to you about one of my favorite open source projects around, ",[812,17847,17850],{"href":17848,"rel":17849},"https://jhipster.github.io/",[816],"JHipster",". In short, JHipster is a ",[812,17853,17856],{"href":17854,"rel":17855},"http://yeoman.io/",[816],"Yeoman Generator"," used to create a ",[812,17859,7077],{"href":7075,"rel":17860},[816]," and ",[812,17863,17866],{"href":17864,"rel":17865},"https://angular.io/",[816],"Angular"," project. When you get past building sample and demo projects there is a lot of different technologies and stacks that go into building a scalable, complete and modern web application.",[651,17869,17870],{},"The goal of the project is to generate for you a complete and modern Web app, unifying:",[5316,17872,17873,17876,17879],{},[5332,17874,17875],{},"A high-performance and robust Java stack on the server side with Spring Boot",[5332,17877,17878],{},"A sleek, modern, mobile-first front-end with Angular and Bootstrap",[5332,17880,17881],{},"A powerful workflow to build your application with Yeoman, Webpack/Gulp and Maven/Gradle",[4542,17883,17885],{"id":17884},"what-is-jhipster","What is JHipster",[651,17887,17888],{},"JHipster in simple terms is a way to generate a project around some of my favorite technologies. There are generally 3 sides of a story in every modern web application.",[5316,17890,17891,17894,17897],{},[5332,17892,17893],{},"What does the server side stack look like?",[5332,17895,17896],{},"What does the front end stack look like? ",[5332,17898,17899],{},"How do we deploy our application? ",[5909,17901,17903],{"id":17902},"server-side","Server Side",[651,17905,17906,17907,17910,17911,17916],{},"If you follow me at all you know that I am a huge fan of ",[812,17908,7077],{"href":7075,"rel":17909},[816],", so much so that ",[812,17912,17915],{"href":17913,"rel":17914},"http://courses.danvega.dev/p/spring-boot-intro",[816],"I teach a course on it",". It isn't enough to just understand Spring Boot though when it comes to building complete and modern web applications. When we begin building the back end we start asking questions like:",[5316,17918,17919,17922,17925,17928,17931,17934],{},[5332,17920,17921],{},"What does the security model look like?",[5332,17923,17924],{},"What does the data layer look like?",[5332,17926,17927],{},"Will we need a distributed caching system?",[5332,17929,17930],{},"Is this application scalable? ",[5332,17932,17933],{},"How can we provide API documentation?",[5332,17935,17936],{},"How can we make sure that we are testing our application and that we have sufficient code coverage?",[651,17938,17939],{},"When we start looking at the Server Side Options we are talking about technologies like these. ",[651,17941,17942],{},[660,17943],{"alt":17944,"src":17945},"Jhipster Server Side","./2017-04-19_09-04-28.png",[5909,17947,17949],{"id":17948},"front-end","Front End",[651,17951,17952],{},"We spent a lot of time thinking about the back end of the project but now we need to make sure it's functional. Angular is one of the most popular front-end technologies around but it doesn't come without some confusion. There are all these different versions and they seem drastically different. Lucky for you JHipster is going to support all of them. We also want to create a web application that looks good using the latest technologies in HTML 5, CSS 3 & Bootstrap. There are also build tools on the front end to think about. How are we going to test that front end code? When we think about all of these technologies these are our options on the front end. ",[651,17954,17955],{},[660,17956],{"alt":17957,"src":17958},"Jhipster Frontend","./2017-04-19_09-08-48.png",[5909,17960,17962],{"id":17961},"deployment-options","Deployment Options",[651,17964,17965],{},"Now that we have our application built we need to deploy it. JHipster gives us a bunch of options for easily deploying our applications. ",[651,17967,17968],{},[660,17969],{"alt":17970,"src":17971},"Jhipster Deployment","./2017-04-19_09-07-05.png",[4542,17973,17975],{"id":17974},"why-jhipster","Why JHipster",[651,17977,17978],{},"I hope you are starting to see why JHipster is needed. Sure you could go out and piece together all of these technologies but why on earth would you want to do that. The number of hours it would take to make all of those play together nicely would blow you away. That doesn't sound like fun at all so I will stick to using JHipster. I fire up a command line and type \"yo jhipster\" and I am presented with a list of options of how my application is going to look. ",[651,17980,17981],{},[660,17982],{"alt":17850,"src":17983},"./2017-04-19_09-22-57-1024x567.png",[651,17985,17986,17987,664],{},"If you want to see what a sample JHipster application looks like without installing it you can ",[812,17988,17991],{"href":17989,"rel":17990},"https://github.com/jhipster/jhipster-sample-app",[816],"check it out here",[4542,17993,17995],{"id":17994},"upcoming-course","Upcoming Course",[651,17997,17998],{},"I want all of you reading this to that I love this project so much that I have decided to launch a course on it. If you want to find out more about this please click on the link below. ",[651,18000,18001],{},[660,18002],{"alt":18003,"src":18004},"Spring Boot & Angular 2 Course","./jhipster_course.png",[651,18006,18007],{},[812,18008,18011],{"href":18009,"rel":18010},"https://danvega.dev/jhipster",[816],"Spring Boot & Angular Course",[4542,18013,9042],{"id":9041},[651,18015,18016],{},"We will get into how to install JHipster in future posts and how to use it but I wanted to start off here with a gentle introduction to the project. If you are using JHipster I want to hear from you. Please let me know what questions you have in building Spring & Angular applications. ",[651,18018,13453,18019,18021],{},[2939,18020,11650],{}," What are some of your favorite open source projects? _",{"title":674,"searchDepth":790,"depth":790,"links":18023},[18024,18029,18030,18031],{"id":17884,"depth":790,"text":17885,"children":18025},[18026,18027,18028],{"id":17902,"depth":892,"text":17903},{"id":17948,"depth":892,"text":17949},{"id":17961,"depth":892,"text":17962},{"id":17974,"depth":790,"text":17975},{"id":17994,"depth":790,"text":17995},{"id":9041,"depth":790,"text":9042},{"slug":17884,"published":797,"date":18033,"tags":18034,"cover":18035},"2017-04-19T09:38:31-04:00",[4109,7055],"./wallpaper-001-2560x1440-760x428.png",{"title":204,"description":204},"blog/2017/04/19/what-is-jhipster","qQhQX8j5fwvd7GdZmnRDyrSPHn8iizhRwsu8eW-QpfA",{"id":18040,"title":201,"body":18041,"description":201,"extension":793,"meta":18126,"navigation":797,"path":202,"seo":18131,"stem":18132,"__hash__":18133},"content/blog/2017/04/21/how-do-you-define-success.md",{"type":648,"value":18042,"toc":18117},[18043,18046,18050,18053,18059,18063,18066,18070,18073,18077,18080,18086,18090,18093,18099,18102,18104,18112],[651,18044,18045],{},"I had a personal situation that transpired yesterday and while I won't get into the details of that here, It did inspire me to write this article. What I do want to share with you is the amazing advice a person in a position of leadership gave to a friend. There was one person involved that was forced into questioning where they were in their career and whether or not they were successful. The person on the other end of the conversation gave some excellent advice and this is what you can expect from great leaders. He asked, \"How do you define success?\". This is a fantastic question and I want to dive into this today from a Software Engineer perspective.",[4542,18047,18049],{"id":18048},"my-background","My Background",[651,18051,18052],{},"I want to give you a little background on me so you can understand where my perspective comes from. I am going to date myself a little but I am currently 38 years old. When I graduated high school way back in 1996 I got my first computer. I quickly fell in love with this machine and when It would break I would buy parts for it and learned how to fix it myself. I knew within a year of owning that thing that I was destined for a career in computers, but I had no idea what that looked like or how I was going to get there. ",[651,18054,18055],{},[660,18056],{"alt":18057,"src":18058},"Dell Old Days","./Dell_XPS_T600R-223x300.jpg",[5909,18060,18062],{"id":18061},"education","Education",[651,18064,18065],{},"I was a pretty solid student in high school and math was by far my favorite subject. Like most students, I could have done much better if I applied myself but I just wasn't interested. When I was 18 I was living on my own and working full time as a landscaper. I know that I didn't have the means or desire to go to a 4-year college. I could learn a whole ton about computers there but I also realized that I would learn a lot of stuff I didn't care about and when I was finished I would be swimming in a pile of debt. So I opted to go the 2-year technical college route and I think it was a smart one. I would work during the day and go to school at night for 4 hours 4 days a week. Looking back on that it was some pretty amazing dedication and hard work on my part. ",[5909,18067,18069],{"id":18068},"career","Career",[651,18071,18072],{},"While I was going to school I started teaching myself web development. My very first \"customer\" was a friend of mine who owned his own business. I built a full-scale e-commerce application from scratch. For my second gig, in the most unlikely corners of the internet, a chat room, I picked up a pretty large gig for an online luggage company that needed a new eCommerce solution. I didn't realize it then but I discovered an important concept of education at that age. You aren't going to learn everything you can from a book or an instructor. You have to take what you learn and apply it to the real world. I don't consider the technical school a success but the initiative I took during those years was one. The next 15 - 18 years of my life was spent moving from one great company to the next. They all played a vital role in my career development and I am thankful for all of them. What I do remember about this time period and even as I sat here today writing this article is that I never lost my thirst for knowledge. ",[4542,18074,18076],{"id":18075},"what-can-i-do","What can I do? ",[651,18078,18079],{},"So what can you do as an aspiring developer? You are going to need some combination of schooling and self-taught education. If you think a 4-year college is right for you I am not one to talk someone out of an education. What I would say is make sure your vision of the end goal aligns with what you're going to school for. To me, this means that if you want to be an iOS developer a 4-year college doesn't make sense. You can accomplish this in far less time for far less money. If you want to study computer science at Harvard as well as a business because you're going to run the next facebook, then, by all means, look into that. There are great Bootcamp style school's popping up everywhere. A Bootcamp style school offers to teach you a certain skill, like \"Learn how to program in Java in 14 weeks\". These are focused classes and if they were around when I was getting into programming I would have jumped all over this. If you're in the Cleveland or Columbus area I would highly recommend you check out ",[651,18081,18082],{},[812,18083,18084],{"href":18084,"rel":18085},"http://www.techelevator.com",[816],[4542,18087,18089],{"id":18088},"success","Success ",[651,18091,18092],{},"So now we come full circle back to the question of \"How do we define success as a Software Developer?\". When you look back on your career are you going to consider it a success because of the amount of money you made? Will you define success because of what your friends and family think you have accomplished? For me, success is always going to be defined as \"Are you happy?\". I can tell you that from a career perspective (It's not over yet) that I am very happy. I'm happy because I have busted my ass and I am continually learning to improve my craft. Happiness to me is remembering people, even myself, telling me that I couldn't become a Software Developer. I am happy because once I started believing in myself I accomplished something that I set out to do. I get that being a programmer is glamorous but ever since I started playing with that first computer I knew this is where I wanted to be. ",[651,18094,18095],{},[660,18096],{"alt":18097,"src":18098},"Success","./william-stitt-111353-1024x683.jpg",[651,18100,18101],{},"If you're parents, friends or family don't think of you as a success because you're not working at Google or Facebook don't listen to them. If your goal is to work at Google or Facebook then bust your tail and make it happen. Last but not least and I can't stress this enough, is money. Too many people get caught up in the notion that success is defined by your paycheck. I know plenty of people who make a ton of money and are miserable. If your goal is to make more money you can certainly achieve that goal but don't let that be the deciding factor in your pursuit of your career goals. ",[4542,18103,9042],{"id":9041},[651,18105,18106,18107,15266],{},"At the end of the day that leaders advice was just what my friend needed to hear. It reminded them of all the opportunities they do have and not of the ones they don't. It reminded them of how happy they are on a day to day basis and how important those things are in life. If you are passionate about what you do and you continue to learn there will always be a place for you. Software Developers are in demand which means you get to write your future however you see fit. Work your tail off, continue to educate yourself and enjoy what you do. If you do these things, you can wake up every morning with a smile on your face... ",[7300,18108,18109],{},[2939,18110,18111],{},"And life is too short not to smile to my friends",[651,18113,13453,18114,18116],{},[2939,18115,11650],{}," What advice would you have for aspiring Software Developers? _",{"title":674,"searchDepth":790,"depth":790,"links":18118},[18119,18123,18124,18125],{"id":18048,"depth":790,"text":18049,"children":18120},[18121,18122],{"id":18061,"depth":892,"text":18062},{"id":18068,"depth":892,"text":18069},{"id":18075,"depth":790,"text":18076},{"id":18088,"depth":790,"text":18089},{"id":9041,"depth":790,"text":9042},{"slug":18127,"published":797,"date":18128,"tags":18129,"cover":18130},"how-do-you-define-success","2017-04-21T10:00:07-04:00",[11030],"./william-stitt-224301-760x1140.jpg",{"title":201,"description":201},"blog/2017/04/21/how-do-you-define-success","3NM1cmMcxVrYecPkGCdt_bGUqoflkQ0YZHAX6w16jok",{"id":18135,"title":198,"body":18136,"description":198,"extension":793,"meta":18393,"navigation":797,"path":199,"seo":18398,"stem":18399,"__hash__":18400},"content/blog/2017/04/24/spring-boot-2-0-roadmap.md",{"type":648,"value":18137,"toc":18375},[18138,18141,18145,18160,18165,18168,18174,18177,18198,18207,18211,18214,18218,18227,18231,18239,18243,18246,18252,18256,18264,18268,18273,18277,18290,18294,18297,18305,18308,18316,18319,18327,18330,18334,18337,18341,18349,18354,18363,18365,18368],[651,18139,18140],{},"I am a huge fan of open source projects but I love a well-maintained project even more. I like to know when milestones are scheduled to be released and what are some of the features we can expect in them. The Spring Boot team does a very good job of making this information available to the public. Thanks to that we have a pretty good idea of when Spring Boot 2.0 is going to land and some of the features that it will include. In this article, I want to talk about that roadmap and share with you a pretty big announcement. ",[4542,18142,18144],{"id":18143},"spring-boot-20-milestones","Spring Boot 2.0 Milestones",[651,18146,18147,18148,18153,18154,18159],{},"The current date is April 24, 2017, just so you understand my perspective looking at this roadmap. Obviously, if you look at it months from now it might look a little different so please keep that in mind. If you head over to the ",[812,18149,18152],{"href":18150,"rel":18151},"https://github.com/spring-projects/spring-boot/",[816],"Github project page"," you will have access to all of the source code as well as the readme to get you started. If you click on the issues tab though and then ",[812,18155,18158],{"href":18156,"rel":18157},"https://github.com/spring-projects/spring-boot/milestones",[816],"click on milestones"," you will be looking at something that looks like this. ",[651,18161,18162],{},[660,18163],{"alt":7077,"src":18164},"./2017-04-21_17-00-48-300x276.png",[651,18166,18167],{},"What you are looking at here is a list of milestone releases. Clicking on the release, in this case, 2.0.0.M1 you will be shown a list of all that tasks that belong to that milestone. ",[651,18169,18170],{},[660,18171],{"alt":18172,"src":18173},"Spring Boot Milestones","./2017-04-21_17-03-39-300x269.png",[651,18175,18176],{},"From here you can see that there is a due date of May 2 and that it is 72% completed. This gives you some awesome insight into what the Spring Boot team is working on in each release and how far along they are. The big milestones that I care about are the release candidates (RC) because this means we are getting real close to the actual release. ",[5316,18178,18179,18187,18195],{},[5332,18180,18181,18186],{},[812,18182,18185],{"href":18183,"rel":18184},"https://github.com/spring-projects/spring-boot/milestone/56",[816],"2.0.0.RC1"," (October 23, 2017)",[5332,18188,18189,18194],{},[812,18190,18193],{"href":18191,"rel":18192},"https://github.com/spring-projects/spring-boot/milestone/80",[816],"2.0.0.RC2"," (November 9, 2017) ",[5332,18196,18197],{},"2.0.0.RELEASE (December 4th???)",[651,18199,18200,18201,18206],{},"If we look at this timeline and then take a look at what is around the corner can take a good guess at when Spring 2.0 will be released. The ",[812,18202,18205],{"href":18203,"rel":18204},"https://springoneplatform.io/",[816],"SpringOne conference"," takes place in San Francisco on December 4th. It is safe to assume we will probably see the release the night before or on December 4th. So, if you haven't already booked your ticket for SpringOne you might want to start thinking about doing so soon.",[4542,18208,18210],{"id":18209},"spring-20-features","Spring 2.0 Features",[651,18212,18213],{},"Please remember that I am not on the Spring Boot Team and anything written here is purely based off of information I found. This means that something might change by the time we actually get to the release. I am pulling this information from the milestones, release notes and anything that I can find on the interwebs. ",[5909,18215,18217],{"id":18216},"spring-framework-50","Spring Framework 5.0",[651,18219,18220,18221,18226],{},"Spring Boot 2.0 builds on and requires Spring Framework 5.0. There are a number of nice refinements in Spring Framework 5.0 including extensive support for building reactive applications. Please refer to the ",[812,18222,18225],{"href":18223,"rel":18224},"https://github.com/spring-projects/spring-framework/wiki/What%E2%80%99s-New-in-the-Spring-Framework#whats-new-in-spring-framework-5x",[816],"Spring Framework Wiki"," for details. This is probably the biggest feature of the release and I know many of use have been looking forward to this release for awhile now. Spring Framework 5.0 brings with it a bunch of new features but none bigger than their Reactive initiative. ",[5909,18228,18230],{"id":18229},"java-8-baseline","Java 8 Baseline",[651,18232,18233,18234,15266],{},"Spring Boot 2.0 is going to require that you run Java 8. This means that Java 6 & 7 will no longer be supported. My guess is that one of the 1.5.x releases will be the last version to support this. I also am excited that ",[812,18235,18238],{"href":18236,"rel":18237},"https://github.com/spring-projects/spring-boot/issues/7226",[816],"Java 9 is going to be supported as well",[5909,18240,18242],{"id":18241},"new-gradle-plugin","New Gradle Plugin",[651,18244,18245],{},"Spring Boot’s Gradle plugin no longer automatically applies the dependency management plugin. Instead, Spring Boot’s plugin now reacts to the dependency management plugin being applied by importing the correct version of the spring-boot-dependencies bom. This gives you more control over how and when dependency management is configured.",[651,18247,18248],{},[812,18249,18250],{"href":18250,"rel":18251},"https://spring.io/blog/2017/04/05/spring-boot-s-new-gradle-plugin",[816],[5909,18253,18255],{"id":18254},"thymeleaf-3","Thymeleaf 3",[651,18257,18258,18259,664],{},"Spring Boot 2.0 will now support Thymeleaf 3. This will be an update to both the Thymeleaf & Spring Security Thymeleaf dependencies. If you want to read more about Thymeleaf 3 you can do so ",[812,18260,18263],{"href":18261,"rel":18262},"http://www.thymeleaf.org/doc/articles/thymeleaf3migration.html",[816],"here",[5909,18265,18267],{"id":18266},"conditionalonbean","@ConditionalOnBean",[651,18269,18270,18272],{},[676,18271,18267],{}," now uses a logical AND rather than a logical OR when determining whether or not the condition has been met.",[5909,18274,18276],{"id":18275},"junit-5","JUnit 5",[651,18278,18279,18280,18283,18284,18289],{},"This is probably a huge ",[7300,18281,18282],{},"maybe"," at this point. There is a ",[812,18285,18288],{"href":18286,"rel":18287},"https://github.com/spring-projects/spring-boot/issues/6402",[816],"task to investigate"," if an upgrade to JUnit 5 makes sense. ",[5909,18291,18293],{"id":18292},"jetty","Jetty",[651,18295,18296],{},"The minimum supported version of Jetty is now 9.4",[5909,18298,18300],{"id":18299},"tomcat",[812,18301,18304],{"href":18302,"rel":18303},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#tomcat",[816],"Tomcat",[651,18306,18307],{},"The minimum supported version of Tomcat is now 8.5",[5909,18309,18311],{"id":18310},"hibernate",[812,18312,18315],{"href":18313,"rel":18314},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#hibernate",[816],"Hibernate",[651,18317,18318],{},"The minimum supported version of Hibernate is now 5.2",[5909,18320,18322],{"id":18321},"gradle",[812,18323,18326],{"href":18324,"rel":18325},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#gradle",[816],"Gradle",[651,18328,18329],{},"The minimum supported version of Gradle is now 3.4",[5909,18331,18333],{"id":18332},"bug-fixes-feature-enhancements","Bug Fixes & Feature Enhancements",[651,18335,18336],{},"There is plenty more to come and I will try and keep this article up to date as I find them.",[4542,18338,18340],{"id":18339},"spring-20-course","Spring 2.0 Course",[651,18342,18343,18344,18348],{},"If you haven't already had a chance to check out my ",[812,18345,18347],{"href":17913,"rel":18346},[816],"Spring Boot Introduction course"," please do. This course is based on Spring Boot 1.3 and was very much an introduction. My announcement today is that a Spring 2.0 course is coming and I will be releasing it sometime around SpringOne. ",[651,18350,18351],{},[660,18352],{"alt":18340,"src":18353},"./627032_1fbe_7.jpg",[651,18355,18356,18357,18362],{},"This is not just going to be a repeat of the previous course with a few new features. First off, I am going to create 2 free courses for setting up your development environment on both Windows 10 and Mac OS. Next, I will incorporate the feedback I have received from my current students. My current course is the best selling course on one platform and I know there is tons of room for improvement. These courses will allow me to extract that portion of the course that some find necessary and others find boring. These courses will be bundled for free into the Spring Boot 2.0 course. I am working on the curriculum now but if you would like to receive updates check out my ",[812,18358,18361],{"href":18359,"rel":18360},"https://danvega.dev/spring-boot-2-0",[816],"Spring Boot 2.0 Course Page"," and signup for updates. Anyone on this list will be the first to find out when it's released and will receive a discount. ",[4542,18364,9042],{"id":9041},[651,18366,18367],{},"A lot of exciting things happening around Spring Boot 2.0. You have the release itself, The Spring One Conference and now a course to along with it. Invest in yourself and some pretty amazing things can happen. Take care friends! ",[651,18369,18370],{},[7300,18371,18372,18374],{},[2939,18373,11650],{}," What are you looking forward to in Spring Framework 5.0 & Spring Boot 2.0?",{"title":674,"searchDepth":790,"depth":790,"links":18376},[18377,18378,18391,18392],{"id":18143,"depth":790,"text":18144},{"id":18209,"depth":790,"text":18210,"children":18379},[18380,18381,18382,18383,18384,18385,18386,18387,18388,18389,18390],{"id":18216,"depth":892,"text":18217},{"id":18229,"depth":892,"text":18230},{"id":18241,"depth":892,"text":18242},{"id":18254,"depth":892,"text":18255},{"id":18266,"depth":892,"text":18267},{"id":18275,"depth":892,"text":18276},{"id":18292,"depth":892,"text":18293},{"id":18299,"depth":892,"text":18304},{"id":18310,"depth":892,"text":18315},{"id":18321,"depth":892,"text":18326},{"id":18332,"depth":892,"text":18333},{"id":18339,"depth":790,"text":18340},{"id":9041,"depth":790,"text":9042},{"slug":18394,"published":797,"date":18395,"tags":18396,"cover":18397},"spring-boot-2-0-roadmap","2017-04-24T08:00:05-04:00",[7055],"./annie-spratt-161511-760x1140.jpg",{"title":198,"description":198},"blog/2017/04/24/spring-boot-2-0-roadmap","OqbdsWKzCbmkGXnms5Jh2e-nF2kP_eDKH2M3GPjk6nI",{"id":18402,"title":195,"body":18403,"description":195,"extension":793,"meta":19003,"navigation":797,"path":196,"seo":19008,"stem":19009,"__hash__":19010},"content/blog/2017/04/26/what-is-going-wrong-on-the-spring-boot-view-layer.md",{"type":648,"value":18404,"toc":18996},[18405,18408,18417,18421,18424,18430,18434,18441,18533,18540,18547,18603,18606,18610,18613,18643,18646,18650,18653,18675,18678,18780,18783,18971,18974,18980,18983,18985,18988,18993],[651,18406,18407],{},"Today we are going to dive into a question from a student of mine. It may seem obvious to some people what is happening but unless you really understand what is going on in the Spring Boot View Layer It can be a little bit confusing.",[1004,18409,18410],{},[651,18411,18412,18413,18416],{},"Good evening, Mr. Vega. How do you do? My name is Fabio and have a doubt about Spring Boot's behavior. So, I created an index.html file under the static folder (is index.html or any HTML file for that matter what you call a template? Is it just another way to refer to HTML files?) right after importing the project generated by the Initializer and ran the application, with the expected result (the application found and displayed the index). But when I moved on to creating a controller class, with the mapping of the root page, all of a sudden the application couldn't find the HTML anymore, issuing an error message to the console to this effect: ",[676,18414,18415],{},"org.thymeleaf.exceptions.TemplateInputException: Error resolving template \"index\", the template might not exist or might not be accessible by any of the configured Template Resolvers at org.thymeleaf.TemplateRepository.getTemplate(TemplateRepository.java:246) ~[thymeleaf-2.1.5.RELEASE.jar:2.1.5.RELEASE]"," When I move it to the templates folder, suddenly the application finds it. It's as if when I try without a controller, Spring calls the shots, who looks for it under the static folder; and when I have a controller, it falls to Thymeleaf, who looks for it under templates. What's going on?",[4542,18418,18420],{"id":18419},"spring-bootresources","Spring Boot Resources",[651,18422,18423],{},"When you create a brand new Spring Boot Project you will see a resources folder. In that folder, you will have 2 folders (static and templates) that are the subject of this discussion. The static folder is used for static content such as HTML, JavaScript, CSS, Images, etc... The templates folder will be used for template engines to serve dynamic content.",[651,18425,18426],{},[660,18427],{"alt":18428,"src":18429},"Spring Boot Resources","./2017-04-26_07-44-17.png",[5909,18431,18433],{"id":18432},"spring-boot-static-content","Spring Boot Static Content",[651,18435,18436,18437,18440],{},"By default, Spring Boot will serve static content from a directory called ",[676,18438,18439],{},"/static"," (or /public, /resources, /META-INF/resources) in the classpath or from the root of the ServletContext. If you place an index.html in the static folder this will become the homepage for the entire application. ",[669,18442,18444],{"className":4496,"code":18443,"language":4498,"meta":674,"style":674},"\u003Chtml>\n\u003Chead>\n \u003Ctitle>My Spring Boot Application\u003C/title>\n\u003C/head>\n\u003Cbody>\n \u003Ch1>Welcome to my Application\u003C/h1>\n \u003Cp>This is my default index.html page.\u003C/p>\n\u003C/body>\n\u003C/html>\n",[676,18445,18446,18454,18462,18475,18483,18491,18504,18517,18525],{"__ignoreMap":674},[679,18447,18448,18450,18452],{"class":681,"line":682},[679,18449,4505],{"class":693},[679,18451,4498],{"class":4508},[679,18453,4519],{"class":693},[679,18455,18456,18458,18460],{"class":681,"line":790},[679,18457,4505],{"class":693},[679,18459,11741],{"class":4508},[679,18461,4519],{"class":693},[679,18463,18464,18466,18468,18471,18473],{"class":681,"line":892},[679,18465,4524],{"class":693},[679,18467,11750],{"class":4508},[679,18469,18470],{"class":693},">My Spring Boot Application\u003C/",[679,18472,11750],{"class":4508},[679,18474,4519],{"class":693},[679,18476,18477,18479,18481],{"class":681,"line":901},[679,18478,4586],{"class":693},[679,18480,11741],{"class":4508},[679,18482,4519],{"class":693},[679,18484,18485,18487,18489],{"class":681,"line":909},[679,18486,4505],{"class":693},[679,18488,3006],{"class":4508},[679,18490,4519],{"class":693},[679,18492,18493,18495,18497,18500,18502],{"class":681,"line":918},[679,18494,4524],{"class":693},[679,18496,11859],{"class":4508},[679,18498,18499],{"class":693},">Welcome to my Application\u003C/",[679,18501,11859],{"class":4508},[679,18503,4519],{"class":693},[679,18505,18506,18508,18510,18513,18515],{"class":681,"line":935},[679,18507,4524],{"class":693},[679,18509,651],{"class":4508},[679,18511,18512],{"class":693},">This is my default index.html page.\u003C/",[679,18514,651],{"class":4508},[679,18516,4519],{"class":693},[679,18518,18519,18521,18523],{"class":681,"line":944},[679,18520,4586],{"class":693},[679,18522,3006],{"class":4508},[679,18524,4519],{"class":693},[679,18526,18527,18529,18531],{"class":681,"line":959},[679,18528,4586],{"class":693},[679,18530,4498],{"class":4508},[679,18532,4519],{"class":693},[651,18534,18535,18536,18539],{},"We don't have to do anything else at all to our application. If we go ahead and run this demo and visit ",[812,18537,11697],{"href":11697,"rel":18538},[816]," we should see the following page. ",[651,18541,18542,18546],{},[660,18543],{"alt":18544,"src":18545},"Basic Welcome Page","./2017-04-26_07-59-29.png"," If you look in the console you will also see the following log statement. ",[669,18548,18550],{"className":5851,"code":18549,"language":5853,"meta":674,"style":674},"2017-04-26 07:59:07.489 INFO 94384 --- [ restartedMain] oConfiguration$WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]\n",[676,18551,18552],{"__ignoreMap":674},[679,18553,18554,18557,18560,18563,18566,18569,18572,18575,18578,18581,18583,18586,18589,18592,18594,18597,18600],{"class":681,"line":682},[679,18555,18556],{"class":880},"2017-04-26",[679,18558,18559],{"class":689}," 07:59:07.489",[679,18561,18562],{"class":689}," INFO",[679,18564,18565],{"class":931}," 94384",[679,18567,18568],{"class":931}," ---",[679,18570,18571],{"class":693}," [ ",[679,18573,18574],{"class":689},"restartedMain]",[679,18576,18577],{"class":689}," oConfiguration",[679,18579,18580],{"class":693},"$WelcomePageHandlerMapping ",[679,18582,2391],{"class":689},[679,18584,18585],{"class":689}," Adding",[679,18587,18588],{"class":689}," welcome",[679,18590,18591],{"class":689}," page:",[679,18593,4512],{"class":689},[679,18595,18596],{"class":689}," path",[679,18598,18599],{"class":689}," resource",[679,18601,18602],{"class":693}," [static/index.html]\n",[651,18604,18605],{},"So if we refer back to the students question this is exactly why it worked for him the first time. Also, don't think of this as just a single boring HTML file. As you will see in the coming weeks on this blog you can drop an entire Angular application in that folder if you like and use that as the front-end to your application. ",[5909,18607,18609],{"id":18608},"spring-boot-dynamic-content","Spring Boot Dynamic Content",[651,18611,18612],{},"As well as REST web services, you can also use Spring MVC to serve dynamic HTML content. Spring MVC supports a variety of templating technologies including Thymeleaf, FreeMarker, and JSPs. Many other templating engines also ship their own Spring MVC integrations. Spring Boot includes auto-configuration support for the following templating engines:",[5316,18614,18615,18622,18629,18636],{},[5332,18616,18617],{},[812,18618,18621],{"href":18619,"rel":18620},"http://freemarker.org/docs/",[816],"FreeMarker",[5332,18623,18624],{},[812,18625,18628],{"href":18626,"rel":18627},"http://docs.groovy-lang.org/docs/next/html/documentation/template-engines.html#_the_markuptemplateengine",[816],"Groovy",[5332,18630,18631],{},[812,18632,18635],{"href":18633,"rel":18634},"http://www.thymeleaf.org/",[816],"Thymeleaf",[5332,18637,18638],{},[812,18639,18642],{"href":18640,"rel":18641},"https://mustache.github.io/",[816],"Mustache",[651,18644,18645],{},"When you’re using one of these templating engines with the default configuration, your templates will be picked up automatically from src/main/resources/templates.",[5259,18647,18649],{"id":18648},"spring-boot-controller","Spring Boot Controller",[651,18651,18652],{},"If we want to create a controller and add some dynamic content to our home we can do that as well. For this demo, I am going to use Thymeleaf so I have added the appropriate dependency to my pom.xml",[669,18654,18656],{"className":9101,"code":18655,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-thymeleaf\u003C/artifactId>\n\u003C/dependency>\n",[676,18657,18658,18662,18666,18671],{"__ignoreMap":674},[679,18659,18660],{"class":681,"line":682},[679,18661,9110],{},[679,18663,18664],{"class":681,"line":790},[679,18665,9115],{},[679,18667,18668],{"class":681,"line":892},[679,18669,18670],{}," \u003CartifactId>spring-boot-starter-thymeleaf\u003C/artifactId>\n",[679,18672,18673],{"class":681,"line":901},[679,18674,9125],{},[651,18676,18677],{},"Here we create a controller class and a request mapping for \"/\". We are going to add a model attribute here called page title that will be pushed down to our view. ",[669,18679,18681],{"className":4107,"code":18680,"language":4109,"meta":674,"style":674},"package com.therealdanvega.controller;\n\n// imports\n\n@Controller\npublic class HomeController {\n @RequestMapping(\"/\")\n public String home(Model model){\n model.addAttribute(\"pageTitle\",\"Welcome to my Awesome Dynamic Application\");\n return \"index\";\n }\n}\n",[676,18682,18683,18689,18693,18698,18702,18708,18719,18731,18745,18763,18772,18776],{"__ignoreMap":674},[679,18684,18685,18687],{"class":681,"line":682},[679,18686,2543],{"class":685},[679,18688,12409],{"class":693},[679,18690,18691],{"class":681,"line":790},[679,18692,889],{"emptyLinePlaceholder":797},[679,18694,18695],{"class":681,"line":892},[679,18696,18697],{"class":1400},"// imports\n",[679,18699,18700],{"class":681,"line":901},[679,18701,889],{"emptyLinePlaceholder":797},[679,18703,18704,18706],{"class":681,"line":909},[679,18705,4116],{"class":693},[679,18707,9942],{"class":685},[679,18709,18710,18712,18714,18717],{"class":681,"line":918},[679,18711,6073],{"class":685},[679,18713,4512],{"class":685},[679,18715,18716],{"class":880}," HomeController",[679,18718,884],{"class":693},[679,18720,18721,18723,18725,18727,18729],{"class":681,"line":935},[679,18722,6872],{"class":693},[679,18724,9275],{"class":685},[679,18726,745],{"class":693},[679,18728,10032],{"class":689},[679,18730,1339],{"class":693},[679,18732,18733,18735,18737,18739,18741,18743],{"class":681,"line":944},[679,18734,6089],{"class":685},[679,18736,9289],{"class":693},[679,18738,12642],{"class":880},[679,18740,10045],{"class":693},[679,18742,10048],{"class":2099},[679,18744,9533],{"class":693},[679,18746,18747,18749,18751,18753,18756,18758,18761],{"class":681,"line":959},[679,18748,10061],{"class":693},[679,18750,10064],{"class":880},[679,18752,745],{"class":693},[679,18754,18755],{"class":689},"\"pageTitle\"",[679,18757,1202],{"class":693},[679,18759,18760],{"class":689},"\"Welcome to my Awesome Dynamic Application\"",[679,18762,1208],{"class":693},[679,18764,18765,18767,18770],{"class":681,"line":964},[679,18766,9444],{"class":685},[679,18768,18769],{"class":689}," \"index\"",[679,18771,1186],{"class":693},[679,18773,18774],{"class":681,"line":977},[679,18775,985],{"class":693},[679,18777,18778],{"class":681,"line":982},[679,18779,996],{"class":693},[651,18781,18782],{},"As you can see from the return statement we are returning index. You don't need to add the HTML file extension here because based on the template engine we use the extension is configured and already known to Spring. This is our Thymeleaf template and should be located in /resources/templates/index.html",[669,18784,18786],{"className":4496,"code":18785,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\"\n xmlns=\"http://www.w3.org/1999/xhtml\"\n xmlns:th=\"http://www.thymleaf.org\">\n\u003Chead>\n \u003Cmeta charset=\"utf-8\"/>\n \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/>\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n \u003Ctitle>My Spring Boot Application\u003C/title>\n\u003C/head>\n\u003Cbody>\n \u003Ch1 th:text=\"${pageTitle}\">\u003C/h1>\n \u003Cp>This is my Thymeleaf template located at /resources/templates/index.html\u003C/p>\n\u003C/body>\n\u003C/html>\n",[676,18787,18788,18798,18811,18820,18832,18840,18855,18875,18895,18907,18915,18923,18942,18955,18963],{"__ignoreMap":674},[679,18789,18790,18792,18794,18796],{"class":681,"line":682},[679,18791,11904],{"class":693},[679,18793,11907],{"class":4508},[679,18795,11910],{"class":880},[679,18797,4519],{"class":693},[679,18799,18800,18802,18804,18807,18809],{"class":681,"line":790},[679,18801,4505],{"class":693},[679,18803,4498],{"class":4508},[679,18805,18806],{"class":880}," lang",[679,18808,686],{"class":693},[679,18810,11929],{"class":689},[679,18812,18813,18816,18818],{"class":681,"line":892},[679,18814,18815],{"class":880}," xmlns",[679,18817,686],{"class":693},[679,18819,11939],{"class":689},[679,18821,18822,18825,18827,18830],{"class":681,"line":901},[679,18823,18824],{"class":880}," xmlns:th",[679,18826,686],{"class":693},[679,18828,18829],{"class":689},"\"http://www.thymleaf.org\"",[679,18831,4519],{"class":693},[679,18833,18834,18836,18838],{"class":681,"line":909},[679,18835,4505],{"class":693},[679,18837,11741],{"class":4508},[679,18839,4519],{"class":693},[679,18841,18842,18844,18846,18848,18850,18852],{"class":681,"line":918},[679,18843,4524],{"class":693},[679,18845,11968],{"class":4508},[679,18847,11971],{"class":880},[679,18849,686],{"class":693},[679,18851,11976],{"class":689},[679,18853,18854],{"class":693},"/>\n",[679,18856,18857,18859,18861,18863,18865,18867,18869,18871,18873],{"class":681,"line":935},[679,18858,4524],{"class":693},[679,18860,11968],{"class":4508},[679,18862,11987],{"class":880},[679,18864,686],{"class":693},[679,18866,11992],{"class":689},[679,18868,11995],{"class":880},[679,18870,686],{"class":693},[679,18872,12000],{"class":689},[679,18874,18854],{"class":693},[679,18876,18877,18879,18881,18883,18885,18887,18889,18891,18893],{"class":681,"line":944},[679,18878,4524],{"class":693},[679,18880,11968],{"class":4508},[679,18882,5283],{"class":880},[679,18884,686],{"class":693},[679,18886,12015],{"class":689},[679,18888,11995],{"class":880},[679,18890,686],{"class":693},[679,18892,12022],{"class":689},[679,18894,18854],{"class":693},[679,18896,18897,18899,18901,18903,18905],{"class":681,"line":959},[679,18898,4524],{"class":693},[679,18900,11750],{"class":4508},[679,18902,18470],{"class":693},[679,18904,11750],{"class":4508},[679,18906,4519],{"class":693},[679,18908,18909,18911,18913],{"class":681,"line":964},[679,18910,4586],{"class":693},[679,18912,11741],{"class":4508},[679,18914,4519],{"class":693},[679,18916,18917,18919,18921],{"class":681,"line":977},[679,18918,4505],{"class":693},[679,18920,3006],{"class":4508},[679,18922,4519],{"class":693},[679,18924,18925,18927,18929,18931,18933,18936,18938,18940],{"class":681,"line":982},[679,18926,4524],{"class":693},[679,18928,11859],{"class":4508},[679,18930,12150],{"class":880},[679,18932,686],{"class":693},[679,18934,18935],{"class":689},"\"${pageTitle}\"",[679,18937,4563],{"class":693},[679,18939,11859],{"class":4508},[679,18941,4519],{"class":693},[679,18943,18944,18946,18948,18951,18953],{"class":681,"line":988},[679,18945,4524],{"class":693},[679,18947,651],{"class":4508},[679,18949,18950],{"class":693},">This is my Thymeleaf template located at /resources/templates/index.html\u003C/",[679,18952,651],{"class":4508},[679,18954,4519],{"class":693},[679,18956,18957,18959,18961],{"class":681,"line":993},[679,18958,4586],{"class":693},[679,18960,3006],{"class":4508},[679,18962,4519],{"class":693},[679,18964,18965,18967,18969],{"class":681,"line":2129},[679,18966,4586],{"class":693},[679,18968,4498],{"class":4508},[679,18970,4519],{"class":693},[651,18972,18973],{},"Running the application should produce the following. ",[651,18975,18976],{},[660,18977],{"alt":18978,"src":18979},"Spring Boot Application","./2017-04-26_08-52-04.png",[651,18981,18982],{},"_** Make sure you remove the static HTML file or that will overwrite everything as your home page. _",[4542,18984,9042],{"id":9041},[651,18986,18987],{},"This can be pretty confusing, especially to new Spring Boot developers. We can have an index.html file in both the static and templates folders. We just need to think about what type of content we are trying to serve and that might clear things up for everyone. ",[651,18989,13453,18990,18992],{},[2939,18991,11650],{}," What is your favorite view layer technology? _",[786,18994,18995],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":18997},[18998,19002],{"id":18419,"depth":790,"text":18420,"children":18999},[19000,19001],{"id":18432,"depth":892,"text":18433},{"id":18608,"depth":892,"text":18609},{"id":9041,"depth":790,"text":9042},{"slug":19004,"published":797,"date":19005,"tags":19006,"cover":19007},"what-is-going-wrong-on-the-spring-boot-view-layer","2017-04-26T09:02:28-04:00",[7055],"./view_layer-760x760.png",{"title":195,"description":195},"blog/2017/04/26/what-is-going-wrong-on-the-spring-boot-view-layer","N01eZpqu4UcffTcVW-yvUrx7Ol5PUD9khiJXrugwzcg",{"id":19012,"title":192,"body":19013,"description":192,"extension":793,"meta":19160,"navigation":797,"path":193,"seo":19166,"stem":19167,"__hash__":19168},"content/blog/2017/04/28/every-developer-start-blog-right-now.md",{"type":648,"value":19014,"toc":19147},[19015,19018,19022,19025,19029,19043,19048,19051,19055,19062,19066,19069,19075,19089,19093,19100,19104,19107,19111,19114,19118,19121,19127,19135,19137,19140],[651,19016,19017],{},"In this article, I want to dive into why I believe that every developer should start a blog right now. This will be a little bit of a bias opinion because I believe this is where both my career and my life's mission started. I realize that not everyone is walking the same path as I am in life. With that said I do see some real value in sharing your experiences as a developer with others. I am going to share my journey with you and give you the one thing I wish I would have known when I started blogging.",[4542,19019,19021],{"id":19020},"why-i-started-blogging","Why I started Blogging",[651,19023,19024],{},"I know that a lot of people start blogging because they want to have their voice heard. That wasn't the case for me when I started writing and it doesn't need to be your reason either.",[5909,19026,19028],{"id":19027},"blogcfc","BlogCFC",[651,19030,19031,19032,19037,19038,19042],{},"When I started I did it because I honestly wanted to learn how to build stuff. I looked up to a bunch of developers in the ColdFusion community and all of them all had blogs. One of those bloggers was ",[812,19033,19036],{"href":19034,"rel":19035},"https://www.raymondcamden.com/",[816],"Ray Camden"," and he had this cool open source blogging software called ",[812,19039,19028],{"href":19040,"rel":19041},"http://www.blogcfc.com/index.cfm",[816],". Below is a screenshot of the website that I would one day help Ray create. It might not look like much but I was really excited to be a part of an open source project. If you look closely you can see a screenshot of my old blog. ",[651,19044,19045],{},[660,19046],{"alt":19028,"src":19047},"./2017-04-28_07-21-20.png",[651,19049,19050],{},"So there was this community of bloggers and I really wanted to be one of them. I downloaded this open source blogging software and I learned how to install it locally, customize it and publish it to a production server. So not only was I now blogging but I was also involved in the community and a part of an open source project. This point of being involved in a community is a special one and we will dive into that deeper later in this article. I started writing in October 2005 and while my focus might have changed over the years my goal hasn't. I wanted to learn \"stuff\" and I wanted to understand it to the point where I could help other people understand it as well. This innate sense to want to help others ultimately led me to what I am doing today. This innate sense to lead and help others ultimately led me to what I am doing today. ",[4542,19052,19054],{"id":19053},"why-you-should-start-blogging","Why You Should Start Blogging",[651,19056,19057,19058,19061],{},"As I said at the beginning of this article I completely understand that everyone's goals are not the same as mine. I do believe there are some real good reasons for you starting your own blog and we will discuss those here. Before we jump into those I also want to stress that this isn't just for new developers. It is ",[2939,19059,19060],{},"never"," too late to start writing and be a part of a community so please don't let silly numbers like age or years on the job stop you from being a part of something great. ",[5909,19063,19065],{"id":19064},"improve-your-writing-skills","Improve your writing skills",[651,19067,19068],{},"I don't think there is anyone that is reading this that couldn't benefit from improving your writing skills. This is a skill that will follow you around in life and it doesn't hurt to constantly work on it. Whether you're writing a resume or an email to a friend, good writing and more importantly, terrible writing is easy to spot. ",[651,19070,19071],{},[660,19072],{"alt":19073,"src":19074},"Start a blog to improve your writing skills.","./alejandro-escamilla-4-1024x683.jpg",[651,19076,19077,13517,19080],{},[7300,19078,19079],{},"Pro Tip:",[7300,19081,19082,19083,19088],{},"If you aren't already using ",[812,19084,19087],{"href":19085,"rel":19086},"https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwiqgvCNlcfTAhVrqlQKHZuxB0wQFgg1MAA&url=https%3A%2F%2Fwww.grammarly.com%2F&usg=AFQjCNEEJC76jVSls2ggmUw2JGlwSqyTtQ&sig2=CCSGHVYnlTRRQgZ96zRpAA",[816],"Grammarly"," you need to check it out.",[5909,19090,19092],{"id":19091},"boost-you-resume","Boost You Resume",[651,19094,19095,19096,19099],{},"A personal blog where talk passionately about what you do is only going to help your career. You have to remember that potential employers are Googling you to see what comes up. If your personal blog, YouTube channel and Github account come up, that is a ",[2939,19097,19098],{},"WIN",". This screams that you are passionate about what you do and that it isn't just a job. ",[5909,19101,19103],{"id":19102},"community","Community",[651,19105,19106],{},"When I started writing I would write about stuff that I came across as an everyday developer. In doing so I would start to conversate with other members of the community. To me, this is very important for all developers. If you work for a smaller company chances are you don't have a large number of developers that you can look up to. If you get involved in the community you have a chance to have discussions about the problems you face with developers who have real world experience. The biggest part of the community for me was the connections I made. I am blessed to have met some really great people just by chatting them up at conferences. A majority of these conversations started because of my blog and being involved in the community. Did I happen to mention that I have my current job because of connections I have made in the community? I'm also proud to say that 4 coworkers will be attending my wedding later on this year. These are just work connections, these are friends for life connections. ",[5909,19108,19110],{"id":19109},"my-top-10-reasons-to-blog","My Top 10 Reasons to Blog",[651,19112,19113],{},"As I was writing this I realized that I have a top 10 list of reasons every developer should start a blog. I am adding this to my task list and when I get that published I will let you guys know! ",[4542,19115,19117],{"id":19116},"the-1thing-you-must-do-when-starting-your-blog","The 1 Thing you must do when starting your blog",[651,19119,19120],{},"I said at the start of this article that I was going to give you 1 thing you must do today. You absolutely need to start an email list. If I could go back in time to 2005 when I started this blog the one thing I would do is start collecting email addresses. This is an audience your building and you need to stay engaged with them. If it's just letting them know about a new post or telling them about a new product your working on. These readers subscribe because of the value that you bring to them and they want to stay up to date with what you are doing. ",[651,19122,19123],{},[660,19124],{"alt":19125,"src":19126},"Building your audience","./luca-bravo-207676-1024x683.jpg",[651,19128,19129,19130,15266],{},"There are many tools that can help you with building your list but my absolute favorite is ",[812,19131,19134],{"href":19132,"rel":19133},"https://danvega.dev/convertkit",[816],"Convertkit",[4542,19136,9042],{"id":9041},[651,19138,19139],{},"I am tired of hearing people scream from the rooftops that blogging is dead. Is video on the rise, of course, it is. This doesn't mean that there is real value in starting your own blog. I hope this pushes anyone thinking about starting a blog into action. It was one of the best decisions I have ever made and I hope you will give it a shot. Please let me know if you have any questions on getting started, finding content or anything related to blogging. Happy Friday friends!",[651,19141,19142],{},[7300,19143,19144,19146],{},[2939,19145,11650],{}," What is stopping you from starting a blog right now?",{"title":674,"searchDepth":790,"depth":790,"links":19148},[19149,19152,19158,19159],{"id":19020,"depth":790,"text":19021,"children":19150},[19151],{"id":19027,"depth":892,"text":19028},{"id":19053,"depth":790,"text":19054,"children":19153},[19154,19155,19156,19157],{"id":19064,"depth":892,"text":19065},{"id":19091,"depth":892,"text":19092},{"id":19102,"depth":892,"text":19103},{"id":19109,"depth":892,"text":19110},{"id":19116,"depth":790,"text":19117},{"id":9041,"depth":790,"text":9042},{"slug":19161,"published":797,"date":19162,"tags":19163,"cover":19165},"every-developer-start-blog-right-now","2017-04-28T09:15:56-04:00",[19164],"blogging","./ken-tomita-239357-760x507.jpg",{"title":192,"description":192},"blog/2017/04/28/every-developer-start-blog-right-now","ezOdobfcMl7wPztPHJ7PsKhgYm9a89INYFCd9pnDXWY",{"id":19170,"title":189,"body":19171,"description":189,"extension":793,"meta":20713,"navigation":797,"path":190,"seo":20718,"stem":20719,"__hash__":20720},"content/blog/2017/05/01/add-validation-spring-entities.md",{"type":648,"value":19172,"toc":20708},[19173,19176,19181,19190,19364,19368,19371,19534,19537,19709,19712,19789,19798,19802,19805,20042,20045,20266,20468,20471,20633,20692,20695,20698,20705],[651,19174,19175],{},"A student had a question about validating data at the domain level and so I thought it would share it with you.",[1004,19177,19178],{},[651,19179,19180],{},"Hi Dan, First of all thanks a lot for this great course. It really helped me to get into Spring. But now I'm facing a problem and didn't find a solution yet. I want to use for my DTO classes annotations like @NotNull (javax.validation.constraints) or custom annotations. But both don't work within spring boot. Do you know a good way or practice to solve this? Or is it too expensive to make these annotations work? If yes, is there a spring alternative for such annotations that execute a custom validation like a license-plate for instance? I hope this question isn't too off-topic to this course and perhaps also interesting for another member of this course. Best wishes, Daniel ",[651,19182,19183,19184,19189],{},"Before we get started I just want to thank Daniel for the question. If you want to follow along with this project you can ",[812,19185,19188],{"href":19186,"rel":19187},"https://github.com/cfaddict/spring-boot-validation-demo",[816],"grab the source code here",". We are going to start a new project and select the Web, JPA & H2 dependencies. ",[669,19191,19193],{"className":9101,"code":19192,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-actuator\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-actuator-docs\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-data-jpa\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n \u003C/dependency>\n\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-devtools\u003C/artifactId>\n \u003Cscope>runtime\u003C/scope>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.projectlombok\u003C/groupId>\n \u003CartifactId>lombok\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>com.h2database\u003C/groupId>\n \u003CartifactId>h2\u003C/artifactId>\n \u003Cscope>runtime\u003C/scope>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-test\u003C/artifactId>\n \u003Cscope>test\u003C/scope>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,19194,19195,19200,19205,19210,19215,19220,19224,19228,19233,19237,19241,19245,19250,19254,19258,19262,19267,19271,19275,19279,19283,19288,19293,19297,19301,19306,19311,19315,19319,19324,19329,19333,19337,19341,19345,19350,19355,19359],{"__ignoreMap":674},[679,19196,19197],{"class":681,"line":682},[679,19198,19199],{},"\u003Cdependencies>\n",[679,19201,19202],{"class":681,"line":790},[679,19203,19204],{}," \u003Cdependency>\n",[679,19206,19207],{"class":681,"line":892},[679,19208,19209],{}," \u003CgroupId>org.springframework.boot\u003C/groupId>\n",[679,19211,19212],{"class":681,"line":901},[679,19213,19214],{}," \u003CartifactId>spring-boot-starter-actuator\u003C/artifactId>\n",[679,19216,19217],{"class":681,"line":909},[679,19218,19219],{}," \u003C/dependency>\n",[679,19221,19222],{"class":681,"line":918},[679,19223,19204],{},[679,19225,19226],{"class":681,"line":935},[679,19227,19209],{},[679,19229,19230],{"class":681,"line":944},[679,19231,19232],{}," \u003CartifactId>spring-boot-actuator-docs\u003C/artifactId>\n",[679,19234,19235],{"class":681,"line":959},[679,19236,19219],{},[679,19238,19239],{"class":681,"line":964},[679,19240,19204],{},[679,19242,19243],{"class":681,"line":977},[679,19244,19209],{},[679,19246,19247],{"class":681,"line":982},[679,19248,19249],{}," \u003CartifactId>spring-boot-starter-data-jpa\u003C/artifactId>\n",[679,19251,19252],{"class":681,"line":988},[679,19253,19219],{},[679,19255,19256],{"class":681,"line":993},[679,19257,19204],{},[679,19259,19260],{"class":681,"line":2129},[679,19261,19209],{},[679,19263,19264],{"class":681,"line":2140},[679,19265,19266],{}," \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n",[679,19268,19269],{"class":681,"line":2145},[679,19270,19219],{},[679,19272,19273],{"class":681,"line":2154},[679,19274,889],{"emptyLinePlaceholder":797},[679,19276,19277],{"class":681,"line":2159},[679,19278,19204],{},[679,19280,19281],{"class":681,"line":2164},[679,19282,19209],{},[679,19284,19285],{"class":681,"line":3134},[679,19286,19287],{}," \u003CartifactId>spring-boot-devtools\u003C/artifactId>\n",[679,19289,19290],{"class":681,"line":3139},[679,19291,19292],{}," \u003Cscope>runtime\u003C/scope>\n",[679,19294,19295],{"class":681,"line":3144},[679,19296,19219],{},[679,19298,19299],{"class":681,"line":3149},[679,19300,19204],{},[679,19302,19303],{"class":681,"line":3169},[679,19304,19305],{}," \u003CgroupId>org.projectlombok\u003C/groupId>\n",[679,19307,19308],{"class":681,"line":3185},[679,19309,19310],{}," \u003CartifactId>lombok\u003C/artifactId>\n",[679,19312,19313],{"class":681,"line":3194},[679,19314,19219],{},[679,19316,19317],{"class":681,"line":3199},[679,19318,19204],{},[679,19320,19321],{"class":681,"line":3212},[679,19322,19323],{}," \u003CgroupId>com.h2database\u003C/groupId>\n",[679,19325,19326],{"class":681,"line":3217},[679,19327,19328],{}," \u003CartifactId>h2\u003C/artifactId>\n",[679,19330,19331],{"class":681,"line":3222},[679,19332,19292],{},[679,19334,19335],{"class":681,"line":3227},[679,19336,19219],{},[679,19338,19339],{"class":681,"line":3232},[679,19340,19204],{},[679,19342,19343],{"class":681,"line":3499},[679,19344,19209],{},[679,19346,19347],{"class":681,"line":3509},[679,19348,19349],{}," \u003CartifactId>spring-boot-starter-test\u003C/artifactId>\n",[679,19351,19352],{"class":681,"line":3516},[679,19353,19354],{}," \u003Cscope>test\u003C/scope>\n",[679,19356,19357],{"class":681,"line":3531},[679,19358,19219],{},[679,19360,19361],{"class":681,"line":3536},[679,19362,19363],{},"\u003C/dependencies>\n",[4542,19365,19367],{"id":19366},"validation","Validation",[651,19369,19370],{},"I am gong to create an entity called the city and the key here is to look at the state property. We are using an annotation on the state @NotNull. This says that we create a new city and try to save it that the state can't be null. ",[669,19372,19374],{"className":4107,"code":19373,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport lombok.Data;\n\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.Id;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Data\npublic class City {\n\n @Id @GeneratedValue\n private Long id;\n public String name;\n @NotNull\n public String state;\n\n private City() {}\n\n public City(String name){\n this.name = name;\n }\n}\n",[676,19375,19376,19382,19386,19392,19396,19402,19408,19414,19421,19425,19431,19437,19448,19452,19462,19468,19474,19481,19488,19492,19500,19504,19516,19526,19530],{"__ignoreMap":674},[679,19377,19378,19380],{"class":681,"line":682},[679,19379,2543],{"class":685},[679,19381,6039],{"class":693},[679,19383,19384],{"class":681,"line":790},[679,19385,889],{"emptyLinePlaceholder":797},[679,19387,19388,19390],{"class":681,"line":892},[679,19389,1999],{"class":685},[679,19391,15288],{"class":693},[679,19393,19394],{"class":681,"line":901},[679,19395,889],{"emptyLinePlaceholder":797},[679,19397,19398,19400],{"class":681,"line":909},[679,19399,1999],{"class":685},[679,19401,11209],{"class":693},[679,19403,19404,19406],{"class":681,"line":918},[679,19405,1999],{"class":685},[679,19407,11216],{"class":693},[679,19409,19410,19412],{"class":681,"line":935},[679,19411,1999],{"class":685},[679,19413,11223],{"class":693},[679,19415,19416,19418],{"class":681,"line":944},[679,19417,1999],{"class":685},[679,19419,19420],{"class":693}," javax.validation.constraints.NotNull;\n",[679,19422,19423],{"class":681,"line":959},[679,19424,889],{"emptyLinePlaceholder":797},[679,19426,19427,19429],{"class":681,"line":964},[679,19428,4116],{"class":693},[679,19430,11234],{"class":685},[679,19432,19433,19435],{"class":681,"line":977},[679,19434,4116],{"class":693},[679,19436,15305],{"class":685},[679,19438,19439,19441,19443,19446],{"class":681,"line":982},[679,19440,6073],{"class":685},[679,19442,4512],{"class":685},[679,19444,19445],{"class":880}," City",[679,19447,884],{"class":693},[679,19449,19450],{"class":681,"line":988},[679,19451,889],{"emptyLinePlaceholder":797},[679,19453,19454,19456,19458,19460],{"class":681,"line":993},[679,19455,6872],{"class":693},[679,19457,11256],{"class":685},[679,19459,6475],{"class":693},[679,19461,11261],{"class":685},[679,19463,19464,19466],{"class":681,"line":2129},[679,19465,9232],{"class":685},[679,19467,11268],{"class":693},[679,19469,19470,19472],{"class":681,"line":2140},[679,19471,6089],{"class":685},[679,19473,16319],{"class":693},[679,19475,19476,19478],{"class":681,"line":2145},[679,19477,6872],{"class":693},[679,19479,19480],{"class":685},"NotNull\n",[679,19482,19483,19485],{"class":681,"line":2154},[679,19484,6089],{"class":685},[679,19486,19487],{"class":693}," String state;\n",[679,19489,19490],{"class":681,"line":2159},[679,19491,889],{"emptyLinePlaceholder":797},[679,19493,19494,19496,19498],{"class":681,"line":2164},[679,19495,9232],{"class":685},[679,19497,19445],{"class":880},[679,19499,11295],{"class":693},[679,19501,19502],{"class":681,"line":3134},[679,19503,889],{"emptyLinePlaceholder":797},[679,19505,19506,19508,19510,19512,19514],{"class":681,"line":3139},[679,19507,6089],{"class":685},[679,19509,19445],{"class":880},[679,19511,11400],{"class":693},[679,19513,16334],{"class":2099},[679,19515,9533],{"class":693},[679,19517,19518,19520,19522,19524],{"class":681,"line":3144},[679,19519,7862],{"class":931},[679,19521,16343],{"class":693},[679,19523,686],{"class":685},[679,19525,15853],{"class":693},[679,19527,19528],{"class":681,"line":3149},[679,19529,985],{"class":693},[679,19531,19532],{"class":681,"line":3169},[679,19533,996],{"class":693},[651,19535,19536],{},"I then create a Command Line Runner to insert a new record. I am intentionally not adding the state to this object. ",[669,19538,19540],{"className":4107,"code":19539,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\n\n@SpringBootApplication\npublic class ValidationApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(ValidationApplication.class, args);\n }\n\n @Bean\n CommandLineRunner runner(CityRepository cityRepository){\n return args -> {\n cityRepository.save( new City(\"Cleveland\") );\n };\n }\n}\n",[676,19541,19542,19548,19552,19559,19565,19571,19577,19581,19587,19598,19602,19623,19633,19638,19642,19649,19664,19675,19696,19701,19705],{"__ignoreMap":674},[679,19543,19544,19546],{"class":681,"line":682},[679,19545,2543],{"class":685},[679,19547,6039],{"class":693},[679,19549,19550],{"class":681,"line":790},[679,19551,889],{"emptyLinePlaceholder":797},[679,19553,19554,19556],{"class":681,"line":892},[679,19555,1999],{"class":685},[679,19557,19558],{"class":693}," org.springframework.boot.CommandLineRunner;\n",[679,19560,19561,19563],{"class":681,"line":901},[679,19562,1999],{"class":685},[679,19564,6050],{"class":693},[679,19566,19567,19569],{"class":681,"line":909},[679,19568,1999],{"class":685},[679,19570,6057],{"class":693},[679,19572,19573,19575],{"class":681,"line":918},[679,19574,1999],{"class":685},[679,19576,6362],{"class":693},[679,19578,19579],{"class":681,"line":935},[679,19580,889],{"emptyLinePlaceholder":797},[679,19582,19583,19585],{"class":681,"line":944},[679,19584,4116],{"class":693},[679,19586,6068],{"class":685},[679,19588,19589,19591,19593,19596],{"class":681,"line":959},[679,19590,6073],{"class":685},[679,19592,4512],{"class":685},[679,19594,19595],{"class":880}," ValidationApplication",[679,19597,884],{"class":693},[679,19599,19600],{"class":681,"line":964},[679,19601,889],{"emptyLinePlaceholder":797},[679,19603,19604,19607,19609,19611,19613,19615,19617,19619,19621],{"class":681,"line":977},[679,19605,19606],{"class":685}," public",[679,19608,6092],{"class":685},[679,19610,6095],{"class":685},[679,19612,6098],{"class":880},[679,19614,745],{"class":693},[679,19616,4758],{"class":685},[679,19618,16901],{"class":693},[679,19620,6108],{"class":2099},[679,19622,4390],{"class":693},[679,19624,19625,19628,19630],{"class":681,"line":982},[679,19626,19627],{"class":693}," SpringApplication.",[679,19629,6118],{"class":880},[679,19631,19632],{"class":693},"(ValidationApplication.class, args);\n",[679,19634,19635],{"class":681,"line":988},[679,19636,19637],{"class":693}," }\n",[679,19639,19640],{"class":681,"line":993},[679,19641,889],{"emptyLinePlaceholder":797},[679,19643,19644,19647],{"class":681,"line":2129},[679,19645,19646],{"class":693}," @",[679,19648,16929],{"class":685},[679,19650,19651,19654,19656,19659,19662],{"class":681,"line":2140},[679,19652,19653],{"class":693}," CommandLineRunner ",[679,19655,16939],{"class":880},[679,19657,19658],{"class":693},"(CityRepository ",[679,19660,19661],{"class":2099},"cityRepository",[679,19663,9533],{"class":693},[679,19665,19666,19669,19671,19673],{"class":681,"line":2145},[679,19667,19668],{"class":685}," return",[679,19670,16952],{"class":693},[679,19672,16955],{"class":685},[679,19674,884],{"class":693},[679,19676,19677,19680,19682,19684,19686,19688,19690,19693],{"class":681,"line":2154},[679,19678,19679],{"class":693}," cityRepository.",[679,19681,7629],{"class":880},[679,19683,1234],{"class":693},[679,19685,8930],{"class":685},[679,19687,19445],{"class":880},[679,19689,745],{"class":693},[679,19691,19692],{"class":689},"\"Cleveland\"",[679,19694,19695],{"class":693},") );\n",[679,19697,19698],{"class":681,"line":2159},[679,19699,19700],{"class":693}," };\n",[679,19702,19703],{"class":681,"line":2164},[679,19704,19637],{"class":693},[679,19706,19707],{"class":681,"line":3134},[679,19708,996],{"class":693},[651,19710,19711],{},"When we try and run this application you will see the following error. ",[669,19713,19715],{"className":5851,"code":19714,"language":5853,"meta":674,"style":674},"Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [com.therealdanvega.City] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=state, rootBeanClass=class com.therealdanvega.City, messageTemplate='{javax.validation.constraints.NotNull.message}'}\n",[676,19716,19717,19750,19764],{"__ignoreMap":674},[679,19718,19719,19722,19725,19728,19731,19734,19736,19739,19742,19745,19747],{"class":681,"line":682},[679,19720,19721],{"class":880},"Caused",[679,19723,19724],{"class":689}," by:",[679,19726,19727],{"class":689}," javax.validation.ConstraintViolationException:",[679,19729,19730],{"class":689}," Validation",[679,19732,19733],{"class":689}," failed",[679,19735,6818],{"class":689},[679,19737,19738],{"class":689}," classes",[679,19740,19741],{"class":693}," [com.therealdanvega.City] during persist ",[679,19743,19744],{"class":685},"time",[679,19746,6818],{"class":685},[679,19748,19749],{"class":693}," groups [javax.validation.groups.Default, ]\n",[679,19751,19752,19755,19758,19761],{"class":681,"line":790},[679,19753,19754],{"class":880},"List",[679,19756,19757],{"class":689}," of",[679,19759,19760],{"class":689}," constraint",[679,19762,19763],{"class":689}," violations:[\n",[679,19765,19766,19769,19772,19775,19777,19780,19783,19786],{"class":681,"line":892},[679,19767,19768],{"class":880}," ConstraintViolationImpl",[679,19770,19771],{"class":689},"{interpolatedMessage=",[679,19773,19774],{"class":880},"'may not be null'",[679,19776,1202],{"class":880},[679,19778,19779],{"class":689}," propertyPath=state,",[679,19781,19782],{"class":689}," rootBeanClass=class",[679,19784,19785],{"class":689}," com.therealdanvega.City,",[679,19787,19788],{"class":689}," messageTemplate='{javax.validation.constraints.NotNull.message}'}\n",[651,19790,19791,19792,19797],{},"This was so easy to do and the great thing is it doesn't stop there. If you want to add all kinds of validation to different properties you can. ",[812,19793,19796],{"href":19794,"rel":19795},"https://docs.oracle.com/javaee/7/api/javax/validation/constraints/package-summary.html",[816],"Check out the documentation"," to find a list of annotations you can add for validation. ",[4542,19799,19801],{"id":19800},"custom-validation","Custom Validation",[651,19803,19804],{},"Most of the time the annotations provided will get the job the done. There are times when you need some type of custom validation done. In these cases, we can create our own custom validator and it's really easy to do. Say on our City object we wanted an annotation where we can make sure the state was equal to \"OHIO\". I know this is a silly example but I want to keep it simple. This is what our domain object would look like now. ",[669,19806,19808],{"className":4107,"code":19807,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport com.therealdanvega.validator.StateValidator;\nimport lombok.Data;\n\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.Id;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Data\npublic class City {\n\n @Id @GeneratedValue\n private Long id;\n public String name;\n\n @NotNull\n @StateValidator( value = \"OHIO\" )\n public String state;\n\n private City() {}\n\n public City(String name){\n this.name = name;\n }\n\n public City(String name, String state){\n this.name = name;\n this.state = state;\n }\n}\n",[676,19809,19810,19816,19820,19827,19833,19837,19843,19849,19855,19861,19865,19871,19877,19887,19891,19901,19907,19913,19917,19923,19942,19948,19952,19960,19964,19976,19986,19990,19994,20012,20022,20034,20038],{"__ignoreMap":674},[679,19811,19812,19814],{"class":681,"line":682},[679,19813,2543],{"class":685},[679,19815,6039],{"class":693},[679,19817,19818],{"class":681,"line":790},[679,19819,889],{"emptyLinePlaceholder":797},[679,19821,19822,19824],{"class":681,"line":892},[679,19823,1999],{"class":685},[679,19825,19826],{"class":693}," com.therealdanvega.validator.StateValidator;\n",[679,19828,19829,19831],{"class":681,"line":901},[679,19830,1999],{"class":685},[679,19832,15288],{"class":693},[679,19834,19835],{"class":681,"line":909},[679,19836,889],{"emptyLinePlaceholder":797},[679,19838,19839,19841],{"class":681,"line":918},[679,19840,1999],{"class":685},[679,19842,11209],{"class":693},[679,19844,19845,19847],{"class":681,"line":935},[679,19846,1999],{"class":685},[679,19848,11216],{"class":693},[679,19850,19851,19853],{"class":681,"line":944},[679,19852,1999],{"class":685},[679,19854,11223],{"class":693},[679,19856,19857,19859],{"class":681,"line":959},[679,19858,1999],{"class":685},[679,19860,19420],{"class":693},[679,19862,19863],{"class":681,"line":964},[679,19864,889],{"emptyLinePlaceholder":797},[679,19866,19867,19869],{"class":681,"line":977},[679,19868,4116],{"class":693},[679,19870,11234],{"class":685},[679,19872,19873,19875],{"class":681,"line":982},[679,19874,4116],{"class":693},[679,19876,15305],{"class":685},[679,19878,19879,19881,19883,19885],{"class":681,"line":988},[679,19880,6073],{"class":685},[679,19882,4512],{"class":685},[679,19884,19445],{"class":880},[679,19886,884],{"class":693},[679,19888,19889],{"class":681,"line":993},[679,19890,889],{"emptyLinePlaceholder":797},[679,19892,19893,19895,19897,19899],{"class":681,"line":2129},[679,19894,6872],{"class":693},[679,19896,11256],{"class":685},[679,19898,6475],{"class":693},[679,19900,11261],{"class":685},[679,19902,19903,19905],{"class":681,"line":2140},[679,19904,9232],{"class":685},[679,19906,11268],{"class":693},[679,19908,19909,19911],{"class":681,"line":2145},[679,19910,6089],{"class":685},[679,19912,16319],{"class":693},[679,19914,19915],{"class":681,"line":2154},[679,19916,889],{"emptyLinePlaceholder":797},[679,19918,19919,19921],{"class":681,"line":2159},[679,19920,6872],{"class":693},[679,19922,19480],{"class":685},[679,19924,19925,19927,19930,19932,19935,19937,19940],{"class":681,"line":2164},[679,19926,6872],{"class":693},[679,19928,19929],{"class":685},"StateValidator",[679,19931,1234],{"class":693},[679,19933,19934],{"class":931},"value",[679,19936,6883],{"class":685},[679,19938,19939],{"class":689}," \"OHIO\"",[679,19941,5610],{"class":693},[679,19943,19944,19946],{"class":681,"line":3134},[679,19945,6089],{"class":685},[679,19947,19487],{"class":693},[679,19949,19950],{"class":681,"line":3139},[679,19951,889],{"emptyLinePlaceholder":797},[679,19953,19954,19956,19958],{"class":681,"line":3144},[679,19955,9232],{"class":685},[679,19957,19445],{"class":880},[679,19959,11295],{"class":693},[679,19961,19962],{"class":681,"line":3149},[679,19963,889],{"emptyLinePlaceholder":797},[679,19965,19966,19968,19970,19972,19974],{"class":681,"line":3169},[679,19967,6089],{"class":685},[679,19969,19445],{"class":880},[679,19971,11400],{"class":693},[679,19973,16334],{"class":2099},[679,19975,9533],{"class":693},[679,19977,19978,19980,19982,19984],{"class":681,"line":3185},[679,19979,7862],{"class":931},[679,19981,16343],{"class":693},[679,19983,686],{"class":685},[679,19985,15853],{"class":693},[679,19987,19988],{"class":681,"line":3194},[679,19989,985],{"class":693},[679,19991,19992],{"class":681,"line":3199},[679,19993,889],{"emptyLinePlaceholder":797},[679,19995,19996,19998,20000,20002,20004,20007,20010],{"class":681,"line":3212},[679,19997,6089],{"class":685},[679,19999,19445],{"class":880},[679,20001,11400],{"class":693},[679,20003,16334],{"class":2099},[679,20005,20006],{"class":693},", String ",[679,20008,20009],{"class":2099},"state",[679,20011,9533],{"class":693},[679,20013,20014,20016,20018,20020],{"class":681,"line":3217},[679,20015,7862],{"class":931},[679,20017,16343],{"class":693},[679,20019,686],{"class":685},[679,20021,15853],{"class":693},[679,20023,20024,20026,20029,20031],{"class":681,"line":3222},[679,20025,7862],{"class":931},[679,20027,20028],{"class":693},".state ",[679,20030,686],{"class":685},[679,20032,20033],{"class":693}," state;\n",[679,20035,20036],{"class":681,"line":3227},[679,20037,985],{"class":693},[679,20039,20040],{"class":681,"line":3232},[679,20041,996],{"class":693},[651,20043,20044],{},"Now we create our own annotation & StateValidatorCheck constraint. ",[669,20046,20048],{"className":4107,"code":20047,"language":4109,"meta":674,"style":674},"package com.therealdanvega.validator;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.ANNOTATION_TYPE;\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.ElementType.METHOD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n@Documented\n@Constraint(validatedBy = StateValidatorCheck.class)\n@Target({ METHOD, FIELD, ANNOTATION_TYPE })\n@Retention(RUNTIME)\npublic @interface StateValidator {\n String message() default \"{com.therealdanvega.state.message}\";\n Class\u003C?>[] groups() default {};\n Class\u003C? extends Payload>[] payload() default {};\n String value() default \"\";\n}\n",[676,20049,20050,20057,20061,20068,20075,20081,20087,20093,20097,20106,20115,20124,20133,20137,20143,20160,20169,20178,20191,20208,20225,20248,20262],{"__ignoreMap":674},[679,20051,20052,20054],{"class":681,"line":682},[679,20053,2543],{"class":685},[679,20055,20056],{"class":693}," com.therealdanvega.validator;\n",[679,20058,20059],{"class":681,"line":790},[679,20060,889],{"emptyLinePlaceholder":797},[679,20062,20063,20065],{"class":681,"line":892},[679,20064,1999],{"class":685},[679,20066,20067],{"class":693}," javax.validation.Constraint;\n",[679,20069,20070,20072],{"class":681,"line":901},[679,20071,1999],{"class":685},[679,20073,20074],{"class":693}," javax.validation.Payload;\n",[679,20076,20077,20079],{"class":681,"line":909},[679,20078,1999],{"class":685},[679,20080,6316],{"class":693},[679,20082,20083,20085],{"class":681,"line":918},[679,20084,1999],{"class":685},[679,20086,6337],{"class":693},[679,20088,20089,20091],{"class":681,"line":935},[679,20090,1999],{"class":685},[679,20092,6351],{"class":693},[679,20094,20095],{"class":681,"line":944},[679,20096,889],{"emptyLinePlaceholder":797},[679,20098,20099,20101,20103],{"class":681,"line":959},[679,20100,1999],{"class":685},[679,20102,6092],{"class":685},[679,20104,20105],{"class":693}," java.lang.annotation.ElementType.ANNOTATION_TYPE;\n",[679,20107,20108,20110,20112],{"class":681,"line":964},[679,20109,1999],{"class":685},[679,20111,6092],{"class":685},[679,20113,20114],{"class":693}," java.lang.annotation.ElementType.FIELD;\n",[679,20116,20117,20119,20121],{"class":681,"line":977},[679,20118,1999],{"class":685},[679,20120,6092],{"class":685},[679,20122,20123],{"class":693}," java.lang.annotation.ElementType.METHOD;\n",[679,20125,20126,20128,20130],{"class":681,"line":982},[679,20127,1999],{"class":685},[679,20129,6092],{"class":685},[679,20131,20132],{"class":693}," java.lang.annotation.RetentionPolicy.RUNTIME;\n",[679,20134,20135],{"class":681,"line":988},[679,20136,889],{"emptyLinePlaceholder":797},[679,20138,20139,20141],{"class":681,"line":993},[679,20140,4116],{"class":693},[679,20142,6595],{"class":685},[679,20144,20145,20147,20150,20152,20155,20157],{"class":681,"line":2129},[679,20146,4116],{"class":693},[679,20148,20149],{"class":685},"Constraint",[679,20151,745],{"class":693},[679,20153,20154],{"class":931},"validatedBy",[679,20156,6883],{"class":685},[679,20158,20159],{"class":693}," StateValidatorCheck.class)\n",[679,20161,20162,20164,20166],{"class":681,"line":2140},[679,20163,4116],{"class":693},[679,20165,6575],{"class":685},[679,20167,20168],{"class":693},"({ METHOD, FIELD, ANNOTATION_TYPE })\n",[679,20170,20171,20173,20175],{"class":681,"line":2145},[679,20172,4116],{"class":693},[679,20174,6585],{"class":685},[679,20176,20177],{"class":693},"(RUNTIME)\n",[679,20179,20180,20182,20184,20186,20189],{"class":681,"line":2154},[679,20181,6073],{"class":685},[679,20183,6475],{"class":693},[679,20185,6630],{"class":685},[679,20187,20188],{"class":685}," StateValidator",[679,20190,884],{"class":693},[679,20192,20193,20196,20199,20201,20203,20206],{"class":681,"line":2159},[679,20194,20195],{"class":693}," String ",[679,20197,20198],{"class":880},"message",[679,20200,6700],{"class":693},[679,20202,6703],{"class":685},[679,20204,20205],{"class":689}," \"{com.therealdanvega.state.message}\"",[679,20207,1186],{"class":693},[679,20209,20210,20212,20214,20216,20219,20221,20223],{"class":681,"line":2164},[679,20211,6689],{"class":693},[679,20213,6692],{"class":685},[679,20215,16901],{"class":693},[679,20217,20218],{"class":880},"groups",[679,20220,6700],{"class":693},[679,20222,6703],{"class":685},[679,20224,6706],{"class":693},[679,20226,20227,20229,20232,20235,20237,20239,20242,20244,20246],{"class":681,"line":3134},[679,20228,6689],{"class":693},[679,20230,20231],{"class":685},"\u003C?",[679,20233,20234],{"class":693}," extends Payload",[679,20236,5860],{"class":685},[679,20238,16901],{"class":693},[679,20240,20241],{"class":880},"payload",[679,20243,6700],{"class":693},[679,20245,6703],{"class":685},[679,20247,6706],{"class":693},[679,20249,20250,20252,20254,20256,20258,20260],{"class":681,"line":3139},[679,20251,20195],{"class":693},[679,20253,19934],{"class":880},[679,20255,6700],{"class":693},[679,20257,6703],{"class":685},[679,20259,1183],{"class":689},[679,20261,1186],{"class":693},[679,20263,20264],{"class":681,"line":3144},[679,20265,996],{"class":693},[669,20267,20269],{"className":4107,"code":20268,"language":4109,"meta":674,"style":674},"package com.therealdanvega.validator;\n\nimport com.therealdanvega.City;\n\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\n\npublic class StateValidatorCheck implements ConstraintValidator\u003CStateValidator, String> {\n\n private String state;\n\n @Override\n public void initialize(StateValidator constraint) {\n this.state = constraint.value();\n }\n\n @Override\n public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {\n if( s.equalsIgnoreCase( this.state ))\n return true;\n\n return false;\n }\n}\n",[676,20270,20271,20277,20281,20288,20292,20299,20306,20310,20334,20338,20344,20348,20354,20371,20386,20390,20394,20400,20422,20439,20448,20452,20460,20464],{"__ignoreMap":674},[679,20272,20273,20275],{"class":681,"line":682},[679,20274,2543],{"class":685},[679,20276,20056],{"class":693},[679,20278,20279],{"class":681,"line":790},[679,20280,889],{"emptyLinePlaceholder":797},[679,20282,20283,20285],{"class":681,"line":892},[679,20284,1999],{"class":685},[679,20286,20287],{"class":693}," com.therealdanvega.City;\n",[679,20289,20290],{"class":681,"line":901},[679,20291,889],{"emptyLinePlaceholder":797},[679,20293,20294,20296],{"class":681,"line":909},[679,20295,1999],{"class":685},[679,20297,20298],{"class":693}," javax.validation.ConstraintValidator;\n",[679,20300,20301,20303],{"class":681,"line":918},[679,20302,1999],{"class":685},[679,20304,20305],{"class":693}," javax.validation.ConstraintValidatorContext;\n",[679,20307,20308],{"class":681,"line":935},[679,20309,889],{"emptyLinePlaceholder":797},[679,20311,20312,20314,20316,20319,20321,20324,20326,20328,20330,20332],{"class":681,"line":944},[679,20313,6073],{"class":685},[679,20315,4512],{"class":685},[679,20317,20318],{"class":880}," StateValidatorCheck",[679,20320,4661],{"class":685},[679,20322,20323],{"class":880}," ConstraintValidator",[679,20325,4505],{"class":693},[679,20327,19929],{"class":685},[679,20329,2797],{"class":693},[679,20331,4758],{"class":685},[679,20333,16397],{"class":693},[679,20335,20336],{"class":681,"line":959},[679,20337,889],{"emptyLinePlaceholder":797},[679,20339,20340,20342],{"class":681,"line":964},[679,20341,9232],{"class":685},[679,20343,19487],{"class":693},[679,20345,20346],{"class":681,"line":977},[679,20347,889],{"emptyLinePlaceholder":797},[679,20349,20350,20352],{"class":681,"line":982},[679,20351,6872],{"class":693},[679,20353,10723],{"class":685},[679,20355,20356,20358,20360,20363,20366,20369],{"class":681,"line":988},[679,20357,6089],{"class":685},[679,20359,6095],{"class":685},[679,20361,20362],{"class":880}," initialize",[679,20364,20365],{"class":693},"(StateValidator ",[679,20367,20368],{"class":2099},"constraint",[679,20370,4390],{"class":693},[679,20372,20373,20375,20377,20379,20382,20384],{"class":681,"line":993},[679,20374,7862],{"class":931},[679,20376,20028],{"class":693},[679,20378,686],{"class":685},[679,20380,20381],{"class":693}," constraint.",[679,20383,19934],{"class":880},[679,20385,9317],{"class":693},[679,20387,20388],{"class":681,"line":2129},[679,20389,985],{"class":693},[679,20391,20392],{"class":681,"line":2140},[679,20393,889],{"emptyLinePlaceholder":797},[679,20395,20396,20398],{"class":681,"line":2145},[679,20397,6872],{"class":693},[679,20399,10723],{"class":685},[679,20401,20402,20404,20406,20409,20411,20414,20417,20420],{"class":681,"line":2154},[679,20403,6089],{"class":685},[679,20405,14493],{"class":685},[679,20407,20408],{"class":880}," isValid",[679,20410,11400],{"class":693},[679,20412,20413],{"class":2099},"s",[679,20415,20416],{"class":693},", ConstraintValidatorContext ",[679,20418,20419],{"class":2099},"constraintValidatorContext",[679,20421,4390],{"class":693},[679,20423,20424,20426,20429,20432,20434,20436],{"class":681,"line":2159},[679,20425,1249],{"class":685},[679,20427,20428],{"class":693},"( s.",[679,20430,20431],{"class":880},"equalsIgnoreCase",[679,20433,1234],{"class":693},[679,20435,4732],{"class":931},[679,20437,20438],{"class":693},".state ))\n",[679,20440,20441,20444,20446],{"class":681,"line":2164},[679,20442,20443],{"class":685}," return",[679,20445,14523],{"class":931},[679,20447,1186],{"class":693},[679,20449,20450],{"class":681,"line":3134},[679,20451,889],{"emptyLinePlaceholder":797},[679,20453,20454,20456,20458],{"class":681,"line":3139},[679,20455,9444],{"class":685},[679,20457,14559],{"class":931},[679,20459,1186],{"class":693},[679,20461,20462],{"class":681,"line":3144},[679,20463,985],{"class":693},[679,20465,20466],{"class":681,"line":3149},[679,20467,996],{"class":693},[651,20469,20470],{},"Now if we try and create a city object with a state other than OHIO we will get an error. ",[669,20472,20474],{"className":4107,"code":20473,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\n\n@SpringBootApplication\npublic class ValidationApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(ValidationApplication.class, args);\n }\n\n @Bean\n CommandLineRunner runner(CityRepository cityRepository){\n return args -> {\n cityRepository.save( new City(\"Cleveland\", \"Tennesee\") );\n };\n }\n}\n",[676,20475,20476,20482,20486,20492,20498,20504,20510,20514,20520,20530,20534,20554,20562,20566,20570,20576,20588,20598,20621,20625,20629],{"__ignoreMap":674},[679,20477,20478,20480],{"class":681,"line":682},[679,20479,2543],{"class":685},[679,20481,6039],{"class":693},[679,20483,20484],{"class":681,"line":790},[679,20485,889],{"emptyLinePlaceholder":797},[679,20487,20488,20490],{"class":681,"line":892},[679,20489,1999],{"class":685},[679,20491,19558],{"class":693},[679,20493,20494,20496],{"class":681,"line":901},[679,20495,1999],{"class":685},[679,20497,6050],{"class":693},[679,20499,20500,20502],{"class":681,"line":909},[679,20501,1999],{"class":685},[679,20503,6057],{"class":693},[679,20505,20506,20508],{"class":681,"line":918},[679,20507,1999],{"class":685},[679,20509,6362],{"class":693},[679,20511,20512],{"class":681,"line":935},[679,20513,889],{"emptyLinePlaceholder":797},[679,20515,20516,20518],{"class":681,"line":944},[679,20517,4116],{"class":693},[679,20519,6068],{"class":685},[679,20521,20522,20524,20526,20528],{"class":681,"line":959},[679,20523,6073],{"class":685},[679,20525,4512],{"class":685},[679,20527,19595],{"class":880},[679,20529,884],{"class":693},[679,20531,20532],{"class":681,"line":964},[679,20533,889],{"emptyLinePlaceholder":797},[679,20535,20536,20538,20540,20542,20544,20546,20548,20550,20552],{"class":681,"line":977},[679,20537,19606],{"class":685},[679,20539,6092],{"class":685},[679,20541,6095],{"class":685},[679,20543,6098],{"class":880},[679,20545,745],{"class":693},[679,20547,4758],{"class":685},[679,20549,16901],{"class":693},[679,20551,6108],{"class":2099},[679,20553,4390],{"class":693},[679,20555,20556,20558,20560],{"class":681,"line":982},[679,20557,19627],{"class":693},[679,20559,6118],{"class":880},[679,20561,19632],{"class":693},[679,20563,20564],{"class":681,"line":988},[679,20565,19637],{"class":693},[679,20567,20568],{"class":681,"line":993},[679,20569,889],{"emptyLinePlaceholder":797},[679,20571,20572,20574],{"class":681,"line":2129},[679,20573,19646],{"class":693},[679,20575,16929],{"class":685},[679,20577,20578,20580,20582,20584,20586],{"class":681,"line":2140},[679,20579,19653],{"class":693},[679,20581,16939],{"class":880},[679,20583,19658],{"class":693},[679,20585,19661],{"class":2099},[679,20587,9533],{"class":693},[679,20589,20590,20592,20594,20596],{"class":681,"line":2145},[679,20591,19668],{"class":685},[679,20593,16952],{"class":693},[679,20595,16955],{"class":685},[679,20597,884],{"class":693},[679,20599,20600,20602,20604,20606,20608,20610,20612,20614,20616,20619],{"class":681,"line":2154},[679,20601,19679],{"class":693},[679,20603,7629],{"class":880},[679,20605,1234],{"class":693},[679,20607,8930],{"class":685},[679,20609,19445],{"class":880},[679,20611,745],{"class":693},[679,20613,19692],{"class":689},[679,20615,2797],{"class":693},[679,20617,20618],{"class":689},"\"Tennesee\"",[679,20620,19695],{"class":693},[679,20622,20623],{"class":681,"line":2159},[679,20624,19700],{"class":693},[679,20626,20627],{"class":681,"line":2164},[679,20628,19637],{"class":693},[679,20630,20631],{"class":681,"line":3134},[679,20632,996],{"class":693},[669,20634,20636],{"className":5851,"code":20635,"language":5853,"meta":674,"style":674},"Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [com.therealdanvega.City] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n ConstraintViolationImpl{interpolatedMessage='{com.therealdanvega.state.message}', propertyPath=state, rootBeanClass=class com.therealdanvega.City, messageTemplate='{com.therealdanvega.state.message}'}\n",[676,20637,20638,20662,20672],{"__ignoreMap":674},[679,20639,20640,20642,20644,20646,20648,20650,20652,20654,20656,20658,20660],{"class":681,"line":682},[679,20641,19721],{"class":880},[679,20643,19724],{"class":689},[679,20645,19727],{"class":689},[679,20647,19730],{"class":689},[679,20649,19733],{"class":689},[679,20651,6818],{"class":689},[679,20653,19738],{"class":689},[679,20655,19741],{"class":693},[679,20657,19744],{"class":685},[679,20659,6818],{"class":685},[679,20661,19749],{"class":693},[679,20663,20664,20666,20668,20670],{"class":681,"line":790},[679,20665,19754],{"class":880},[679,20667,19757],{"class":689},[679,20669,19760],{"class":689},[679,20671,19763],{"class":689},[679,20673,20674,20676,20678,20681,20683,20685,20687,20689],{"class":681,"line":892},[679,20675,19768],{"class":880},[679,20677,19771],{"class":689},[679,20679,20680],{"class":880},"'{com.therealdanvega.state.message}'",[679,20682,1202],{"class":880},[679,20684,19779],{"class":689},[679,20686,19782],{"class":689},[679,20688,19785],{"class":689},[679,20690,20691],{"class":689}," messageTemplate='{com.therealdanvega.state.message}'}\n",[4542,20693,20694],{"id":9041},"Conclusion ",[651,20696,20697],{},"As you can see its pretty easy to sprinkle in some validation in your Spring Boot applications. ",[651,20699,20700],{},[7300,20701,20702,20704],{},[2939,20703,11650],{}," What are the challenges you face in validating data?",[786,20706,20707],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":20709},[20710,20711,20712],{"id":19366,"depth":790,"text":19367},{"id":19800,"depth":790,"text":19801},{"id":9041,"depth":790,"text":20694},{"slug":20714,"published":797,"date":20715,"tags":20716,"cover":20717},"add-validation-spring-entities","2017-05-01T16:37:13-04:00",[7055],"./luis-llerena-14779-760x507.jpg",{"title":189,"description":189},"blog/2017/05/01/add-validation-spring-entities","r28Hse09tbOSTBarjQulHqQa3JbfD72U7rBBzHmboyA",{"id":20722,"title":186,"body":20723,"description":186,"extension":793,"meta":21584,"navigation":797,"path":187,"seo":21589,"stem":21590,"__hash__":21591},"content/blog/2017/05/03/spring-angular-applications.md",{"type":648,"value":20724,"toc":21573},[20725,20728,20732,20740,20743,20762,20906,20909,21127,21130,21133,21162,21166,21169,21500,21503,21507,21544,21548,21555,21557,21565,21570],[651,20726,20727],{},"If your story reads anything like mine this is going to be an exciting post. I have worked with Angular JS 1.x in both projects and large scale projects at work. I honestly liked the framework but always felt that it was a bit too boilerplate and complex. Now that Angular 2, and subsequently Angular 4 has been released I have spent the last few months catching up. If you ever felt like I did towards Angular 1.x I am here to tell you that it is time to give Angular another look. I absolutely love building Angular 2 & Spring Applications and in this article, I am going to give you some resources to help kick start your journey. ",[4542,20729,20731],{"id":20730},"getting-started-with-angular-and-spring-boot","Getting Started with Angular and Spring Boot",[651,20733,20734,20735,15266],{},"We are going to walk through some basics and give you lots of resources to start looking into. At the end of this article, you will find a screencast I did where I walk through a simple Tasks application written in Spring Boot & Angular. If you want to check out the source code for this application you can ",[812,20736,20739],{"href":20737,"rel":20738},"https://github.com/cfaddict/spring-angular2-tasks",[816],"grab it here",[4542,20741,7077],{"id":20742},"spring-boot",[651,20744,20745,20746,20750,20751,20756,20757,20761],{},"Any great modern application is going to need a proven application framework on the back end. Spring Boot is an amazing project that helps us quickly get our Spring Framework Applications up and running. In our case, we will use the ",[812,20747,7117],{"href":20748,"rel":20749},"http://start.spring.io/",[816]," to help bootstrap our application. We can do this right in our ",[812,20752,20755],{"href":20753,"rel":20754},"https://www.jetbrains.com/idea/",[816],"favorite IDE"," or directly from ",[812,20758,20760],{"href":20748,"rel":20759},[816],"the website",". In Spring we are going to create a REST Controller that can handle a call from our Angular application. This controller will accept a GET request and return to it a list of Tasks. ",[669,20763,20765],{"className":4107,"code":20764,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/tasks\")\npublic class TaskController {\n\n private TaskService taskService;\n\n public TaskController(TaskService taskService) {\n this.taskService = taskService;\n }\n\n @GetMapping( value = {\"\",\"/\"})\n public Iterable\u003CTask> listTasks(){\n return taskService.list();\n }\n}\n",[676,20766,20767,20773,20786,20797,20801,20808,20812,20826,20838,20842,20846,20869,20887,20898,20902],{"__ignoreMap":674},[679,20768,20769,20771],{"class":681,"line":682},[679,20770,4116],{"class":693},[679,20772,9212],{"class":685},[679,20774,20775,20777,20779,20781,20784],{"class":681,"line":790},[679,20776,4116],{"class":693},[679,20778,9275],{"class":685},[679,20780,745],{"class":693},[679,20782,20783],{"class":689},"\"/api/tasks\"",[679,20785,1339],{"class":693},[679,20787,20788,20790,20792,20795],{"class":681,"line":892},[679,20789,6073],{"class":685},[679,20791,4512],{"class":685},[679,20793,20794],{"class":880}," TaskController",[679,20796,884],{"class":693},[679,20798,20799],{"class":681,"line":901},[679,20800,889],{"emptyLinePlaceholder":797},[679,20802,20803,20805],{"class":681,"line":909},[679,20804,9232],{"class":685},[679,20806,20807],{"class":693}," TaskService taskService;\n",[679,20809,20810],{"class":681,"line":918},[679,20811,889],{"emptyLinePlaceholder":797},[679,20813,20814,20816,20818,20821,20824],{"class":681,"line":935},[679,20815,6089],{"class":685},[679,20817,20794],{"class":880},[679,20819,20820],{"class":693},"(TaskService ",[679,20822,20823],{"class":2099},"taskService",[679,20825,4390],{"class":693},[679,20827,20828,20830,20833,20835],{"class":681,"line":944},[679,20829,7862],{"class":931},[679,20831,20832],{"class":693},".taskService ",[679,20834,686],{"class":685},[679,20836,20837],{"class":693}," taskService;\n",[679,20839,20840],{"class":681,"line":959},[679,20841,985],{"class":693},[679,20843,20844],{"class":681,"line":964},[679,20845,889],{"emptyLinePlaceholder":797},[679,20847,20848,20850,20853,20855,20857,20859,20861,20863,20865,20867],{"class":681,"line":977},[679,20849,6872],{"class":693},[679,20851,20852],{"class":685},"GetMapping",[679,20854,1234],{"class":693},[679,20856,19934],{"class":931},[679,20858,6883],{"class":685},[679,20860,11566],{"class":693},[679,20862,3579],{"class":689},[679,20864,1202],{"class":693},[679,20866,10032],{"class":689},[679,20868,6240],{"class":693},[679,20870,20871,20873,20876,20879,20882,20885],{"class":681,"line":982},[679,20872,6089],{"class":685},[679,20874,20875],{"class":693}," Iterable\u003C",[679,20877,20878],{"class":685},"Task",[679,20880,20881],{"class":693},"> ",[679,20883,20884],{"class":880},"listTasks",[679,20886,2041],{"class":693},[679,20888,20889,20891,20894,20896],{"class":681,"line":988},[679,20890,9444],{"class":685},[679,20892,20893],{"class":693}," taskService.",[679,20895,7623],{"class":880},[679,20897,9317],{"class":693},[679,20899,20900],{"class":681,"line":993},[679,20901,985],{"class":693},[679,20903,20904],{"class":681,"line":2129},[679,20905,996],{"class":693},[651,20907,20908],{},"This will call a service and repository that will fetch our 4 tasks from the database. We are going to load our initial data by using a Command Line Runner. ",[669,20910,20912],{"className":4107,"code":20911,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class TasksApplication {\n\n public static void main(String\\[\\] args) {\n SpringApplication.run(TasksApplication.class, args);\n }\n\n @Bean\n CommandLineRunner runner(TaskService taskService){\n return args -> {\n taskService.save( new Task(1L,\"Create Spring Boot Application\",true));\n taskService.save( new Task(2L,\"Create Angular 2 Application\",true));\n taskService.save( new Task(3L,\"Run the demo application\",true));\n taskService.save( new Task(4L, \"Make 1 Million Dollars\", false));\n };\n }\n}\n",[676,20913,20914,20920,20931,20935,20955,20964,20968,20972,20978,20991,21001,21031,21059,21087,21115,21119,21123],{"__ignoreMap":674},[679,20915,20916,20918],{"class":681,"line":682},[679,20917,4116],{"class":693},[679,20919,6068],{"class":685},[679,20921,20922,20924,20926,20929],{"class":681,"line":790},[679,20923,6073],{"class":685},[679,20925,4512],{"class":685},[679,20927,20928],{"class":880}," TasksApplication",[679,20930,884],{"class":693},[679,20932,20933],{"class":681,"line":892},[679,20934,889],{"emptyLinePlaceholder":797},[679,20936,20937,20939,20941,20943,20945,20947,20949,20951,20953],{"class":681,"line":901},[679,20938,6089],{"class":685},[679,20940,6092],{"class":685},[679,20942,6095],{"class":685},[679,20944,6098],{"class":880},[679,20946,745],{"class":693},[679,20948,4758],{"class":2099},[679,20950,6105],{"class":693},[679,20952,6108],{"class":2099},[679,20954,4390],{"class":693},[679,20956,20957,20959,20961],{"class":681,"line":909},[679,20958,6115],{"class":693},[679,20960,6118],{"class":880},[679,20962,20963],{"class":693},"(TasksApplication.class, args);\n",[679,20965,20966],{"class":681,"line":918},[679,20967,985],{"class":693},[679,20969,20970],{"class":681,"line":935},[679,20971,889],{"emptyLinePlaceholder":797},[679,20973,20974,20976],{"class":681,"line":944},[679,20975,6872],{"class":693},[679,20977,16929],{"class":685},[679,20979,20980,20983,20985,20987,20989],{"class":681,"line":959},[679,20981,20982],{"class":693}," CommandLineRunner ",[679,20984,16939],{"class":880},[679,20986,20820],{"class":693},[679,20988,20823],{"class":2099},[679,20990,9533],{"class":693},[679,20992,20993,20995,20997,20999],{"class":681,"line":964},[679,20994,9444],{"class":685},[679,20996,16952],{"class":693},[679,20998,16955],{"class":685},[679,21000,884],{"class":693},[679,21002,21003,21006,21008,21010,21012,21015,21017,21020,21022,21025,21027,21029],{"class":681,"line":977},[679,21004,21005],{"class":693}," taskService.",[679,21007,7629],{"class":880},[679,21009,1234],{"class":693},[679,21011,8930],{"class":685},[679,21013,21014],{"class":880}," Task",[679,21016,745],{"class":693},[679,21018,21019],{"class":931},"1L",[679,21021,1202],{"class":693},[679,21023,21024],{"class":689},"\"Create Spring Boot Application\"",[679,21026,1202],{"class":693},[679,21028,3441],{"class":931},[679,21030,1669],{"class":693},[679,21032,21033,21035,21037,21039,21041,21043,21045,21048,21050,21053,21055,21057],{"class":681,"line":982},[679,21034,21005],{"class":693},[679,21036,7629],{"class":880},[679,21038,1234],{"class":693},[679,21040,8930],{"class":685},[679,21042,21014],{"class":880},[679,21044,745],{"class":693},[679,21046,21047],{"class":931},"2L",[679,21049,1202],{"class":693},[679,21051,21052],{"class":689},"\"Create Angular 2 Application\"",[679,21054,1202],{"class":693},[679,21056,3441],{"class":931},[679,21058,1669],{"class":693},[679,21060,21061,21063,21065,21067,21069,21071,21073,21076,21078,21081,21083,21085],{"class":681,"line":988},[679,21062,21005],{"class":693},[679,21064,7629],{"class":880},[679,21066,1234],{"class":693},[679,21068,8930],{"class":685},[679,21070,21014],{"class":880},[679,21072,745],{"class":693},[679,21074,21075],{"class":931},"3L",[679,21077,1202],{"class":693},[679,21079,21080],{"class":689},"\"Run the demo application\"",[679,21082,1202],{"class":693},[679,21084,3441],{"class":931},[679,21086,1669],{"class":693},[679,21088,21089,21091,21093,21095,21097,21099,21101,21104,21106,21109,21111,21113],{"class":681,"line":993},[679,21090,21005],{"class":693},[679,21092,7629],{"class":880},[679,21094,1234],{"class":693},[679,21096,8930],{"class":685},[679,21098,21014],{"class":880},[679,21100,745],{"class":693},[679,21102,21103],{"class":931},"4L",[679,21105,2797],{"class":693},[679,21107,21108],{"class":689},"\"Make 1 Million Dollars\"",[679,21110,2797],{"class":693},[679,21112,1135],{"class":931},[679,21114,1669],{"class":693},[679,21116,21117],{"class":681,"line":2129},[679,21118,17018],{"class":693},[679,21120,21121],{"class":681,"line":2140},[679,21122,985],{"class":693},[679,21124,21125],{"class":681,"line":2145},[679,21126,996],{"class":693},[651,21128,21129],{},"There is a little more going on but that is essentially our back end service. If you want to learn more about Spring Boot please check out some of these resources. ",[5909,21131,18428],{"id":21132},"spring-boot-resources",[5316,21134,21135,21141,21148,21155],{},[5332,21136,21137],{},[812,21138,21140],{"href":17913,"rel":21139},[816],"Spring Boot Introduction Course",[5332,21142,21143],{},[812,21144,21147],{"href":21145,"rel":21146},"https://projects.spring.io/spring-boot/",[816],"Spring Boot Project Page",[5332,21149,21150],{},[812,21151,21154],{"href":21152,"rel":21153},"http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/",[816],"Spring Boot Reference Guide",[5332,21156,21157],{},[812,21158,21161],{"href":21159,"rel":21160},"http://docs.spring.io/spring-boot/docs/current/api/",[816],"Spring Boot API",[4542,21163,21165],{"id":21164},"angular-24","Angular 2/4",[651,21167,21168],{},"The first thing you need to know is that Angular 1/2/4 JS is just going by Angular these days. So when I refer to the newest version of Angular from now on, I will just be calling it Angular. As I said before I was never a huge fan of previous versions of Angular. With the new releases, writing components in TypeScript and the Angular CLI this is really a development cycle I can get behind. The 1st thing I would do is work on getting the Angular CLI installed and your local development environment up and running. From there you can generate your first component. Everything is component based in Angular and here is my TypeScript component that I really enjoyed writing. ",[669,21170,21172],{"className":4107,"code":21171,"language":4109,"meta":674,"style":674},"import { Component, OnInit } from '@angular/core';\nimport {Task} from \"../task.model\";\nimport {TaskService} from \"../task.service\";\n\n@Component({\n selector: 'app-tasks-list',\n templateUrl: './tasks-list.component.html',\n styleUrls: \\['./tasks-list.component.css'\\]\n})\nexport class TasksListComponent implements OnInit {\n tasks: Task\\[\\] = \\[\\];\n constructor(private taskService: TaskService) {\n // fetch our tasks from our Spring Boot Application\n taskService.getTasks()\n .subscribe(\n (tasks: any\\[\\]) => this.tasks = tasks,\n (error) => console.log(error),\n () => console.log('Task Service completed.')\n );\n }\n\n ngOnInit() {\n }\n\n getTaskClass(task: Task){\n let completed: string = 'list-group-item list-group-item-success';\n let incomplete: string = 'list-group-item list-group-item-danger';\n return task.completed ? completed : incomplete;\n }\n}\n",[676,21173,21174,21181,21193,21205,21209,21219,21231,21243,21257,21261,21278,21292,21312,21317,21327,21338,21362,21378,21396,21401,21406,21410,21417,21421,21425,21441,21458,21474,21492,21496],{"__ignoreMap":674},[679,21175,21176,21178],{"class":681,"line":682},[679,21177,1999],{"class":685},[679,21179,21180],{"class":693}," { Component, OnInit } from '@angular/core';\n",[679,21182,21183,21185,21188,21190],{"class":681,"line":790},[679,21184,1999],{"class":685},[679,21186,21187],{"class":693}," {Task} from \".",[679,21189,664],{"class":6561},[679,21191,21192],{"class":693},"/task.model\";\n",[679,21194,21195,21197,21200,21202],{"class":681,"line":892},[679,21196,1999],{"class":685},[679,21198,21199],{"class":693}," {TaskService} from \".",[679,21201,664],{"class":6561},[679,21203,21204],{"class":693},"/task.service\";\n",[679,21206,21207],{"class":681,"line":901},[679,21208,889],{"emptyLinePlaceholder":797},[679,21210,21211,21213,21216],{"class":681,"line":909},[679,21212,4116],{"class":693},[679,21214,21215],{"class":685},"Component",[679,21217,21218],{"class":693},"({\n",[679,21220,21221,21224,21226,21229],{"class":681,"line":918},[679,21222,21223],{"class":693}," selector",[679,21225,2391],{"class":685},[679,21227,21228],{"class":689}," 'app-tasks-list'",[679,21230,12083],{"class":693},[679,21232,21233,21236,21238,21241],{"class":681,"line":935},[679,21234,21235],{"class":693}," templateUrl",[679,21237,2391],{"class":685},[679,21239,21240],{"class":689}," './tasks-list.component.html'",[679,21242,12083],{"class":693},[679,21244,21245,21248,21250,21252,21255],{"class":681,"line":944},[679,21246,21247],{"class":693}," styleUrls",[679,21249,2391],{"class":685},[679,21251,1411],{"class":693},[679,21253,21254],{"class":689},"'./tasks-list.component.css'",[679,21256,1720],{"class":693},[679,21258,21259],{"class":681,"line":959},[679,21260,6240],{"class":693},[679,21262,21263,21266,21268,21271,21273,21276],{"class":681,"line":964},[679,21264,21265],{"class":693},"export ",[679,21267,877],{"class":685},[679,21269,21270],{"class":880}," TasksListComponent",[679,21272,4661],{"class":685},[679,21274,21275],{"class":880}," OnInit",[679,21277,884],{"class":693},[679,21279,21280,21283,21285,21288,21290],{"class":681,"line":977},[679,21281,21282],{"class":693}," tasks",[679,21284,2391],{"class":685},[679,21286,21287],{"class":693}," Task\\[\\] ",[679,21289,686],{"class":685},[679,21291,1866],{"class":693},[679,21293,21294,21297,21299,21302,21305,21307,21310],{"class":681,"line":982},[679,21295,21296],{"class":880}," constructor",[679,21298,745],{"class":693},[679,21300,21301],{"class":2099},"private",[679,21303,21304],{"class":2099}," taskService",[679,21306,4282],{"class":693},[679,21308,21309],{"class":2099},"TaskService",[679,21311,4390],{"class":693},[679,21313,21314],{"class":681,"line":988},[679,21315,21316],{"class":1400}," // fetch our tasks from our Spring Boot Application\n",[679,21318,21319,21322,21325],{"class":681,"line":993},[679,21320,21321],{"class":693}," taskService.",[679,21323,21324],{"class":880},"getTasks",[679,21326,17545],{"class":693},[679,21328,21329,21332,21335],{"class":681,"line":2129},[679,21330,21331],{"class":693}," .",[679,21333,21334],{"class":880},"subscribe",[679,21336,21337],{"class":693},"(\n",[679,21339,21340,21343,21345,21348,21351,21354,21357,21359],{"class":681,"line":2140},[679,21341,21342],{"class":693}," (tasks",[679,21344,2391],{"class":685},[679,21346,21347],{"class":693}," any\\[\\]) ",[679,21349,21350],{"class":685},"=>",[679,21352,21353],{"class":931}," this",[679,21355,21356],{"class":693},".tasks ",[679,21358,686],{"class":685},[679,21360,21361],{"class":693}," tasks,\n",[679,21363,21364,21367,21369,21372,21375],{"class":681,"line":2145},[679,21365,21366],{"class":693}," (error) ",[679,21368,21350],{"class":685},[679,21370,21371],{"class":693}," console.",[679,21373,21374],{"class":880},"log",[679,21376,21377],{"class":693},"(error),\n",[679,21379,21380,21383,21385,21387,21389,21391,21394],{"class":681,"line":2154},[679,21381,21382],{"class":693}," () ",[679,21384,21350],{"class":685},[679,21386,21371],{"class":693},[679,21388,21374],{"class":880},[679,21390,745],{"class":693},[679,21392,21393],{"class":689},"'Task Service completed.'",[679,21395,1339],{"class":693},[679,21397,21398],{"class":681,"line":2159},[679,21399,21400],{"class":693}," );\n",[679,21402,21403],{"class":681,"line":2164},[679,21404,21405],{"class":693}," }\n",[679,21407,21408],{"class":681,"line":3134},[679,21409,889],{"emptyLinePlaceholder":797},[679,21411,21412,21415],{"class":681,"line":3139},[679,21413,21414],{"class":880}," ngOnInit",[679,21416,2667],{"class":693},[679,21418,21419],{"class":681,"line":3144},[679,21420,21405],{"class":693},[679,21422,21423],{"class":681,"line":3149},[679,21424,889],{"emptyLinePlaceholder":797},[679,21426,21427,21430,21432,21435,21437,21439],{"class":681,"line":3169},[679,21428,21429],{"class":880}," getTaskClass",[679,21431,745],{"class":693},[679,21433,21434],{"class":2099},"task",[679,21436,4282],{"class":693},[679,21438,20878],{"class":2099},[679,21440,9533],{"class":693},[679,21442,21443,21446,21448,21451,21453,21456],{"class":681,"line":3185},[679,21444,21445],{"class":693}," let completed",[679,21447,2391],{"class":685},[679,21449,21450],{"class":693}," string ",[679,21452,686],{"class":685},[679,21454,21455],{"class":689}," 'list-group-item list-group-item-success'",[679,21457,1186],{"class":693},[679,21459,21460,21463,21465,21467,21469,21472],{"class":681,"line":3194},[679,21461,21462],{"class":693}," let incomplete",[679,21464,2391],{"class":685},[679,21466,21450],{"class":693},[679,21468,686],{"class":685},[679,21470,21471],{"class":689}," 'list-group-item list-group-item-danger'",[679,21473,1186],{"class":693},[679,21475,21476,21479,21482,21484,21487,21489],{"class":681,"line":3199},[679,21477,21478],{"class":685}," return",[679,21480,21481],{"class":693}," task.completed ",[679,21483,2381],{"class":685},[679,21485,21486],{"class":693}," completed ",[679,21488,2391],{"class":685},[679,21490,21491],{"class":693}," incomplete;\n",[679,21493,21494],{"class":681,"line":3212},[679,21495,21405],{"class":693},[679,21497,21498],{"class":681,"line":3217},[679,21499,996],{"class":693},[651,21501,21502],{},"I simply can't teach you everything you need to know about Angular in this blog post and that is the reason I being pretty vague here. I am going to leave you with some resources and my screencast below but please feel free to ask any questions you have. ",[5909,21504,21506],{"id":21505},"angular-resources","Angular Resources",[5316,21508,21509,21516,21523,21530,21537],{},[5332,21510,21511],{},[812,21512,21515],{"href":21513,"rel":21514},"https://angularjs.org/",[816],"Angular JS",[5332,21517,21518],{},[812,21519,21522],{"href":21520,"rel":21521},"https://cli.angular.io/",[816],"Angular CLI",[5332,21524,21525],{},[812,21526,21529],{"href":21527,"rel":21528},"http://amzn.to/2qrYPPd",[816],"Angular 2 Development with TypeScript",[5332,21531,21532],{},[812,21533,21536],{"href":21534,"rel":21535},"http://amzn.to/2qElhRq",[816],"ng-book 2: The Complete Book on Angular 2",[5332,21538,21539],{},[812,21540,21543],{"href":21541,"rel":21542},"https://www.udemy.com/the-complete-guide-to-angular-2/",[816],"Angular 4: The Complete Guide (Online Course)",[4542,21545,21547],{"id":21546},"spring-boot-angular-tasks-application","Spring Boot & Angular Tasks Application",[651,21549,21550],{},[812,21551,21554],{"href":21552,"rel":21553},"https://www.youtube.com/watch?v=v7X%5C_ZHdcNvc&t=25s",[816],"https://www.youtube.com/watch?v=v7X\\_ZHdcNvc&t=25s",[4542,21556,17995],{"id":17994},[651,21558,21559,21560,21564],{},"I am currently working on a course that teaches you all about Spring Boot & Angular 4 development. We will show you how to get started and what it takes to build these projects from scratch. Then we will walk through an even better way to rapidly develop these applications so you can focus on writing business logic and not boilerplate code. If you're interested in learning more please head over to the ",[812,21561,21563],{"href":18009,"rel":21562},[816],"course page"," and signup for free. ",[651,21566,13453,21567,21569],{},[2939,21568,11650],{}," What are your biggest challenges in developing Spring Boot & Angular applications? _",[786,21571,21572],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":21574},[21575,21576,21579,21582,21583],{"id":20730,"depth":790,"text":20731},{"id":20742,"depth":790,"text":7077,"children":21577},[21578],{"id":21132,"depth":892,"text":18428},{"id":21164,"depth":790,"text":21165,"children":21580},[21581],{"id":21505,"depth":892,"text":21506},{"id":21546,"depth":790,"text":21547},{"id":17994,"depth":790,"text":17995},{"slug":21585,"published":797,"date":21586,"tags":21587,"cover":21588},"spring-angular-applications","2017-05-03T09:00:37-04:00",[17866,7055],"./carl-heyerdahl-181868-760x507.jpg",{"title":186,"description":186},"blog/2017/05/03/spring-angular-applications","EB4fW0T5uNspWJ-eH4aXvt7Qm0foDvjxhubUbIh7sDk",{"id":21593,"title":183,"body":21594,"description":183,"extension":793,"meta":22013,"navigation":797,"path":184,"seo":22019,"stem":22020,"__hash__":22021},"content/blog/2017/05/05/make-weakness-strength.md",{"type":648,"value":21595,"toc":22003},[21596,21599,21603,21606,21610,21613,21642,21645,21649,21652,21663,21672,21676,21764,21769,21777,21781,21800,21897,21901,21907,21921,21925,21928,21932,21935,21939,21942,21948,21954,21957,21965,21970,21974,21988,21990,21993,22000],[651,21597,21598],{},"I have been writing software for a fairly long time now. I have worked on applications that range from open source projects to a suite of applications that run a $5B dollar company. If you want to stay relevant in this industry though you have always got to be learning and improving your craft. One of the best things you can do is identify an area of weakness and work on improving it. A friend of mine is preparing for an interview next week and asked for some help. I haven't been on an actual coding interview in probably 10 years so I might not be the best person to ask but, what are friends for. In this article, we are going to look at some exercises he was trying to tackle. These questions led me to realize that I have a weakness that I would like to improve on. ",[4542,21600,21602],{"id":21601},"the-interview","The Interview",[651,21604,21605],{},"In preparing for his interview he broke down his preparation into 2 different categories, language material, and problem sets. ",[5909,21607,21609],{"id":21608},"language-material","Language Material",[651,21611,21612],{},"My advice here is to simply break down the primary language into several categories and write a bunch of questions down. If you can write down about 5-10 questions in each of the following categories and understand who you want to answer them you will be fine. ",[5316,21614,21615,21618,21621,21624,21627,21630,21633,21636,21639],{},[5332,21616,21617],{},"Java Basics ",[5332,21619,21620],{},"Java Classes",[5332,21622,21623],{},"Object Oriented Programming (OOP)",[5332,21625,21626],{},"Java Collections",[5332,21628,21629],{},"Threads",[5332,21631,21632],{},"Exception Handling ",[5332,21634,21635],{},"Garbage Collection",[5332,21637,21638],{},"Java 8",[5332,21640,21641],{},"Java 9",[651,21643,21644],{},"The main point of this is to not script answers. You should know these answers and you need to answer them in your own words, not some textbook definition. I also threw in Java 9 as a category for one simple reason. If they ask you if there is anything in the language that you are looking forward to this would be an opportunity for a huge win. If this comes up you can mention 1 or 2 of the new features in Java 9 and what excites you about them. This shows that you are constantly learning and that you are passionate about the language you use. ",[5909,21646,21648],{"id":21647},"coding-exercises","Coding Exercises",[651,21650,21651],{},"In the form of exercises, I thought that it would look something like this.",[5316,21653,21654,21657,21660],{},[5332,21655,21656],{},"Create an interface that has at least 1 default method and then implements that interface",[5332,21658,21659],{},"Construct a class that shows the difference between overloading and overriding ",[5332,21661,21662],{},"Construct a class to read in a directory and then filter on the filename using a lambda.",[651,21664,21665,21666,21671],{},"When I sent him a bunch of examples like this he was a little thrown off and rightfully so. Again, I haven't interviewed in 10 years so I was just throwing out things that I might use to test someone's knowledge. He was looking for more algorithm based problem sets. Interviews are apparently much more likely to throw someone up on the whiteboard and give them problem sets to solve. This accomplishes 2 things, you get an idea of how someone solves a problem and you can watch their thought process and how they handle a stressful situation. After walking through some examples on ",[812,21667,21670],{"href":21668,"rel":21669},"https://leetcode.com/problemset/algorithms/",[816],"LeetCode"," I found 3 pretty good examples to share with you. If you want to try and go through these remember that you have no IDE to help out and its just you and whiteboard talking out your solution. ",[5259,21673,21675],{"id":21674},"intersection-of-two-arrays","Intersection of Two Arrays",[669,21677,21679],{"className":5851,"code":21678,"language":5853,"meta":674,"style":674},"Given two arrays, write a function to compute their intersection. **Example:**\n\nGiven nums1 = [1, 2, 2, 1], nums2 = [2, 2], return [2].\n",[676,21680,21681,21722,21726],{"__ignoreMap":674},[679,21682,21683,21686,21689,21692,21695,21698,21701,21704,21707,21710,21713,21716,21719],{"class":681,"line":682},[679,21684,21685],{"class":880},"Given",[679,21687,21688],{"class":689}," two",[679,21690,21691],{"class":689}," arrays,",[679,21693,21694],{"class":689}," write",[679,21696,21697],{"class":689}," a",[679,21699,21700],{"class":689}," function",[679,21702,21703],{"class":689}," to",[679,21705,21706],{"class":689}," compute",[679,21708,21709],{"class":689}," their",[679,21711,21712],{"class":689}," intersection.",[679,21714,21715],{"class":931}," **",[679,21717,21718],{"class":689},"Example:",[679,21720,21721],{"class":931},"**\n",[679,21723,21724],{"class":681,"line":790},[679,21725,889],{"emptyLinePlaceholder":797},[679,21727,21728,21730,21733,21735,21738,21741,21744,21747,21750,21752,21755,21758,21761],{"class":681,"line":892},[679,21729,21685],{"class":880},[679,21731,21732],{"class":689}," nums1",[679,21734,6883],{"class":689},[679,21736,21737],{"class":693}," [1, ",[679,21739,21740],{"class":689},"2,",[679,21742,21743],{"class":689}," 2,",[679,21745,21746],{"class":689}," 1],",[679,21748,21749],{"class":689}," nums2",[679,21751,6883],{"class":689},[679,21753,21754],{"class":693}," [2, ",[679,21756,21757],{"class":689},"2],",[679,21759,21760],{"class":689}," return",[679,21762,21763],{"class":693}," [2].\n",[651,21765,21766],{},[2939,21767,21768],{},"Note:",[5316,21770,21771,21774],{},[5332,21772,21773],{},"Each element in the result must be unique.",[5332,21775,21776],{},"The result can be in any order.",[5259,21778,21780],{"id":21779},"two-sum","Two Sum",[651,21782,21783,21784,21787,21788,21793,21794,21797,21798],{},"Given an array of integers, return ",[2939,21785,21786],{},"indices"," of the two numbers such that they add up to a specific target. You may assume that each input would have ",[2939,21789,21790],{},[7300,21791,21792],{},"exactly"," one solution, and you may not use the ",[7300,21795,21796],{},"same"," element twice. ",[2939,21799,21718],{},[669,21801,21803],{"className":5851,"code":21802,"language":5853,"meta":674,"style":674},"Given nums = [2, 7, 11, 15], target = 9,\n\nBecause nums[**0**] + nums[**1**] = 2 + 7 = 9,\nreturn [**0**, **1**].\n",[676,21804,21805,21833,21837,21881],{"__ignoreMap":674},[679,21806,21807,21809,21812,21814,21816,21819,21822,21825,21828,21830],{"class":681,"line":682},[679,21808,21685],{"class":880},[679,21810,21811],{"class":689}," nums",[679,21813,6883],{"class":689},[679,21815,21754],{"class":693},[679,21817,21818],{"class":689},"7,",[679,21820,21821],{"class":689}," 11,",[679,21823,21824],{"class":689}," 15],",[679,21826,21827],{"class":689}," target",[679,21829,6883],{"class":689},[679,21831,21832],{"class":689}," 9,\n",[679,21834,21835],{"class":681,"line":790},[679,21836,889],{"emptyLinePlaceholder":797},[679,21838,21839,21842,21845,21848,21850,21852,21855,21857,21859,21861,21863,21865,21867,21869,21872,21874,21877,21879],{"class":681,"line":892},[679,21840,21841],{"class":880},"Because",[679,21843,21844],{"class":689}," nums[",[679,21846,21847],{"class":931},"**",[679,21849,1060],{"class":689},[679,21851,21847],{"class":931},[679,21853,21854],{"class":689},"]",[679,21856,3059],{"class":689},[679,21858,21844],{"class":689},[679,21860,21847],{"class":931},[679,21862,1557],{"class":689},[679,21864,21847],{"class":931},[679,21866,21854],{"class":689},[679,21868,6883],{"class":689},[679,21870,21871],{"class":931}," 2",[679,21873,3059],{"class":689},[679,21875,21876],{"class":931}," 7",[679,21878,6883],{"class":689},[679,21880,21832],{"class":689},[679,21882,21883,21885,21888,21890,21892,21894],{"class":681,"line":901},[679,21884,1307],{"class":685},[679,21886,21887],{"class":693}," [**0**, ",[679,21889,21847],{"class":931},[679,21891,1557],{"class":689},[679,21893,21847],{"class":931},[679,21895,21896],{"class":689},"].\n",[5259,21898,21900],{"id":21899},"palindrome-number","Palindrome Number",[651,21902,21903,21904],{},"Determine whether an integer is a palindrome. Do this without extra space. ",[2939,21905,21906],{},"Some Hints:",[5316,21908,21909,21912,21915,21918],{},[5332,21910,21911],{},"Could negative integers be palindromes? (ie, -1)",[5332,21913,21914],{},"If you are thinking of converting the integer to string, note the restriction of using extra space.",[5332,21916,21917],{},"You could also try reversing an integer. However, if you have solved the problem \"Reverse Integer\", you know that the reversed integer might overflow. How would you handle such case?",[5332,21919,21920],{},"There is a more generic way of solving this problem.",[4542,21922,21924],{"id":21923},"make-your-weakness-a-strength","Make your weakness a strength",[651,21926,21927],{},"After going through some of these problem sets I realized that I have a real weakness. Now, some of this stuff I won't ever use in my day to day job. I don't need to learn how to manually sort a list of objects because these are methods are given to me in the Java API. This, on the other hand, isn't an excuse not to understand how to solve problems that I was pretty good at in college. I also realize that the ability to break down a problem and solve it is what I do on a day to day basis so getting better at that can only help me out. ",[5909,21929,21931],{"id":21930},"resources","Resources",[651,21933,21934],{},"Now that I realized this is something I wanted to improve on I need get to work. I learn best when I have a variety of resources to learn from. In this case, I found a book, a website and a set of lectures that I think are really going to help out. ",[5259,21936,21938],{"id":21937},"algorithms-fourth-edition","Algorithms (Fourth Edition)",[651,21940,21941],{},"I decided to pick up Algorithms (Fourth Edition) by Robert Sedgewick. This book not only had a ton of great reviews but all of the examples in the book are in Java. With that said I am going to try and focus on solving the problem and not the language because that is the point of this. ",[651,21943,21944],{},[660,21945],{"alt":21946,"src":21947},"Alogrithms","./2017-05-04_08-36-00.png",[651,21949,21950],{},[812,21951,21952],{"href":21952,"rel":21953},"http://amzn.to/2pBWwbf",[816],[5259,21955,21670],{"id":21956},"leetcode",[651,21958,21959,21960,21964],{},"Another great resource that we mentioned earlier was ",[812,21961,21670],{"href":21962,"rel":21963},"https://leetcode.com",[816],". This website gives you some awesome problem sets to solve. You can solve the problem yourself and then look at other solutions and discussions around these problems. ",[651,21966,21967],{},[660,21968],{"alt":21670,"src":21969},"./2017-05-04_09-00-39-291x300.png",[5259,21971,21973],{"id":21972},"courses","Courses",[651,21975,21976,21981,21982,21987],{},[812,21977,21980],{"href":21978,"rel":21979},"https://www.coursera.org/specializations/algorithms?action=enroll",[816],"Intro to Algorithms"," This course looks like it is really well designed. If I wasn't planning for a wedding, bachelor party and honeymoon I think this would be the course I would want to jump into. I won't be able to take this one but I did want to share it as a resource with you. ",[812,21983,21986],{"href":21984,"rel":21985},"https://www.udemy.com/practical-data-structures-algorithms-in-java/",[816],"Practical Data Structures & Algorithms in Java"," I decided to enroll in this course because it had a really good amount of lectures on both data structures and algorithms. The combination of my new course, book and a list of problem sets is going to help me get to where I want to be. ",[4542,21989,9042],{"id":9041},[651,21991,21992],{},"There is nothing wrong with having weaknesses. We all have them and anyone who tells you they don't is probably lying. If you want to be better you need to identify your weakness and instead of hanging your head, make it your strength. I hope to have more posts and videos to follow on this so stay tuned! ",[651,21994,21995],{},[7300,21996,21997,21999],{},[2939,21998,11650],{}," What are your weaknesses and what are you doing to improve them?",[786,22001,22002],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}",{"title":674,"searchDepth":790,"depth":790,"links":22004},[22005,22009,22012],{"id":21601,"depth":790,"text":21602,"children":22006},[22007,22008],{"id":21608,"depth":892,"text":21609},{"id":21647,"depth":892,"text":21648},{"id":21923,"depth":790,"text":21924,"children":22010},[22011],{"id":21930,"depth":892,"text":21931},{"id":9041,"depth":790,"text":9042},{"slug":22014,"published":797,"date":22015,"tags":22016,"cover":22018},"make-weakness-strength","2017-05-05T08:00:15-04:00",[22017],"Software Development","./lucas-rosas-98304-760x507.jpg",{"title":183,"description":183},"blog/2017/05/05/make-weakness-strength","ULwehthuvLpWe49RHpNo8EEBSUbtzmP8JTH5z6ldEDs",{"id":22023,"title":180,"body":22024,"description":180,"extension":793,"meta":22084,"navigation":797,"path":181,"seo":22089,"stem":22090,"__hash__":22091},"content/blog/2017/05/08/enable-new-youtube-dark-theme-right-now.md",{"type":648,"value":22025,"toc":22082},[22026,22034,22040,22043,22049,22052,22058,22061,22067,22076],[651,22027,22028,22029,22033],{},"YouTube is working on rolling out a new design for their desktop application. This new design is cleaner, easier to use and gives channels a brand new look. All that is great but what really has me excited is the all new dark theme. They are still in the process of testing this out but in this article, I will show you how you can take advantage of this today. The 1st thing you need to do is go to ",[812,22030,22031],{"href":22031,"rel":22032},"http://www.youtube.com/new",[816]," and click try it now to opt-in to the new design. ",[651,22035,22036],{},[660,22037],{"alt":22038,"src":22039},"Youtube Fresh Look","./2017-05-04_23-03-41-1024x495.png",[651,22041,22042],{},"The first thing you will notice is that the three tabs across the top are gone. We also have a cleaner tighter look along with some larger typeface across the board. ",[651,22044,22045],{},[660,22046],{"alt":22047,"src":22048},"Youtube Settings","./2017-05-04_23-04-38-1024x495.png",[651,22050,22051],{},"To enable dark mode simply click on your profile icon in the upper right hand corner and select Dark Theme: Off. ",[651,22053,22054],{},[660,22055],{"alt":22056,"src":22057},"Enable Dark Mode","./2017-05-04_23-05-10.png",[651,22059,22060],{},"Finally, click on the activate dark theme. Welcome to the party! ",[651,22062,22063],{},[660,22064],{"alt":22065,"src":22066},"Youtube Dark Mode","./2017-05-04_23-05-38-1024x495.png",[651,22068,22069,22070,22075],{},"This is what the new channel view looks like for ",[812,22071,22074],{"href":22072,"rel":22073},"http://www.youtube.com/therealdanvega",[816],"mine"," in dark mode.",[651,22077,22078],{},[660,22079],{"alt":22080,"src":22081},"Account- Youtube Dark Mode","./2017-05-05_07-40-20-1024x495.png",{"title":674,"searchDepth":790,"depth":790,"links":22083},[],{"slug":22085,"published":797,"date":22086,"tags":22087,"cover":22088},"enable-new-youtube-dark-theme-right-now","2017-05-08T08:00:50-04:00",[15432],"./2017-05-04_23-05-38-760x367.png",{"title":180,"description":180},"blog/2017/05/08/enable-new-youtube-dark-theme-right-now","p5RhfkOxj0qyTMVNlYecdqh1jp_y3eDYqRmzOpYJ1kQ",{"id":22093,"title":177,"body":22094,"description":177,"extension":793,"meta":22463,"navigation":797,"path":178,"seo":22468,"stem":22469,"__hash__":22470},"content/blog/2017/05/10/spring-boot-moving-tomcat-jetty.md",{"type":648,"value":22095,"toc":22459},[22096,22104,22109,22112,22116,22119,22125,22128,22246,22249,22300,22303,22325,22446,22448,22451,22456],[651,22097,22098,22099,22103],{},"I love getting questions from students because it gives me a great idea of the real world problems people are trying to solve. Today's question comes from a student in my ",[812,22100,21140],{"href":22101,"rel":22102},"https://danvega.dev/spring-boot",[816]," and has to do with using something other than the default. ",[1004,22105,22106],{},[651,22107,22108],{},"When I start a new Spring Boot Project and I include the web dependency I get Tomcat as the default servlet container. If I want to change that to something else like Jetty can I?",[651,22110,22111],{},"The great thing about Spring Boot is that while it comes with some sensible defaults to get you up and running it doesn't stand in your way if you need to change something. The answer to this question is Yes and in this short tutorial, we will look at how you can do this.",[4542,22113,22115],{"id":22114},"replacing-tomcat-with-jetty","Replacing Tomcat with Jetty",[651,22117,22118],{},"If we create a new Spring Boot project and select web as a dependency the default servlet container is going to be Tomcat. ",[651,22120,22121],{},[660,22122],{"alt":22123,"src":22124},"Tomcat with Jetty","./2017-05-10_08-32-05-1024x645.png",[651,22126,22127],{},"The reason we know this is because if you dive into the spring-boot-starter-web dependency you will see the following dependencies declared. ",[669,22129,22131],{"className":9101,"code":22130,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-tomcat\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.hibernate\u003C/groupId>\n \u003CartifactId>hibernate-validator\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>com.fasterxml.jackson.core\u003C/groupId>\n \u003CartifactId>jackson-databind\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework\u003C/groupId>\n \u003CartifactId>spring-web\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework\u003C/groupId>\n \u003CartifactId>spring-webmvc\u003C/artifactId>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,22132,22133,22137,22141,22145,22150,22154,22158,22162,22167,22171,22175,22180,22185,22189,22193,22198,22203,22207,22211,22216,22221,22225,22229,22233,22238,22242],{"__ignoreMap":674},[679,22134,22135],{"class":681,"line":682},[679,22136,19199],{},[679,22138,22139],{"class":681,"line":790},[679,22140,19204],{},[679,22142,22143],{"class":681,"line":892},[679,22144,19209],{},[679,22146,22147],{"class":681,"line":901},[679,22148,22149],{}," \u003CartifactId>spring-boot-starter\u003C/artifactId>\n",[679,22151,22152],{"class":681,"line":909},[679,22153,19219],{},[679,22155,22156],{"class":681,"line":918},[679,22157,19204],{},[679,22159,22160],{"class":681,"line":935},[679,22161,19209],{},[679,22163,22164],{"class":681,"line":944},[679,22165,22166],{}," \u003CartifactId>spring-boot-starter-tomcat\u003C/artifactId>\n",[679,22168,22169],{"class":681,"line":959},[679,22170,19219],{},[679,22172,22173],{"class":681,"line":964},[679,22174,19204],{},[679,22176,22177],{"class":681,"line":977},[679,22178,22179],{}," \u003CgroupId>org.hibernate\u003C/groupId>\n",[679,22181,22182],{"class":681,"line":982},[679,22183,22184],{}," \u003CartifactId>hibernate-validator\u003C/artifactId>\n",[679,22186,22187],{"class":681,"line":988},[679,22188,19219],{},[679,22190,22191],{"class":681,"line":993},[679,22192,19204],{},[679,22194,22195],{"class":681,"line":2129},[679,22196,22197],{}," \u003CgroupId>com.fasterxml.jackson.core\u003C/groupId>\n",[679,22199,22200],{"class":681,"line":2140},[679,22201,22202],{}," \u003CartifactId>jackson-databind\u003C/artifactId>\n",[679,22204,22205],{"class":681,"line":2145},[679,22206,19219],{},[679,22208,22209],{"class":681,"line":2154},[679,22210,19204],{},[679,22212,22213],{"class":681,"line":2159},[679,22214,22215],{}," \u003CgroupId>org.springframework\u003C/groupId>\n",[679,22217,22218],{"class":681,"line":2164},[679,22219,22220],{}," \u003CartifactId>spring-web\u003C/artifactId>\n",[679,22222,22223],{"class":681,"line":3134},[679,22224,19219],{},[679,22226,22227],{"class":681,"line":3139},[679,22228,19204],{},[679,22230,22231],{"class":681,"line":3144},[679,22232,22215],{},[679,22234,22235],{"class":681,"line":3149},[679,22236,22237],{}," \u003CartifactId>spring-webmvc\u003C/artifactId>\n",[679,22239,22240],{"class":681,"line":3169},[679,22241,19219],{},[679,22243,22244],{"class":681,"line":3185},[679,22245,19363],{},[651,22247,22248],{},"The first thing we need to do is remove the Tomcat dependency. We are going to do this in the starter dependency itself so we need some way of doing this in our Maven POM. Luckily we have a way to exclude a dependency using an exclusions block. ",[669,22250,22252],{"className":9101,"code":22251,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n \u003Cexclusions>\n \u003Cexclusion>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-tomcat\u003C/artifactId>\n \u003C/exclusion>\n \u003C/exclusions>\n\u003C/dependency>\n",[676,22253,22254,22258,22262,22266,22271,22276,22281,22286,22291,22296],{"__ignoreMap":674},[679,22255,22256],{"class":681,"line":682},[679,22257,9110],{},[679,22259,22260],{"class":681,"line":790},[679,22261,9115],{},[679,22263,22264],{"class":681,"line":892},[679,22265,9138],{},[679,22267,22268],{"class":681,"line":901},[679,22269,22270],{}," \u003Cexclusions>\n",[679,22272,22273],{"class":681,"line":909},[679,22274,22275],{}," \u003Cexclusion>\n",[679,22277,22278],{"class":681,"line":918},[679,22279,22280],{}," \u003CgroupId>org.springframework.boot\u003C/groupId>\n",[679,22282,22283],{"class":681,"line":935},[679,22284,22285],{}," \u003CartifactId>spring-boot-starter-tomcat\u003C/artifactId>\n",[679,22287,22288],{"class":681,"line":944},[679,22289,22290],{}," \u003C/exclusion>\n",[679,22292,22293],{"class":681,"line":959},[679,22294,22295],{}," \u003C/exclusions>\n",[679,22297,22298],{"class":681,"line":964},[679,22299,9125],{},[651,22301,22302],{},"Now that we have excluded Tomcat we can add our Jetty dependency. ",[669,22304,22306],{"className":9101,"code":22305,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-jetty\u003C/artifactId>\n\u003C/dependency>\n",[676,22307,22308,22312,22316,22321],{"__ignoreMap":674},[679,22309,22310],{"class":681,"line":682},[679,22311,9110],{},[679,22313,22314],{"class":681,"line":790},[679,22315,9115],{},[679,22317,22318],{"class":681,"line":892},[679,22319,22320],{}," \u003CartifactId>spring-boot-starter-jetty\u003C/artifactId>\n",[679,22322,22323],{"class":681,"line":901},[679,22324,9125],{},[669,22326,22328],{"className":5851,"code":22327,"language":5853,"meta":674,"style":674},"If you go ahead and run the application you will see in the console that we are indeed now running on Jetty. \n\n2017-05-10 08:42:24.880 INFO 32757 --- [ main] .s.b.c.e.j.JettyEmbeddedServletContainer : Jetty started on port(s) 8080 (http/1.1)\n",[676,22329,22330,22393,22397],{"__ignoreMap":674},[679,22331,22332,22335,22338,22341,22344,22347,22349,22352,22355,22357,22360,22363,22365,22367,22370,22372,22375,22378,22381,22384,22387,22390],{"class":681,"line":682},[679,22333,22334],{"class":880},"If",[679,22336,22337],{"class":689}," you",[679,22339,22340],{"class":689}," go",[679,22342,22343],{"class":689}," ahead",[679,22345,22346],{"class":689}," and",[679,22348,16486],{"class":689},[679,22350,22351],{"class":689}," the",[679,22353,22354],{"class":689}," application",[679,22356,22337],{"class":689},[679,22358,22359],{"class":689}," will",[679,22361,22362],{"class":689}," see",[679,22364,6997],{"class":689},[679,22366,22351],{"class":689},[679,22368,22369],{"class":689}," console",[679,22371,6417],{"class":689},[679,22373,22374],{"class":689}," we",[679,22376,22377],{"class":689}," are",[679,22379,22380],{"class":689}," indeed",[679,22382,22383],{"class":689}," now",[679,22385,22386],{"class":689}," running",[679,22388,22389],{"class":689}," on",[679,22391,22392],{"class":689}," Jetty. \n",[679,22394,22395],{"class":681,"line":790},[679,22396,889],{"emptyLinePlaceholder":797},[679,22398,22399,22402,22405,22407,22410,22412,22415,22418,22421,22423,22426,22429,22431,22434,22436,22438,22440,22443],{"class":681,"line":892},[679,22400,22401],{"class":880},"2017-05-10",[679,22403,22404],{"class":689}," 08:42:24.880",[679,22406,18562],{"class":689},[679,22408,22409],{"class":931}," 32757",[679,22411,18568],{"class":931},[679,22413,22414],{"class":693}," [ ",[679,22416,22417],{"class":689},"main]",[679,22419,22420],{"class":689}," .s.b.c.e.j.JettyEmbeddedServletContainer",[679,22422,3033],{"class":689},[679,22424,22425],{"class":689}," Jetty",[679,22427,22428],{"class":689}," started",[679,22430,22389],{"class":689},[679,22432,22433],{"class":689}," port",[679,22435,745],{"class":693},[679,22437,20413],{"class":880},[679,22439,2378],{"class":693},[679,22441,22442],{"class":931},"8080",[679,22444,22445],{"class":693}," (http/1.1)\n",[4542,22447,9042],{"id":9041},[651,22449,22450],{},"I hope this really helps drive home a point about Spring Boot. There are conventions and defaults that help us quickly and easily stand up a new Spring Application. Just because this is the default though doesn't mean that we can change something to fit our needs. ",[651,22452,13453,22453,22455],{},[2939,22454,11650],{}," What problems are you facing in your Spring Applications? _",[786,22457,22458],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}",{"title":674,"searchDepth":790,"depth":790,"links":22460},[22461,22462],{"id":22114,"depth":790,"text":22115},{"id":9041,"depth":790,"text":9042},{"slug":22464,"published":797,"date":22465,"tags":22466,"cover":22467},"spring-boot-moving-tomcat-jetty","2017-05-10T09:00:06-04:00",[7055],"./emile-perron-190221-760x428.jpg",{"title":177,"description":177},"blog/2017/05/10/spring-boot-moving-tomcat-jetty","aeuzL3fXLaNuvQ9gWkIxSBjEFsTNTUhg1K5inkKXt4A",{"id":22472,"title":174,"body":22473,"description":174,"extension":793,"meta":22568,"navigation":797,"path":175,"seo":22573,"stem":22574,"__hash__":22575},"content/blog/2017/05/12/6-courses-itunes-u.md",{"type":648,"value":22474,"toc":22560},[22475,22478,22489,22492,22500,22503,22511,22514,22521,22524,22532,22535,22543,22546,22554,22557],[651,22476,22477],{},"When I hear people talk about taking online courses I hear the usual marketplaces mentioned. While these are great places to learn I find it a little odd that iTunes U is usually a place that people forget about. If you weren't aware iTunes U is the world's largest catalog of free education content. ",[5316,22479,22480,22483,22486],{},[5332,22481,22482],{},"Choose from more than 1 million free lectures, videos, books, and other resources on thousands of subjects from leading educational and cultural institutions around the world.",[5332,22484,22485],{},"Share your favorite courses with friends using Twitter, Facebook, Mail, and Messages.",[5332,22487,22488],{},"iTunes U includes materials from Stanford, Yale, MIT, Oxford, McGill University, La Trobe University, University of Tokyo, Museo Nacional del Prado, Smithsonian Libraries, National Theatre, Library of Congress, and much more.",[651,22490,22491],{},"In this article, I am going to give you 6 courses that can help you grow as a Software Developer. While some of the courses on iTunes U are a little out of date I believe these courses can be of help to you right now.",[4542,22493,22495],{"id":22494},"developer-ios-10-apps-with-swift",[812,22496,22499],{"href":22497,"rel":22498},"https://itunes.apple.com/us/course/developing-ios-10-apps-with-swift/id1198467120",[816],"Developer iOS 10 Apps with Swift",[651,22501,22502],{},"Tools and APIs required to build applications for the iPhone and iPad platforms using the iOS SDK. User interface design for mobile devices and unique user interactions using multi-touch technologies. Object-oriented design using model-view-controller paradigm, memory management, Swift programming language. Other topics include object-oriented database API, animation, mobile device power management, multi-threading, networking and performance considerations.",[4542,22504,22506],{"id":22505},"machine-learning",[812,22507,22510],{"href":22508,"rel":22509},"https://itunes.apple.com/us/itunes-u/machine-learning/id384233048?mt=10",[816],"Machine Learning",[651,22512,22513],{},"This course provides a broad introduction to machine learning and statistical pattern recognition. The course also discusses recent applications of machine learning, such as to robotic control, data mining, autonomous navigation, bioinformatics, speech recognition, and text and web data processing. Topics include supervised learning (generative/discriminative learning, parametric/non-parametric learning, neural networks, support vector machines); unsupervised learning (clustering, dimensionality reduction, kernel methods); learning theory (bias/variance tradeoffs; VC theory; large margins); reinforcement learning and adaptive control.",[4542,22515,22517],{"id":22516},"intro-to-algorithms",[812,22518,21980],{"href":22519,"rel":22520},"https://itunes.apple.com/us/course/introduction-to-algorithms/id495066198",[816],[651,22522,22523],{},"This course teaches techniques for the design and analysis of efficient algorithms, emphasizing methods useful in practice. Topics covered include: sorting; search trees, heaps, and hashing; divide-and-conquer; dynamic programming; amortized analysis; graph algorithms; shortest paths; network flow; computational geometry; number-theoretic algorithms; polynomial and matrix calculations; caching; and parallel computing. ",[4542,22525,22527],{"id":22526},"introduction-to-python",[812,22528,22531],{"href":22529,"rel":22530},"https://itunes.apple.com/us/course/introduction-to-python/id897854234",[816],"Introduction to Python",[651,22533,22534],{},"This course will show you the basics of Python and you will be able to create some basic programs using it. You will need Python, which can be downloaded from the Python website, and also easygui which can be downloaded from the easygui website!",[4542,22536,22538],{"id":22537},"introduction-to-java",[812,22539,22542],{"href":22540,"rel":22541},"https://itunes.apple.com/us/course/introduction-to-java/id551000192",[816],"Introduction to Java",[651,22544,22545],{},"An introduction to the fundamental features of the Java language. Topics include object-oriented programming, generics, collections, and I/O. ",[4542,22547,22549],{"id":22548},"this-is-cs50-2016",[812,22550,22553],{"href":22551,"rel":22552},"https://itunes.apple.com/us/course/this-is-cs50-2016/id1191487593",[816],"This is CS50 2016",[651,22555,22556],{},"\"Demanding, but definitely doable. Social, but educational. A focused topic, but broadly applicable skills. CS50 is the quintessential Harvard (and Yale!) course.\"",[651,22558,22559],{},"Introduction to the intellectual enterprises of computer science and the art of programming. This course teaches students how to think algorithmically and solve problems efficiently. Topics include abstraction, algorithms, data structures, encapsulation, resource management, security, software engineering, and web development. Languages include C, Python, SQL, and JavaScript plus CSS and HTML. Problem sets inspired by real-world domains of biology, cryptography, finance, forensics, and gaming. Designed for majors and non-majors alike, with or without prior programming experience.",{"title":674,"searchDepth":790,"depth":790,"links":22561},[22562,22563,22564,22565,22566,22567],{"id":22494,"depth":790,"text":22499},{"id":22505,"depth":790,"text":22510},{"id":22516,"depth":790,"text":21980},{"id":22526,"depth":790,"text":22531},{"id":22537,"depth":790,"text":22542},{"id":22548,"depth":790,"text":22553},{"slug":22569,"published":797,"date":22570,"tags":22571,"cover":22572},"6-courses-itunes-u","2017-05-12T08:00:52-04:00",[22017],"./2017-05-11_16-07-03-760x399.png",{"title":174,"description":174},"blog/2017/05/12/6-courses-itunes-u","-_FCuFJ4v0dgZ5pMM89KIcZaBQL2cycUKKvU4xDQVvM",{"id":22577,"title":171,"body":22578,"description":171,"extension":793,"meta":23009,"navigation":797,"path":172,"seo":23014,"stem":23015,"__hash__":23016},"content/blog/2017/05/15/getting-started-spring-boot-actuator.md",{"type":648,"value":22579,"toc":23003},[22580,22583,22587,22590,22595,22598,22637,22642,22646,22654,22873,22876,22958,22962,22965,22970,22973,22987,22991,22994,23000],[651,22581,22582],{},"Spring Boot includes a number of additional features to help you monitor and manage your application when it’s pushed to production. You can choose to manage and monitor your application using HTTP endpoints, with JMX or even by a remote shell (SSH or Telnet). Auditing, health and metrics gathering can be automatically applied to your application. In this article, we will take a look at how to include the actuator in your next project and what endpoints are available. ",[4542,22584,22586],{"id":22585},"actuator-dependencies","Actuator Dependencies",[651,22588,22589],{},"If you look all the way down the list you find the ops category and this is where our Spring Boot Actuator & Actuator Docs dependencies live. ",[651,22591,22592],{},[660,22593],{"alt":22586,"src":22594},"./2017-05-11_18-02-09-1024x645.png",[651,22596,22597],{},"If you want to add the actuator and actuator docs to an existing project simply include the following dependencies. ",[669,22599,22601],{"className":9101,"code":22600,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-actuator\u003C/artifactId>\n\u003C/dependency>\n\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-actuator-docs\u003C/artifactId>\n\u003C/dependency>\n",[676,22602,22603,22607,22611,22616,22620,22624,22628,22633],{"__ignoreMap":674},[679,22604,22605],{"class":681,"line":682},[679,22606,9110],{},[679,22608,22609],{"class":681,"line":790},[679,22610,9115],{},[679,22612,22613],{"class":681,"line":892},[679,22614,22615],{}," \u003CartifactId>spring-boot-starter-actuator\u003C/artifactId>\n",[679,22617,22618],{"class":681,"line":901},[679,22619,9125],{},[679,22621,22622],{"class":681,"line":909},[679,22623,9110],{},[679,22625,22626],{"class":681,"line":918},[679,22627,9115],{},[679,22629,22630],{"class":681,"line":935},[679,22631,22632],{}," \u003CartifactId>spring-boot-actuator-docs\u003C/artifactId>\n",[679,22634,22635],{"class":681,"line":944},[679,22636,9125],{},[651,22638,22639],{},[7300,22640,22641],{},"* Actuator HTTP endpoints are only available with a Spring MVC-based application.",[4542,22643,22645],{"id":22644},"spring-boot-actuator-endpoints","Spring Boot Actuator Endpoints",[651,22647,22648,22649,22653],{},"Actuator endpoints allow you to monitor and interact with your application. Spring Boot includes a number of built-in endpoints and you can also add your own. This means that if you run the application and go to ",[812,22650,22651],{"href":22651,"rel":22652},"http://localhost:8080/health",[816]," you will get the health endpoint. Please create a simple application on your own and trying visiting some of the endpoints. ",[1031,22655,22656,22670],{},[1034,22657,22658],{},[1037,22659,22660,22663,22666],{},[1040,22661,22662],{},"ID",[1040,22664,12285],{"align":22665},"center",[1040,22667,22669],{"align":22668},"right","Sensitive Default",[1050,22671,22672,22684,22696,22708,22720,22736,22748,22763,22775,22787,22798,22810,22822,22834,22849,22861],{},[1037,22673,22674,22679,22682],{},[1055,22675,22676],{},[676,22677,22678],{},"actuator",[1055,22680,22681],{"align":22665},"Provides a hypermedia-based “discovery page” for the other endpoints. Requires Spring HATEOAS to be on the classpath.",[1055,22683,3441],{"align":22668},[1037,22685,22686,22691,22694],{},[1055,22687,22688],{},[676,22689,22690],{},"auditevents",[1055,22692,22693],{"align":22665},"Exposes audit events information for the current application.",[1055,22695,3441],{"align":22668},[1037,22697,22698,22703,22706],{},[1055,22699,22700],{},[676,22701,22702],{},"autoconfig",[1055,22704,22705],{"align":22665},"Displays an auto-configuration report showing all auto-configuration candidates and the reason why they ‘were’ or ‘were not’ applied.",[1055,22707,3441],{"align":22668},[1037,22709,22710,22715,22718],{},[1055,22711,22712],{},[676,22713,22714],{},"beans",[1055,22716,22717],{"align":22665},"Displays a complete list of all the Spring beans in your application.",[1055,22719,3441],{"align":22668},[1037,22721,22722,22727,22734],{},[1055,22723,22724],{},[676,22725,22726],{},"configprops",[1055,22728,22729,22730,22733],{"align":22665},"Displays a collated list of all ",[676,22731,22732],{},"@ConfigurationProperties"," .",[1055,22735,3441],{"align":22668},[1037,22737,22738,22743,22746],{},[1055,22739,22740],{},[676,22741,22742],{},"dump",[1055,22744,22745],{"align":22665},"Performs a thread dump.",[1055,22747,3441],{"align":22668},[1037,22749,22750,22755,22761],{},[1055,22751,22752],{},[676,22753,22754],{},"env",[1055,22756,22757,22758,22733],{"align":22665},"Exposes properties from Spring’s ",[676,22759,22760],{},"ConfigurableEnvironment",[1055,22762,3441],{"align":22668},[1037,22764,22765,22770,22773],{},[1055,22766,22767],{},[676,22768,22769],{},"flyway",[1055,22771,22772],{"align":22665},"Shows any Flyway database migrations that have been applied.",[1055,22774,3441],{"align":22668},[1037,22776,22777,22782,22785],{},[1055,22778,22779],{},[676,22780,22781],{},"health",[1055,22783,22784],{"align":22665},"Shows application health information (when the application is secure, a simple ‘status’ when accessed over an unauthenticated connection or full message details when authenticated).",[1055,22786,1135],{"align":22668},[1037,22788,22789,22793,22796],{},[1055,22790,22791],{},[676,22792,9415],{},[1055,22794,22795],{"align":22665},"Displays arbitrary application info.",[1055,22797,1135],{"align":22668},[1037,22799,22800,22805,22808],{},[1055,22801,22802],{},[676,22803,22804],{},"loggers",[1055,22806,22807],{"align":22665},"Shows and modifies the configuration of loggers in the application.",[1055,22809,3441],{"align":22668},[1037,22811,22812,22817,22820],{},[1055,22813,22814],{},[676,22815,22816],{},"liquibase",[1055,22818,22819],{"align":22665},"Shows any Liquibase database migrations that have been applied.",[1055,22821,3441],{"align":22668},[1037,22823,22824,22829,22832],{},[1055,22825,22826],{},[676,22827,22828],{},"metrics",[1055,22830,22831],{"align":22665},"Shows ‘metrics’ information for the current application.",[1055,22833,3441],{"align":22668},[1037,22835,22836,22841,22847],{},[1055,22837,22838],{},[676,22839,22840],{},"mappings",[1055,22842,22729,22843,22846],{"align":22665},[676,22844,22845],{},"@RequestMapping"," paths.",[1055,22848,3441],{"align":22668},[1037,22850,22851,22856,22859],{},[1055,22852,22853],{},[676,22854,22855],{},"shutdown",[1055,22857,22858],{"align":22665},"Allows the application to be gracefully shutdown (not enabled by default).",[1055,22860,3441],{"align":22668},[1037,22862,22863,22868,22871],{},[1055,22864,22865],{},[676,22866,22867],{},"trace",[1055,22869,22870],{"align":22665},"Displays trace information (by default the last 100 HTTP requests).",[1055,22872,3441],{"align":22668},[651,22874,22875],{},"If you are using Spring MVC, the following additional endpoints can also be used:",[1031,22877,22878,22888],{},[1034,22879,22880],{},[1037,22881,22882,22884,22886],{},[1040,22883,22662],{},[1040,22885,12285],{"align":22665},[1040,22887,22669],{"align":22668},[1050,22889,22890,22906,22922,22934],{},[1037,22891,22892,22897,22904],{},[1055,22893,22894],{},[676,22895,22896],{},"docs",[1055,22898,22899,22900,22903],{"align":22665},"Displays documentation, including example requests and responses, for the Actuator’s endpoints. Requires ",[676,22901,22902],{},"spring-boot-actuator-docs"," to be on the classpath.",[1055,22905,1135],{"align":22668},[1037,22907,22908,22913,22920],{},[1055,22909,22910],{},[676,22911,22912],{},"heapdump",[1055,22914,22915,22916,22919],{"align":22665},"Returns a GZip compressed ",[676,22917,22918],{},"hprof"," heap dump file.",[1055,22921,3441],{"align":22668},[1037,22923,22924,22929,22932],{},[1055,22925,22926],{},[676,22927,22928],{},"jolokia",[1055,22930,22931],{"align":22665},"Exposes JMX beans over HTTP (when Jolokia is on the classpath).",[1055,22933,3441],{"align":22668},[1037,22935,22936,22941,22956],{},[1055,22937,22938],{},[676,22939,22940],{},"logfile",[1055,22942,22943,22944,22947,22948,22951,22952,22955],{"align":22665},"Returns the contents of the logfile (if ",[676,22945,22946],{},"logging.file"," or ",[676,22949,22950],{},"logging.path"," properties have been set). Supports the use of the HTTP ",[676,22953,22954],{},"Range"," header to retrieve part of the log file’s content.",[1055,22957,3441],{"align":22668},[4542,22959,22961],{"id":22960},"actuator-security","Actuator Security",[651,22963,22964],{},"If you tried to visit some of the endpoints you might have received an error that looks like this. ",[651,22966,22967],{},[660,22968],{"alt":22961,"src":22969},"./2017-05-11_18-16-40.png",[651,22971,22972],{},"This is because by default the /beans endpoint is secured. It might have sensitive information and we wouldn't want this information available to just anyone in production. If you look at the list of endpoints above you can see which ones are secured by default. If you want to disable the security of these endpoints in development you can do so by adding the following to your application.properties file.",[669,22974,22976],{"className":5851,"code":22975,"language":5853,"meta":674,"style":674},"management.security.enabled=false\n",[676,22977,22978],{"__ignoreMap":674},[679,22979,22980,22983,22985],{"class":681,"line":682},[679,22981,22982],{"class":880},"management.security.enabled",[679,22984,686],{"class":689},[679,22986,2649],{"class":931},[4542,22988,22990],{"id":22989},"spring-boot-actuator-screencast","Spring Boot Actuator Screencast",[651,22992,22993],{},"I created a short tutorial on everything we walked through in this article. ",[651,22995,22996],{},[812,22997,22998],{"href":22998,"rel":22999},"https://youtu.be/uxGzRyfcSU8",[816],[786,23001,23002],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":23004},[23005,23006,23007,23008],{"id":22585,"depth":790,"text":22586},{"id":22644,"depth":790,"text":22645},{"id":22960,"depth":790,"text":22961},{"id":22989,"depth":790,"text":22990},{"slug":23010,"published":797,"date":23011,"tags":23012,"cover":23013},"getting-started-spring-boot-actuator","2017-05-15T08:00:22-04:00",[7055],"./pexels-photo-169573-760x507.jpeg",{"title":171,"description":171},"blog/2017/05/15/getting-started-spring-boot-actuator","N88vPO6DVHi7rkeloUtwhoTPyGz5T6iv84bHEgiwGrI",{"id":23018,"title":168,"body":23019,"description":168,"extension":793,"meta":23811,"navigation":797,"path":169,"seo":23819,"stem":23820,"__hash__":23821},"content/blog/2017/05/17/spring-component-vs-bean.md",{"type":648,"value":23020,"toc":23802},[23021,23036,23040,23043,23046,23098,23101,23110,23114,23120,23204,23207,23216,23220,23226,23255,23263,23342,23359,23431,23434,23444,23450,23527,23531,23546,23556,23627,23639,23754,23756,23789,23791,23799],[651,23022,23023,23024,23026,23027,23029,23030,23035],{},"In this article, you will learn what a Spring Bean is and what the annotations ",[676,23025,16857],{}," vs ",[676,23028,12292],{}," are used for, and how to use them. Before we dive into how each of these annotations is used it’s important to understand what a Spring Bean is. If you haven’t had a chance to check out my ",[812,23031,23034],{"href":23032,"rel":23033},"https://www.danvega.dev/blog/2023/03/09/spring-boot-crash-course/",[816],"Spring Boot Crash Course"," I cover this topic in more!",[4542,23037,23039],{"id":23038},"what-is-a-spring-bean-the-spring-framework","What is a Spring Bean? The Spring Framework",[651,23041,23042],{},"The Spring Framework was built on the principles of Inversion of Control (IoC) and Dependency Injection (DI). When your code depends on another class to function, that class is considered a dependency. Instead of creating an instance of the class yourself, you delegate that responsibility to the framework, creating an inverse of responsibility. This pattern emphasizes loose coupling between components, allowing for more modular and flexible code.",[651,23044,23045],{},"A Spring Bean is an object that is managed by the Spring IoC container, along with some metadata. The data that describes the bean has the following properties: The data that describes a Spring Bean has the following properties:",[5316,23047,23048,23054,23060,23066,23072,23078,23088],{},[5332,23049,23050,23053],{},[2939,23051,23052],{},"Class",": the class of the bean",[5332,23055,23056,23059],{},[2939,23057,23058],{},"Name",": the name of the bean",[5332,23061,23062,23065],{},[2939,23063,23064],{},"Scope",": the scope of the bean (e.g. singleton or prototype)",[5332,23067,23068,23071],{},[2939,23069,23070],{},"Constructor arguments",": any arguments that need to be passed to the constructor when creating the bean",[5332,23073,23074,23077],{},[2939,23075,23076],{},"Properties",": any properties that need to be set on the bean after it is created",[5332,23079,23080,23083,23084,23087],{},[2939,23081,23082],{},"Initialization method",": The ",[676,23085,23086],{},"InitializingBean","interface lets a bean perform initialization work after the container has set all necessary properties on the bean.",[5332,23089,23090,23093,23094,23097],{},[2939,23091,23092],{},"Destruction method",": Implementing the ",[676,23095,23096],{},"DisposableBean"," interface lets a bean get a callback when the container that contains it is destroyed.",[651,23099,23100],{},"Beans are the building blocks of a Spring application, as they provide the necessary components and services that other parts of the application can use. Examples of beans might include a database connection pool, a service layer that handles business logic, or a controller that maps incoming requests to the appropriate handler method.",[651,23102,23103,23104,23109],{},"If you would like to learn more about Spring Beans, we suggest reading the ",[812,23105,23108],{"href":23106,"rel":23107},"https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition",[816],"reference documentation"," after finishing this article.",[4542,23111,23113],{"id":23112},"configuring-spring-beans","Configuring Spring Beans",[651,23115,23116,23117,23119],{},"Now that you understand what beans are, you need to be able to create and configure them in your applications. As we learn what we should do, it's important to start with what we shouldn't do. In the following example, the post controller depends on the post service to function. Whenever you see the ",[676,23118,8930],{}," keyword, it should raise alarm bells in your head that something may be wrong.",[669,23121,23123],{"className":4107,"code":23122,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private PostService postService;\n\n public PostController() {\n this.postService = new PostService();\n }\n\n}\n",[676,23124,23125,23131,23144,23155,23159,23165,23169,23177,23192,23196,23200],{"__ignoreMap":674},[679,23126,23127,23129],{"class":681,"line":682},[679,23128,4116],{"class":693},[679,23130,9212],{"class":685},[679,23132,23133,23135,23137,23139,23142],{"class":681,"line":790},[679,23134,4116],{"class":693},[679,23136,9275],{"class":685},[679,23138,745],{"class":693},[679,23140,23141],{"class":689},"\"/api/posts\"",[679,23143,1339],{"class":693},[679,23145,23146,23148,23150,23153],{"class":681,"line":892},[679,23147,6073],{"class":685},[679,23149,4512],{"class":685},[679,23151,23152],{"class":880}," PostController",[679,23154,884],{"class":693},[679,23156,23157],{"class":681,"line":901},[679,23158,889],{"emptyLinePlaceholder":797},[679,23160,23161,23163],{"class":681,"line":909},[679,23162,9232],{"class":685},[679,23164,9977],{"class":693},[679,23166,23167],{"class":681,"line":918},[679,23168,889],{"emptyLinePlaceholder":797},[679,23170,23171,23173,23175],{"class":681,"line":935},[679,23172,6089],{"class":685},[679,23174,23152],{"class":880},[679,23176,2667],{"class":693},[679,23178,23179,23181,23183,23185,23187,23190],{"class":681,"line":944},[679,23180,7862],{"class":931},[679,23182,10008],{"class":693},[679,23184,686],{"class":685},[679,23186,2054],{"class":685},[679,23188,23189],{"class":880}," PostService",[679,23191,9317],{"class":693},[679,23193,23194],{"class":681,"line":959},[679,23195,985],{"class":693},[679,23197,23198],{"class":681,"line":964},[679,23199,889],{"emptyLinePlaceholder":797},[679,23201,23202],{"class":681,"line":977},[679,23203,996],{"class":693},[651,23205,23206],{},"While the code above may work for a simple use case, it is not a best practice and does not scale well. A big issue you will run into is when you write tests against your controller class. If you try to isolate the controller class, you will also bring along the post repository with it. If that repository is talking to a database, you will have inadvertently written a full-blown integration test instead of a unit test with a mock of the repository.",[651,23208,23209,23210,23212,23213,23215],{},"There are several ways to define and configure beans in a Spring application. One approach that was used in the past was to use XML configuration files, which specify the beans, their dependencies, and any associated properties. Another approach is to use Java configuration classes, which use annotations to define the beans and their dependencies. Finally, there are also annotations like ",[676,23211,12292],{}," and ",[676,23214,16857],{},"that can be used to define beans directly within Java code.",[5909,23217,23219],{"id":23218},"component-annotation","@Component Annotation",[651,23221,23222,23223,23225],{},"The @Component annotation is a class-level annotation. If you mark a class with ",[676,23224,12292],{}," or any of the stereotype annotations (more on that below) the class will be auto-detected using classpath scanning.",[669,23227,23229],{"className":4107,"code":23228,"language":4109,"meta":674,"style":674},"@Component\npublic class PostService {\n\n}\n",[676,23230,23231,23237,23247,23251],{"__ignoreMap":674},[679,23232,23233,23235],{"class":681,"line":682},[679,23234,4116],{"class":693},[679,23236,13105],{"class":685},[679,23238,23239,23241,23243,23245],{"class":681,"line":790},[679,23240,6073],{"class":685},[679,23242,4512],{"class":685},[679,23244,23189],{"class":880},[679,23246,884],{"class":693},[679,23248,23249],{"class":681,"line":892},[679,23250,889],{"emptyLinePlaceholder":797},[679,23252,23253],{"class":681,"line":901},[679,23254,996],{"class":693},[651,23256,23257,23258,664],{},"Spring will now create an instance of the post service, which is a singleton by default, and make it available through the application context. If you need an instance of that class, you can obtain it through ",[812,23259,23262],{"href":23260,"rel":23261},"https://tanzu.vmware.com/developer/guides/dependency-injection/",[816],"dependency injection",[669,23264,23266],{"className":4107,"code":23265,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private final PostService postService;\n\n public PostController(PostService postService) {\n this.postService = postService;\n }\n}\n",[676,23267,23268,23274,23286,23296,23300,23308,23312,23324,23334,23338],{"__ignoreMap":674},[679,23269,23270,23272],{"class":681,"line":682},[679,23271,4116],{"class":693},[679,23273,9212],{"class":685},[679,23275,23276,23278,23280,23282,23284],{"class":681,"line":790},[679,23277,4116],{"class":693},[679,23279,9275],{"class":685},[679,23281,745],{"class":693},[679,23283,23141],{"class":689},[679,23285,1339],{"class":693},[679,23287,23288,23290,23292,23294],{"class":681,"line":892},[679,23289,6073],{"class":685},[679,23291,4512],{"class":685},[679,23293,23152],{"class":880},[679,23295,884],{"class":693},[679,23297,23298],{"class":681,"line":901},[679,23299,889],{"emptyLinePlaceholder":797},[679,23301,23302,23304,23306],{"class":681,"line":909},[679,23303,9232],{"class":685},[679,23305,12768],{"class":685},[679,23307,9977],{"class":693},[679,23309,23310],{"class":681,"line":918},[679,23311,889],{"emptyLinePlaceholder":797},[679,23313,23314,23316,23318,23320,23322],{"class":681,"line":935},[679,23315,6089],{"class":685},[679,23317,23152],{"class":880},[679,23319,9996],{"class":693},[679,23321,9999],{"class":2099},[679,23323,4390],{"class":693},[679,23325,23326,23328,23330,23332],{"class":681,"line":944},[679,23327,7862],{"class":931},[679,23329,10008],{"class":693},[679,23331,686],{"class":685},[679,23333,10013],{"class":693},[679,23335,23336],{"class":681,"line":959},[679,23337,985],{"class":693},[679,23339,23340],{"class":681,"line":964},[679,23341,996],{"class":693},[651,23343,23344,23345,23348,23349,23352,23353,23355,23356,23358],{},"This approach becomes particularly valuable when dealing with large dependency graphs that would otherwise be difficult to manage manually. For instance, consider the case where ",[676,23346,23347],{},"PostService"," has a dependency on ",[676,23350,23351],{},"PostRepository",". With Spring, you no longer have to worry about manually managing this dependency: as Spring creates an instance of the ",[676,23354,23347],{},", it can see that it requires a ",[676,23357,23351],{},", create an instance of that class, and provide it to the constructor automatically.",[669,23360,23362],{"className":4107,"code":23361,"language":4109,"meta":674,"style":674},"@Component\npublic class PostService {\n\n private final PostRepository postRepository;\n\n public PostService(PostRepository postRepository) {\n this.postRepository = postRepository;\n }\n}\n",[676,23363,23364,23370,23380,23384,23393,23397,23411,23423,23427],{"__ignoreMap":674},[679,23365,23366,23368],{"class":681,"line":682},[679,23367,4116],{"class":693},[679,23369,13105],{"class":685},[679,23371,23372,23374,23376,23378],{"class":681,"line":790},[679,23373,6073],{"class":685},[679,23375,4512],{"class":685},[679,23377,23189],{"class":880},[679,23379,884],{"class":693},[679,23381,23382],{"class":681,"line":892},[679,23383,889],{"emptyLinePlaceholder":797},[679,23385,23386,23388,23390],{"class":681,"line":901},[679,23387,9232],{"class":685},[679,23389,12768],{"class":685},[679,23391,23392],{"class":693}," PostRepository postRepository;\n",[679,23394,23395],{"class":681,"line":909},[679,23396,889],{"emptyLinePlaceholder":797},[679,23398,23399,23401,23403,23406,23409],{"class":681,"line":918},[679,23400,6089],{"class":685},[679,23402,23189],{"class":880},[679,23404,23405],{"class":693},"(PostRepository ",[679,23407,23408],{"class":2099},"postRepository",[679,23410,4390],{"class":693},[679,23412,23413,23415,23418,23420],{"class":681,"line":935},[679,23414,7862],{"class":931},[679,23416,23417],{"class":693},".postRepository ",[679,23419,686],{"class":685},[679,23421,23422],{"class":693}," postRepository;\n",[679,23424,23425],{"class":681,"line":944},[679,23426,985],{"class":693},[679,23428,23429],{"class":681,"line":959},[679,23430,996],{"class":693},[651,23432,23433],{},"There are specialized stereotype annotations that you can use that at the end of the day are also marked with the @Component annotation",[5316,23435,23436,23438,23440,23442],{},[5332,23437,12300],{},[5332,23439,12329],{},[5332,23441,12308],{},[5332,23443,12316],{},[651,23445,23446,23447,23449],{},"This means that you can update your ",[676,23448,23347],{}," to use the service annotation and it will continue to work as expected.",[669,23451,23453],{"className":4107,"code":23452,"language":4109,"meta":674,"style":674},"import org.springframework.stereotype.Service;\n\n@Service\npublic class PostService {\n\n private final PostRepository postRepository;\n\n public PostService(PostRepository postRepository) {\n this.postRepository = postRepository;\n }\n}\n",[676,23454,23455,23461,23465,23471,23481,23485,23493,23497,23509,23519,23523],{"__ignoreMap":674},[679,23456,23457,23459],{"class":681,"line":682},[679,23458,1999],{"class":685},[679,23460,12472],{"class":693},[679,23462,23463],{"class":681,"line":790},[679,23464,889],{"emptyLinePlaceholder":797},[679,23466,23467,23469],{"class":681,"line":892},[679,23468,4116],{"class":693},[679,23470,9486],{"class":685},[679,23472,23473,23475,23477,23479],{"class":681,"line":901},[679,23474,6073],{"class":685},[679,23476,4512],{"class":685},[679,23478,23189],{"class":880},[679,23480,884],{"class":693},[679,23482,23483],{"class":681,"line":909},[679,23484,889],{"emptyLinePlaceholder":797},[679,23486,23487,23489,23491],{"class":681,"line":918},[679,23488,9232],{"class":685},[679,23490,12768],{"class":685},[679,23492,23392],{"class":693},[679,23494,23495],{"class":681,"line":935},[679,23496,889],{"emptyLinePlaceholder":797},[679,23498,23499,23501,23503,23505,23507],{"class":681,"line":944},[679,23500,6089],{"class":685},[679,23502,23189],{"class":880},[679,23504,23405],{"class":693},[679,23506,23408],{"class":2099},[679,23508,4390],{"class":693},[679,23510,23511,23513,23515,23517],{"class":681,"line":959},[679,23512,7862],{"class":931},[679,23514,23417],{"class":693},[679,23516,686],{"class":685},[679,23518,23422],{"class":693},[679,23520,23521],{"class":681,"line":964},[679,23522,985],{"class":693},[679,23524,23525],{"class":681,"line":977},[679,23526,996],{"class":693},[5909,23528,23530],{"id":23529},"bean-annotation","@Bean Annotation",[651,23532,23533,23534,23536,23537,23539,23540,23542,23543,23545],{},"While the ",[676,23535,12292],{}," is convenient and works it is a class-level annotation. What happens if you need to create a bean from a method call? The ",[676,23538,16857],{}," annotation is used to declare a bean in Spring. When applied to a method, this annotation specifies that the method returns a bean that should be managed by the Spring container. ",[676,23541,16857],{}," methods are usually declared within ",[676,23544,6139],{}," classes.",[651,23547,23548,23549,23552,23553,23555],{},"In the following example, you are creating an instance of a ",[676,23550,23551],{},"RestTemplate",". There are two options: create a new instance in each class where it is needed, or create a single instance of the class and ask for that instance wherever it is needed in the app. The latter option can be achieved by having Spring manage the ",[676,23554,23551],{}," instance for you.",[669,23557,23559],{"className":4107,"code":23558,"language":4109,"meta":674,"style":674},"@Configuration\npublic class WebConfig {\n\n @Bean\n public RestTemplate restTemplate() {\n return new RestTemplateBuilder().build();\n }\n\n}\n",[676,23560,23561,23567,23577,23581,23587,23599,23615,23619,23623],{"__ignoreMap":674},[679,23562,23563,23565],{"class":681,"line":682},[679,23564,4116],{"class":693},[679,23566,6212],{"class":685},[679,23568,23569,23571,23573,23575],{"class":681,"line":790},[679,23570,6073],{"class":685},[679,23572,4512],{"class":685},[679,23574,10705],{"class":880},[679,23576,884],{"class":693},[679,23578,23579],{"class":681,"line":892},[679,23580,889],{"emptyLinePlaceholder":797},[679,23582,23583,23585],{"class":681,"line":901},[679,23584,6872],{"class":693},[679,23586,16929],{"class":685},[679,23588,23589,23591,23594,23597],{"class":681,"line":909},[679,23590,6089],{"class":685},[679,23592,23593],{"class":693}," RestTemplate ",[679,23595,23596],{"class":880},"restTemplate",[679,23598,2667],{"class":693},[679,23600,23601,23603,23605,23608,23610,23613],{"class":681,"line":918},[679,23602,9444],{"class":685},[679,23604,2054],{"class":685},[679,23606,23607],{"class":880}," RestTemplateBuilder",[679,23609,10541],{"class":693},[679,23611,23612],{"class":880},"build",[679,23614,9317],{"class":693},[679,23616,23617],{"class":681,"line":935},[679,23618,985],{"class":693},[679,23620,23621],{"class":681,"line":944},[679,23622,889],{"emptyLinePlaceholder":797},[679,23624,23625],{"class":681,"line":959},[679,23626,996],{"class":693},[651,23628,23629,23630,23632,23633,23636,23637,664],{},"If you forget to mark the class with ",[676,23631,6139],{}," the bean will not be added to the application context. You might also see bean definitions in the main application class. In the following example, we are creating an instance of a command line runner. This is possible because the ",[676,23634,23635],{},"@SpringBootApplication"," annotation itself is annotated with ",[676,23638,6139],{},[669,23640,23642],{"className":4107,"code":23641,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n CommandLineRunner commandLineRunner() {\n return args -> {\n System.out.println(\"Hello 👋🏻\");\n };\n }\n\n}\n",[676,23643,23644,23650,23660,23664,23684,23692,23696,23700,23706,23715,23725,23738,23742,23746,23750],{"__ignoreMap":674},[679,23645,23646,23648],{"class":681,"line":682},[679,23647,4116],{"class":693},[679,23649,6068],{"class":685},[679,23651,23652,23654,23656,23658],{"class":681,"line":790},[679,23653,6073],{"class":685},[679,23655,4512],{"class":685},[679,23657,16878],{"class":880},[679,23659,884],{"class":693},[679,23661,23662],{"class":681,"line":892},[679,23663,889],{"emptyLinePlaceholder":797},[679,23665,23666,23668,23670,23672,23674,23676,23678,23680,23682],{"class":681,"line":901},[679,23667,6089],{"class":685},[679,23669,6092],{"class":685},[679,23671,6095],{"class":685},[679,23673,6098],{"class":880},[679,23675,745],{"class":693},[679,23677,4758],{"class":685},[679,23679,16901],{"class":693},[679,23681,6108],{"class":2099},[679,23683,4390],{"class":693},[679,23685,23686,23688,23690],{"class":681,"line":909},[679,23687,6115],{"class":693},[679,23689,6118],{"class":880},[679,23691,16914],{"class":693},[679,23693,23694],{"class":681,"line":918},[679,23695,985],{"class":693},[679,23697,23698],{"class":681,"line":935},[679,23699,889],{"emptyLinePlaceholder":797},[679,23701,23702,23704],{"class":681,"line":944},[679,23703,6872],{"class":693},[679,23705,16929],{"class":685},[679,23707,23708,23710,23713],{"class":681,"line":959},[679,23709,20982],{"class":693},[679,23711,23712],{"class":880},"commandLineRunner",[679,23714,2667],{"class":693},[679,23716,23717,23719,23721,23723],{"class":681,"line":964},[679,23718,9444],{"class":685},[679,23720,16952],{"class":693},[679,23722,16955],{"class":685},[679,23724,884],{"class":693},[679,23726,23727,23729,23731,23733,23736],{"class":681,"line":977},[679,23728,15660],{"class":693},[679,23730,1729],{"class":880},[679,23732,745],{"class":693},[679,23734,23735],{"class":689},"\"Hello 👋🏻\"",[679,23737,1208],{"class":693},[679,23739,23740],{"class":681,"line":982},[679,23741,17018],{"class":693},[679,23743,23744],{"class":681,"line":988},[679,23745,985],{"class":693},[679,23747,23748],{"class":681,"line":993},[679,23749,889],{"emptyLinePlaceholder":797},[679,23751,23752],{"class":681,"line":2129},[679,23753,996],{"class":693},[4542,23755,21931],{"id":21930},[5316,23757,23758,23763,23769,23775,23782],{},[5332,23759,23760],{},[812,23761,23034],{"href":23032,"rel":23762},[816],[5332,23764,23765],{},[812,23766,23768],{"href":23106,"rel":23767},[816],"Spring Beans - Reference Documentation",[5332,23770,23771],{},[812,23772,23774],{"href":23260,"rel":23773},[816],"Spring Dependency Injection - Tanzu Dev Center Article",[5332,23776,23777],{},[812,23778,23781],{"href":23779,"rel":23780},"https://youtu.be/TBlB2_4_Sqo",[816],"Spring Dependency Injection - YouTube Tutorial",[5332,23783,23784],{},[812,23785,23788],{"href":23786,"rel":23787},"https://youtu.be/aX-bgylmprA",[816],"Spring Constructor Injection - YouTube Tutorial",[4542,23790,9042],{"id":9041},[651,23792,23793,23794,23212,23796,23798],{},"I hope that cleared up some things on when to use the ",[676,23795,12292],{},[676,23797,16857],{}," annotations in Spring. We covered what a Spring Bean is, how to configure them, and why it's important to use dependency injection instead of creating instances of classes manually.",[786,23800,23801],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":23803},[23804,23805,23809,23810],{"id":23038,"depth":790,"text":23039},{"id":23112,"depth":790,"text":23113,"children":23806},[23807,23808],{"id":23218,"depth":892,"text":23219},{"id":23529,"depth":892,"text":23530},{"id":21930,"depth":790,"text":21931},{"id":9041,"depth":790,"text":9042},{"slug":23812,"published":797,"date":23813,"updatedOn":23814,"tags":23815,"cover":23817,"keywords":23818},"spring-component-vs-bean","2017-05-17T09:00:06-04:00","2023-04-13T13:00:00-04:00",[23816,7077],"Spring","./bean-vs-component.png","Spring Framework, Spring Boot, Spring Beans, @Component, @Bean",{"title":168,"description":168},"blog/2017/05/17/spring-component-vs-bean","NkdKDJTLdNck0-On4TzukCooVMz_RDUjPnKk7T8axfU",{"id":23823,"title":165,"body":23824,"description":165,"extension":793,"meta":24502,"navigation":797,"path":166,"seo":24507,"stem":24508,"__hash__":24509},"content/blog/2017/05/19/spring-boot-2-first-release.md",{"type":648,"value":23825,"toc":24465},[23826,23835,23839,23842,23876,23879,23884,23887,23890,23893,23897,23901,23909,23918,23926,23929,23937,23951,23959,23966,23974,23981,23989,24004,24012,24022,24028,24030,24035,24037,24042,24044,24049,24051,24059,24069,24077,24097,24105,24119,24127,24136,24144,24147,24156,24162,24170,24180,24188,24203,24207,24209,24215,24223,24233,24241,24244,24265,24271,24282,24296,24309,24329,24337,24357,24365,24378,24386,24399,24415,24430,24433,24439,24444,24450,24452,24455,24462],[651,23827,23828,23829,23834],{},"I have had a lot of fun watching the Spring Boot project grow into an amazing product over the last few years. A month ago I looked at the ",[812,23830,23833],{"href":23831,"rel":23832},"https://danvega.dev/blog/2017/04/24/spring-boot-2-0-roadmap",[816],"Spring 2.0 Roadmap"," and today we get to start walking down that road. It is so exciting to see all of the new features coming in both Spring Framework 5 and Spring Boot 2.0. In this article, we are going to look at the first milestone release of Spring Boot 2.0. We will talk about how to update your current projects, start a new one and a brief look at the new features. ",[4542,23836,23838],{"id":23837},"getting-started-with-spring-20","Getting Started with Spring 2.0",[651,23840,23841],{},"The first thing you need to do to get started with Spring 2.0 is to update the version number to 2.0.0.M1. ",[669,23843,23845],{"className":9101,"code":23844,"language":9103,"meta":674,"style":674},"\u003Cparent>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-parent\u003C/artifactId>\n \u003Cversion>2.0.0.M1\u003C/version>\n \u003CrelativePath/> \u003C!-- lookup parent from repository -->\n\u003C/parent>\n",[676,23846,23847,23852,23856,23861,23866,23871],{"__ignoreMap":674},[679,23848,23849],{"class":681,"line":682},[679,23850,23851],{},"\u003Cparent>\n",[679,23853,23854],{"class":681,"line":790},[679,23855,9115],{},[679,23857,23858],{"class":681,"line":892},[679,23859,23860],{}," \u003CartifactId>spring-boot-starter-parent\u003C/artifactId>\n",[679,23862,23863],{"class":681,"line":901},[679,23864,23865],{}," \u003Cversion>2.0.0.M1\u003C/version>\n",[679,23867,23868],{"class":681,"line":909},[679,23869,23870],{}," \u003CrelativePath/> \u003C!-- lookup parent from repository -->\n",[679,23872,23873],{"class":681,"line":918},[679,23874,23875],{},"\u003C/parent>\n",[651,23877,23878],{},"If you're creating a new project in IntelliJ just change the version number on the dependencies screen to 2.0.0.M1. ",[651,23880,23881],{},[660,23882],{"alt":7077,"src":23883},"./2017-05-19_08-02-11-1024x645.png",[651,23885,23886],{},"If you are upgrading from 1.5 there are a few things that you should know. ",[5909,23888,23889],{"id":18229},"Java 8 baseline",[651,23891,23892],{},"Spring Boot 2.0 requires Java 8 or later. Java 6 and 7 are no longer supported.",[5909,23894,23895],{"id":18266},[676,23896,18267],{},[651,23898,23899,18272],{},[676,23900,18267],{},[5909,23902,23904],{"id":23903},"remote-crash-shell",[812,23905,23908],{"href":23906,"rel":23907},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#remote-crash-shell",[816],"Remote CRaSH shell",[651,23910,23911,23912,23917],{},"Following its depreciation in 1.5, support for the ",[812,23913,23916],{"href":23914,"rel":23915},"http://www.crashub.org/",[816],"CRaSH project"," and remote actuator SSH support that it provided has been removed.",[5909,23919,23921],{"id":23920},"spring-loaded",[812,23922,23925],{"href":23923,"rel":23924},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#spring-loaded",[816],"Spring Loaded",[651,23927,23928],{},"As the Spring Loaded project has been moved to the attic, its support has been removed. We advise using Devtools instead.",[5909,23930,23932],{"id":23931},"dedicated-hazelcast-auto-config-for-caching",[812,23933,23936],{"href":23934,"rel":23935},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#dedicated-hazelcast-auto-config-for-caching",[816],"Dedicated Hazelcast auto-config for Caching",[651,23938,23939,23940,23943,23944,23946,23947,23950],{},"It is no longer possible to auto-configure both a general ",[676,23941,23942],{},"HazelcastInstance"," and a dedicated ",[676,23945,23942],{}," for caching. As a result, the ",[676,23948,23949],{},"spring.cache.hazelcast.config"," property is no longer available.",[5909,23952,23954],{"id":23953},"default-connection-pool",[812,23955,23958],{"href":23956,"rel":23957},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#default-connection-pool",[816],"Default connection pool",[651,23960,23961,23962,23965],{},"The default connection pool has switched from Tomcat to HikariCP. If you used ",[676,23963,23964],{},"spring.datasource.type"," to force the use of Hikari in a Tomcat-based application, you can now remove that override.",[5909,23967,23969],{"id":23968},"servlet-filters",[812,23970,23973],{"href":23971,"rel":23972},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#servlet-filters",[816],"Servlet Filters",[651,23975,23976,23977,23980],{},"The default dispatcher types for a Filter are now ",[676,23978,23979],{},"DipatcherType.REQUEST"," this aligns Spring Boot’s default with the Servlet specification’s default.",[5909,23982,23984],{"id":23983},"spring-security",[812,23985,23988],{"href":23986,"rel":23987},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#spring-security",[816],"Spring Security",[651,23990,23991,23992,23995,23996,23999,24000,24003],{},"Spring Security’s filter is now auto-configured with ",[676,23993,23994],{},"ASYNC"," , ",[676,23997,23998],{},"ERROR"," , and ",[676,24001,24002],{},"REQUEST"," dispatcher types. This aligns Spring Boot’s default configuration with Spring Security’s default configuration.",[5909,24005,24007],{"id":24006},"spring-session",[812,24008,24011],{"href":24009,"rel":24010},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#spring-session",[816],"Spring Session",[651,24013,24014,24015,23995,24017,23999,24019,24021],{},"Spring Session’s filter is now auto-configured with ",[676,24016,23994],{},[676,24018,23998],{},[676,24020,24002],{}," dispatcher types. This aligns Spring Boot’s default configuration with Spring Session’s default configuration. Note that as of Spring Session 2.0, Mongo and GemFire support has been removed.",[5909,24023,24024],{"id":18292},[812,24025,18293],{"href":24026,"rel":24027},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#jetty",[816],[651,24029,18296],{},[5909,24031,24032],{"id":18299},[812,24033,18304],{"href":18302,"rel":24034},[816],[651,24036,18307],{},[5909,24038,24039],{"id":18310},[812,24040,18315],{"href":18313,"rel":24041},[816],[651,24043,18318],{},[5909,24045,24046],{"id":18321},[812,24047,18326],{"href":18324,"rel":24048},[816],[651,24050,18329],{},[5909,24052,24054],{"id":24053},"sendgrid",[812,24055,24058],{"href":24056,"rel":24057},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#sendgrid",[816],"SendGrid",[651,24060,24061,24062,24064,24065,24068],{},"The minimum supported version of SendGrid’s Java client is now 3.2. In support of this upgrade, the ",[676,24063,11403],{}," and ",[676,24066,24067],{},"password"," properties have been removed as an API key is now the only supported means of authentication.",[5909,24070,24072],{"id":24071},"starter-transitive-dependencies",[812,24073,24076],{"href":24074,"rel":24075},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#starter-transitive-dependencies",[816],"Starter transitive dependencies",[651,24078,24079,24080,24083,24084,24064,24087,24090,24091,22947,24093,24096],{},"Previously several Spring Boot starters were transitively depending on Spring MVC with ",[676,24081,24082],{},"spring-boot-starter-web"," . With the new support of Spring WebFlux, ",[676,24085,24086],{},"spring-boot-starter-mustache",[676,24088,24089],{},"spring-boot-starter-thymeleaf"," aren’t depending on on those anymore. It is the developer’s responsibility to choose and add ",[676,24092,24082],{},[676,24094,24095],{},"spring-boot-starter-webflux"," as dependencies.",[5909,24098,24100],{"id":24099},"solr-health-indicator",[812,24101,24104],{"href":24102,"rel":24103},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#solr-health-indicator",[816],"Solr health indicator",[651,24106,24107,24108,24111,24112,24115,24116,24118],{},"The detail of the health check for Solr no longer defines a ",[676,24109,24110],{},"solrStatus"," attribute. Rather a ",[676,24113,24114],{},"status"," property is now defined and corresponds to the integer value of the standard ",[676,24117,24114],{}," property.",[5909,24120,24122],{"id":24121},"default-proxying-strategy",[812,24123,24126],{"href":24124,"rel":24125},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#default-proxying-strategy",[816],"Default Proxying strategy",[651,24128,24129,24130,24133,24134,22733],{},"Spring Boot now uses CGLIB proxying by default, including for the AOP support. If you need proxy-based proxy, you’ll need to set the ",[676,24131,24132],{},"spring.aop.proxy-target-class"," to ",[676,24135,1135],{},[5909,24137,24139],{"id":24138},"cli-based-testing",[812,24140,24143],{"href":24141,"rel":24142},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#cli-based-testing",[816],"CLI-based testing",[651,24145,24146],{},"Testing support has been removed from Spring Boot’s CLI in favour of moving to building the application with Maven or Gradle and using their rich testing support.",[5909,24148,24150],{"id":24149},"configurationproperties",[812,24151,24154],{"href":24152,"rel":24153},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#configurationproperties",[816],[676,24155,22732],{},[651,24157,8384,24158,24161],{},[676,24159,24160],{},"ignoreNestedProperties"," attribute has been removed.",[5909,24163,24165],{"id":24164},"multipart-configuration",[812,24166,24169],{"href":24167,"rel":24168},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#multipart-configuration",[816],"Multipart configuration",[651,24171,24172,24173,24176,24177,22733],{},"To better reflect their Servlet-specific nature, the multipart ",[676,24174,24175],{},"spring.http.multipart."," configuration properties have been renamed to ",[676,24178,24179],{},"spring.servlet.multipart.",[5909,24181,24183],{"id":24182},"mustache-templates-default-file-extension",[812,24184,24187],{"href":24185,"rel":24186},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#mustache-templates-default-file-extension",[816],"Mustache templates default file extension",[651,24189,24190,24191,24194,24195,24198,24199,24202],{},"The default file extension for Mustache templates was ",[676,24192,24193],{},".html"," , it is now ",[676,24196,24197],{},".mustache"," to align with the official spec and most IDE plugins. You can override this new default by changing the ",[676,24200,24201],{},"spring.mustache.suffix"," configuration key.",[4542,24204,24206],{"id":24205},"spring-20-new-features","Spring 2.0 New Features",[5909,24208,18217],{"id":18216},[651,24210,18220,24211,24214],{},[812,24212,18225],{"href":18223,"rel":24213},[816]," for details.",[5909,24216,24218],{"id":24217},"webflux-and-webfluxfn-support",[812,24219,24222],{"href":24220,"rel":24221},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#webflux-and-webfluxfn-support",[816],"WebFlux and WebFlux.fn support",[651,24224,24225,24226,24228,24229,24232],{},"Spring Boot 2.0 provides a new starter for supporting the Reactive Spring web frameworks, for both annotation and functional based variants. ",[676,24227,24095],{}," brings WebFlux itself, plus Reactor Netty as a default web engine ( ",[676,24230,24231],{},"spring-boot-starter-reactor-netty"," ).",[5909,24234,24236],{"id":24235},"reactive-data-support",[812,24237,24240],{"href":24238,"rel":24239},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#reactive-data-support",[816],"Reactive data support",[651,24242,24243],{},"Spring Boot 2.0 provides auto-configuration for the following data store with reactive support:",[5316,24245,24246,24253,24259],{},[5332,24247,24248,24249,24252],{},"MongoDB ( ",[676,24250,24251],{},"spring-boot-starter-mongodb-reactive"," )",[5332,24254,24255,24256,24252],{},"Redis ( ",[676,24257,24258],{},"spring-boot-starter-redis-reactive",[5332,24260,24261,24262,24252],{},"Cassandra ( ",[676,24263,24264],{},"spring-boot-starter-cassandra-reactive",[651,24266,24267,24270],{},[676,24268,24269],{},"@DataMongoTest"," also enables reactive repositories if necessary.",[5909,24272,24274],{"id":24273},"webfluxtest-support",[812,24275,24278,24281],{"href":24276,"rel":24277},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#webfluxtest-support",[816],[676,24279,24280],{},"@WebFluxTest"," support",[651,24283,24284,24285,24287,24288,24291,24292,24295],{},"Reactive controllers can be tested using ",[676,24286,24280],{}," that provides a similar support than ",[676,24289,24290],{},"@WebMvcTest"," for Spring MVC. In particular a ",[676,24293,24294],{},"WebTestClient"," is auto-configured.",[5909,24297,24299],{"id":24298},"webtestclient-auto-configuration-with-springboottest",[812,24300,24303,24305,24306],{"href":24301,"rel":24302},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#webtestclient-auto-configuration-with-springboottest",[816],[676,24304,24294],{}," auto-configuration with ",[676,24307,24308],{},"@SpringBootTest",[651,24310,24311,24312,24314,24315,22947,24318,24321,24322,24324,24325,24328],{},"When using ",[676,24313,24308],{}," with an actual server (that is, either ",[676,24316,24317],{},"DEFINED_PORT",[676,24319,24320],{},"RANDOM_PORT"," ), a ",[676,24323,24294],{}," is available the same way ",[676,24326,24327],{},"TestRestTemplate"," is.",[5909,24330,24332],{"id":24331},"gradle-plugin",[812,24333,24336],{"href":24334,"rel":24335},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#gradle-plugin",[816],"Gradle plugin",[651,24338,24339,24340,24345,24346,23212,24351,24356],{},"Spring Boot’s Gradle plugin has been largely rewritten to enable a ",[812,24341,24344],{"href":24342,"rel":24343},"https://github.com/spring-projects/spring-boot/issues?utf8=%E2%9C%93&q=label%3A%22theme%3A%20gradle-plugin%22%20milestone%3A2.0.0.M1%20",[816],"number of significant improvements",". You can read more about the plugin’s capabilities in its ",[812,24347,24350],{"href":24348,"rel":24349},"https://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/gradle-plugin/reference",[816],"reference",[812,24352,24355],{"href":24353,"rel":24354},"https://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/gradle-plugin/api",[816],"api"," documentation.",[5259,24358,24360],{"id":24359},"building-executable-jars-and-wars",[812,24361,24364],{"href":24362,"rel":24363},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#building-executable-jars-and-wars",[816],"Building executable jars and wars",[651,24366,8384,24367,24370,24371,24064,24374,24377],{},[676,24368,24369],{},"bootRepackage"," task has been replaced with ",[676,24372,24373],{},"bootJar",[676,24375,24376],{},"bootWar"," tasks for building executable jars and wars respectively. Both tasks extend their equivalent standard Gradle jar or war task, giving you access to all of the usual configuration options and behaviour.",[5259,24379,24381],{"id":24380},"dependency-management",[812,24382,24385],{"href":24383,"rel":24384},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#dependency-management",[816],"Dependency management",[651,24387,24388,24389,24394,24395,24398],{},"Spring Boot’s Gradle plugin no longer automatically applies the ",[812,24390,24393],{"href":24391,"rel":24392},"https://github.com/spring-gradle-plugins/dependency-management-plugin",[816],"dependency management plugin",". Instead, Spring Boot’s plugin now reacts to the dependency management plugin being applied by importing the correct version of the ",[676,24396,24397],{},"spring-boot-dependencies"," bom. This gives you more control over how and when dependency management is configured. For most applications applying the dependency management plugin will be sufficient:",[651,24400,24401,24402,4282,24406],{},"apply ",[679,24403,24405],{"style":24404},"pl-c1","plugin",[679,24407,24409,24412,24413],{"style":24408},"pl-s",[679,24410,3056],{"style":24411},"pl-pds","io.spring.dependency-management",[679,24414,3056],{"style":24411},[651,24416,24417,24418,24421,24422,24425,24426,24429],{},"Please note that the dependency management plugin remains a transitive dependency of ",[676,24419,24420],{},"spring-boot-gradle-plugin"," so there’s no need for it to be listed as a ",[676,24423,24424],{},"classpath"," dependency in your ",[676,24427,24428],{},"buildscript"," configuration.",[4542,24431,24432],{"id":18339},"SPRING 2.0 COURSE",[651,24434,24435,24436,18348],{},"If you haven’t already had a chance to check out my ",[812,24437,18347],{"href":17913,"rel":24438},[816],[651,24440,24441],{},[660,24442],{"alt":24443,"src":18353},"Spirngboot",[651,24445,18356,24446,24449],{},[812,24447,18361],{"href":18359,"rel":24448},[816]," and signup for updates. Anyone on this list will be the first to find out when it’s released and will receive a discount. ",[4542,24451,9042],{"id":9041},[651,24453,24454],{},"This is just the start of Spring 2.0 and we should have more exciting announcements to follow.",[651,24456,24457],{},[7300,24458,24459,24461],{},[2939,24460,11650],{}," What are you most looking forward to in Spring Boot 2.0?",[786,24463,24464],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":24466},[24467,24490,24500,24501],{"id":23837,"depth":790,"text":23838,"children":24468},[24469,24470,24471,24472,24473,24474,24475,24476,24477,24478,24479,24480,24481,24482,24483,24484,24485,24486,24487,24488,24489],{"id":18229,"depth":892,"text":23889},{"id":18266,"depth":892,"text":18267},{"id":23903,"depth":892,"text":23908},{"id":23920,"depth":892,"text":23925},{"id":23931,"depth":892,"text":23936},{"id":23953,"depth":892,"text":23958},{"id":23968,"depth":892,"text":23973},{"id":23983,"depth":892,"text":23988},{"id":24006,"depth":892,"text":24011},{"id":18292,"depth":892,"text":18293},{"id":18299,"depth":892,"text":18304},{"id":18310,"depth":892,"text":18315},{"id":18321,"depth":892,"text":18326},{"id":24053,"depth":892,"text":24058},{"id":24071,"depth":892,"text":24076},{"id":24099,"depth":892,"text":24104},{"id":24121,"depth":892,"text":24126},{"id":24138,"depth":892,"text":24143},{"id":24149,"depth":892,"text":22732},{"id":24164,"depth":892,"text":24169},{"id":24182,"depth":892,"text":24187},{"id":24205,"depth":790,"text":24206,"children":24491},[24492,24493,24494,24495,24497,24499],{"id":18216,"depth":892,"text":18217},{"id":24217,"depth":892,"text":24222},{"id":24235,"depth":892,"text":24240},{"id":24273,"depth":892,"text":24496},"@WebFluxTest support",{"id":24298,"depth":892,"text":24498},"WebTestClient auto-configuration with @SpringBootTest",{"id":24331,"depth":892,"text":24336},{"id":18339,"depth":790,"text":24432},{"id":9041,"depth":790,"text":9042},{"slug":24503,"published":797,"date":24504,"tags":24505,"cover":24506},"spring-boot-2-first-release","2017-05-19T10:15:39-04:00",[7055],"./pexels-photo-92904-760x507.jpeg",{"title":165,"description":165},"blog/2017/05/19/spring-boot-2-first-release","8daYPFGUPVikETmoI8M6pIKWAy-VIVZ17qvcywuHPg8",{"id":24511,"title":162,"body":24512,"description":162,"extension":793,"meta":24617,"navigation":797,"path":163,"seo":24621,"stem":24622,"__hash__":24623},"content/blog/2017/05/22/checking-upgrading-jhipster-version.md",{"type":648,"value":24513,"toc":24612},[24514,24527,24531,24534,24547,24550,24556,24559,24578,24583,24586,24588,24592,24597,24599,24602,24609],[651,24515,24516,24517,24522,24523,24526],{},"The last few months of my life have been full of Angular + Spring Boot projects and I have been having a blast. When you start building these types of applications you need to start looking into JHipster. If you're new to JHipster I wrote up a tutorial titled ",[812,24518,24521],{"href":24519,"rel":24520},"https://danvega.dev/blog/2017/04/19/what-is-jhipster",[816],"\"What is JHipster & Why you need to start using it today\"",". In this short post, we are going to look at how to tell what version of ",[812,24524,17850],{"href":17848,"rel":24525},[816]," you're running and how to tell if there are updates available. If there are updates available you will wanna upgrade to the current version. This is not a tutorial on upgrading a current application from one version to another as there is a little more work involved in that.",[4542,24528,24530],{"id":24529},"upgrading-jhipster","Upgrading JHipster",[651,24532,24533],{},"To tell what version of JHipster you are running simply open up command prompt and type the following",[669,24535,24537],{"className":5851,"code":24536,"language":5853,"meta":674,"style":674},"yo jhipster\n",[676,24538,24539],{"__ignoreMap":674},[679,24540,24541,24544],{"class":681,"line":682},[679,24542,24543],{"class":880},"yo",[679,24545,24546],{"class":689}," jhipster\n",[651,24548,24549],{},"You will see that the current version of JHipster is 4.0.2 and there is an update available for 4.5.1. The helpful command prompt also shows you how to update by giving you the command to run.",[651,24551,24552],{},[660,24553],{"alt":24554,"src":24555},"JHipster version","./2017-05-22_08-44-19-1024x623.png",[651,24557,24558],{},"To update run",[669,24560,24562],{"className":5851,"code":24561,"language":5853,"meta":674,"style":674},"npm install -g generator-jhipster\n",[676,24563,24564],{"__ignoreMap":674},[679,24565,24566,24569,24572,24575],{"class":681,"line":682},[679,24567,24568],{"class":880},"npm",[679,24570,24571],{"class":689}," install",[679,24573,24574],{"class":931}," -g",[679,24576,24577],{"class":689}," generator-jhipster\n",[651,24579,24580],{},[660,24581],{"alt":24530,"src":24582},"./2017-05-22_08-52-22-1024x623.png",[4542,24584,24585],{"id":17994},"UPCOMING COURSE",[651,24587,17998],{},[651,24589,24590],{},[660,24591],{"alt":18003,"src":18004},[651,24593,24594],{},[812,24595,18011],{"href":18009,"rel":24596},[816],[4542,24598,9042],{"id":9041},[651,24600,24601],{},"I hope this little JHipster tip was helpful but I am also curious what type of projects you are looking to build with JHipster.",[651,24603,24604],{},[7300,24605,24606,24608],{},[2939,24607,11650],{}," What problems are you facing in your Angular + Spring Boot projects.",[786,24610,24611],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":24613},[24614,24615,24616],{"id":24529,"depth":790,"text":24530},{"id":17994,"depth":790,"text":24585},{"id":9041,"depth":790,"text":9042},{"slug":24618,"published":797,"date":24619,"tags":24620,"cover":18035},"checking-upgrading-jhipster-version","2017-05-22T09:00:26-04:00",[17866,7055],{"title":162,"description":162},"blog/2017/05/22/checking-upgrading-jhipster-version","c4eL7Ozl3PtAkgwNegb_2bxlCYWfDuerUuzT9amuIVY",{"id":24625,"title":159,"body":24626,"description":159,"extension":793,"meta":25657,"navigation":797,"path":160,"seo":25662,"stem":25663,"__hash__":25664},"content/blog/2017/05/24/contributing-groovy-website.md",{"type":648,"value":24627,"toc":25646},[24628,24642,24646,24655,24659,24673,24678,24682,24691,24694,24697,24872,24880,24884,24887,25127,25130,25320,25323,25568,25572,25575,25588,25594,25597,25601,25604,25610,25613,25621,25626,25631,25633,25636,25643],[651,24629,24630,24631,24635,24636,24641],{},"& Anyone who knows me knows that I am a huge fan of ",[812,24632,24634],{"href":13496,"rel":24633},[816],"The Groovy Programming Language",". So much so that I did my part to spread the good word about Groovy by creating a course titled \"",[812,24637,24640],{"href":24638,"rel":24639},"https://danvega.dev/groovy",[816],"The Complete Apache Groovy Developer Course","\". I recently had the opportunity to contribute to the Groovy website. In this article, I will tell you all about what I did and how you can contribute as well. ",[4542,24643,24645],{"id":24644},"contributing-to-the-groovy-website","Contributing to the Groovy Website",[651,24647,24648,24649,24654],{},"The website is ",[812,24650,24653],{"href":24651,"rel":24652},"https://github.com/groovy/groovy-website",[816],"open source and on Github"," so it was really easy to get started with. Contributing to an open source project isn't always all about adding that cool new feature to the core of the project. I wanted to add a resource on the learn page so that people could find my course. I am probably a little biased but I think my course is one of the best resources around for learning Groovy. So now on top of teaching folks how to use the language I made some updates to the website.",[4542,24656,24658],{"id":24657},"fork-pull-request","Fork & Pull Request",[651,24660,24661,24662,24666,24667,24672],{},"I am not an official committer to the project so the first thing we need to do is to fork the repo. To do this we are going to head over the repo for the ",[812,24663,24665],{"href":24651,"rel":24664},[816],"Groovy Website"," and fork it. This means that we are going to make changes on our GitHub account and then send them a pull request. You can see my commits below along with the project lead ",[812,24668,24671],{"href":24669,"rel":24670},"http://glaforge.appspot.com/",[816],"Guillaume Laforge"," merging them into the master branch. ",[651,24674,24675],{},[660,24676],{"alt":18628,"src":24677},"./2017-05-23_21-47-34.png",[4542,24679,24681],{"id":24680},"contributing","Contributing",[651,24683,24684,24685,24690],{},"Now that we understand the workflow we can take a look at how to make that change. If you look at the readme for the source code there is a ton of great information to get you started. The following information is straight from the readme. The website is generated thanks to Gradle and makes use of the ",[812,24686,24689],{"href":24687,"rel":24688},"http://docs.groovy-lang.org/latest/html/documentation/markup-template-engine.html",[816],"Markup Template Engine",". The structure of the project consists of two modules:",[651,24692,24693],{},"generator : utility classes and model for generating the website\nsite : the website itself",[651,24695,24696],{},"The website subproject consists of:",[669,24698,24700],{"className":5851,"code":24699,"language":5853,"meta":674,"style":674},"src/main/site : sources for the static website\n |--- assets : static resources such as images, CSS files, ...\n |--- html : elements that templates include as raw HTML contents\n |--- includes : includes used by templates\n |--- layouts : layouts for the various pages\n |--- pages : individual pages\nbuild.gradle : website weaving logic\n",[676,24701,24702,24721,24758,24791,24814,24838,24855],{"__ignoreMap":674},[679,24703,24704,24707,24710,24713,24715,24717,24719],{"class":681,"line":682},[679,24705,24706],{"class":880},"src/main/site",[679,24708,24709],{"class":689}," :",[679,24711,24712],{"class":689}," sources",[679,24714,6818],{"class":689},[679,24716,22351],{"class":689},[679,24718,6092],{"class":689},[679,24720,5057],{"class":689},[679,24722,24723,24726,24729,24732,24735,24737,24740,24743,24746,24749,24752,24755],{"class":681,"line":790},[679,24724,24725],{"class":685}," |",[679,24727,24728],{"class":880},"---",[679,24730,24731],{"class":689}," assets",[679,24733,24734],{"class":689}," :",[679,24736,6092],{"class":689},[679,24738,24739],{"class":689}," resources",[679,24741,24742],{"class":689}," such",[679,24744,24745],{"class":689}," as",[679,24747,24748],{"class":689}," images,",[679,24750,24751],{"class":689}," CSS",[679,24753,24754],{"class":689}," files,",[679,24756,24757],{"class":689}," ...\n",[679,24759,24760,24762,24764,24766,24769,24772,24774,24777,24780,24782,24785,24788],{"class":681,"line":892},[679,24761,24725],{"class":685},[679,24763,24728],{"class":880},[679,24765,11910],{"class":689},[679,24767,24768],{"class":689}," :",[679,24770,24771],{"class":689}," elements",[679,24773,6417],{"class":689},[679,24775,24776],{"class":689}," templates",[679,24778,24779],{"class":689}," include",[679,24781,24745],{"class":689},[679,24783,24784],{"class":689}," raw",[679,24786,24787],{"class":689}," HTML",[679,24789,24790],{"class":689}," contents\n",[679,24792,24793,24795,24797,24800,24803,24805,24808,24811],{"class":681,"line":901},[679,24794,24725],{"class":685},[679,24796,24728],{"class":880},[679,24798,24799],{"class":689}," includes",[679,24801,24802],{"class":689}," :",[679,24804,24799],{"class":689},[679,24806,24807],{"class":689}," used",[679,24809,24810],{"class":689}," by",[679,24812,24813],{"class":689}," templates\n",[679,24815,24816,24818,24820,24823,24826,24828,24830,24832,24835],{"class":681,"line":909},[679,24817,24725],{"class":685},[679,24819,24728],{"class":880},[679,24821,24822],{"class":689}," layouts",[679,24824,24825],{"class":689}," :",[679,24827,24822],{"class":689},[679,24829,6818],{"class":689},[679,24831,22351],{"class":689},[679,24833,24834],{"class":689}," various",[679,24836,24837],{"class":689}," pages\n",[679,24839,24840,24842,24844,24847,24850,24853],{"class":681,"line":918},[679,24841,24725],{"class":685},[679,24843,24728],{"class":880},[679,24845,24846],{"class":689}," pages",[679,24848,24849],{"class":689}," :",[679,24851,24852],{"class":689}," individual",[679,24854,24837],{"class":689},[679,24856,24857,24860,24863,24866,24869],{"class":681,"line":935},[679,24858,24859],{"class":880},"build.gradle",[679,24861,24862],{"class":689}," :",[679,24864,24865],{"class":689}," website",[679,24867,24868],{"class":689}," weaving",[679,24870,24871],{"class":689}," logic\n",[651,24873,24874,24875,664],{},"Additional details can be found in this ",[812,24876,24879],{"href":24877,"rel":24878},"http://melix.github.io/blog/2014/07/new-groovy-website.html",[816],"blog post",[5909,24881,24883],{"id":24882},"adding-courses-to-the-learn-page","Adding Courses to the Learn Page",[651,24885,24886],{},"There was no courses section when I started this so the first thing I needed to do was to add a new model for courses. ",[669,24888,24890],{"className":4107,"code":24889,"language":4109,"meta":674,"style":674},"package model\n\nimport groovy.transform.CompileStatic\n\n@CompileStatic\nclass Course {\n\n String title\n String instructor\n String url\n String description\n String cover\n\n void title(String title) {\n this.title = title\n }\n\n void instructor(String instructor) {\n this.instructor = instructor\n }\n\n void url(String url){\n this.url = url\n }\n\n void description(String description){\n this.description = description\n }\n\n void cover(String cover){\n this.cover = cover\n }\n\n}\n",[676,24891,24892,24899,24903,24920,24924,24936,24946,24950,24956,24963,24970,24977,24984,24988,25000,25007,25011,25015,25027,25034,25038,25042,25054,25061,25065,25069,25081,25088,25092,25096,25108,25115,25119,25123],{"__ignoreMap":674},[679,24893,24894,24896],{"class":681,"line":682},[679,24895,2543],{"class":685},[679,24897,24898],{"class":693}," model\n",[679,24900,24901],{"class":681,"line":790},[679,24902,889],{"emptyLinePlaceholder":797},[679,24904,24905,24907,24910,24912,24915,24917],{"class":681,"line":892},[679,24906,1999],{"class":6561},[679,24908,24909],{"class":693}," groovy.transform.",[679,24911,7757],{"class":6561},[679,24913,24914],{"class":693},"ompile",[679,24916,7406],{"class":6561},[679,24918,24919],{"class":693},"tatic\n",[679,24921,24922],{"class":681,"line":901},[679,24923,889],{"emptyLinePlaceholder":797},[679,24925,24926,24928,24930,24932,24934],{"class":681,"line":909},[679,24927,4116],{"class":693},[679,24929,7757],{"class":6561},[679,24931,24914],{"class":693},[679,24933,7406],{"class":6561},[679,24935,24919],{"class":693},[679,24937,24938,24940,24943],{"class":681,"line":918},[679,24939,877],{"class":6561},[679,24941,24942],{"class":6561}," C",[679,24944,24945],{"class":693},"ourse {\n",[679,24947,24948],{"class":681,"line":935},[679,24949,889],{"emptyLinePlaceholder":797},[679,24951,24952,24954],{"class":681,"line":944},[679,24953,7214],{"class":6561},[679,24955,7217],{"class":693},[679,24957,24958,24960],{"class":681,"line":959},[679,24959,7214],{"class":6561},[679,24961,24962],{"class":693},"tring instructor\n",[679,24964,24965,24967],{"class":681,"line":964},[679,24966,7214],{"class":6561},[679,24968,24969],{"class":693},"tring url\n",[679,24971,24972,24974],{"class":681,"line":977},[679,24973,7214],{"class":6561},[679,24975,24976],{"class":693},"tring description\n",[679,24978,24979,24981],{"class":681,"line":982},[679,24980,7214],{"class":6561},[679,24982,24983],{"class":693},"tring cover\n",[679,24985,24986],{"class":681,"line":988},[679,24987,889],{"emptyLinePlaceholder":797},[679,24989,24990,24992,24995,24997],{"class":681,"line":993},[679,24991,3314],{"class":6561},[679,24993,24994],{"class":693}," title(",[679,24996,7406],{"class":6561},[679,24998,24999],{"class":693},"tring title) {\n",[679,25001,25002,25004],{"class":681,"line":2129},[679,25003,7862],{"class":6561},[679,25005,25006],{"class":693},".title = title\n",[679,25008,25009],{"class":681,"line":2140},[679,25010,985],{"class":693},[679,25012,25013],{"class":681,"line":2145},[679,25014,889],{"emptyLinePlaceholder":797},[679,25016,25017,25019,25022,25024],{"class":681,"line":2154},[679,25018,3314],{"class":6561},[679,25020,25021],{"class":693}," instructor(",[679,25023,7406],{"class":6561},[679,25025,25026],{"class":693},"tring instructor) {\n",[679,25028,25029,25031],{"class":681,"line":2159},[679,25030,7862],{"class":6561},[679,25032,25033],{"class":693},".instructor = instructor\n",[679,25035,25036],{"class":681,"line":2164},[679,25037,985],{"class":693},[679,25039,25040],{"class":681,"line":3134},[679,25041,889],{"emptyLinePlaceholder":797},[679,25043,25044,25046,25049,25051],{"class":681,"line":3139},[679,25045,3314],{"class":6561},[679,25047,25048],{"class":693}," url(",[679,25050,7406],{"class":6561},[679,25052,25053],{"class":693},"tring url){\n",[679,25055,25056,25058],{"class":681,"line":3144},[679,25057,7862],{"class":6561},[679,25059,25060],{"class":693},".url = url\n",[679,25062,25063],{"class":681,"line":3149},[679,25064,985],{"class":693},[679,25066,25067],{"class":681,"line":3169},[679,25068,889],{"emptyLinePlaceholder":797},[679,25070,25071,25073,25076,25078],{"class":681,"line":3185},[679,25072,3314],{"class":6561},[679,25074,25075],{"class":693}," description(",[679,25077,7406],{"class":6561},[679,25079,25080],{"class":693},"tring description){\n",[679,25082,25083,25085],{"class":681,"line":3194},[679,25084,7862],{"class":6561},[679,25086,25087],{"class":693},".description = description\n",[679,25089,25090],{"class":681,"line":3199},[679,25091,985],{"class":693},[679,25093,25094],{"class":681,"line":3212},[679,25095,889],{"emptyLinePlaceholder":797},[679,25097,25098,25100,25103,25105],{"class":681,"line":3217},[679,25099,3314],{"class":6561},[679,25101,25102],{"class":693}," cover(",[679,25104,7406],{"class":6561},[679,25106,25107],{"class":693},"tring cover){\n",[679,25109,25110,25112],{"class":681,"line":3222},[679,25111,7862],{"class":6561},[679,25113,25114],{"class":693},".cover = cover\n",[679,25116,25117],{"class":681,"line":3227},[679,25118,985],{"class":693},[679,25120,25121],{"class":681,"line":3232},[679,25122,889],{"emptyLinePlaceholder":797},[679,25124,25125],{"class":681,"line":3499},[679,25126,996],{"class":693},[651,25128,25129],{},"Then in the sitemap which contains all of the data in markup form, I just add a new course. ",[669,25131,25135],{"className":25132,"code":25133,"language":25134,"meta":674,"style":674},"language-javascript shiki shiki-themes github-light github-dark github-light","courses {\n course('The Complete Apache Groovy Developer Course') {\n instructor 'Dan Vega'\n url 'https://www.udemy.com/apache-groovy'\n description '''\n \u003Cp>I am going to teach you everything you need to know to start using The Groovy Programming language. This course is really designed\n for 2 different types of people and I think both will benefit from it. If you’re a beginner programmer with a some experience in\n another language like Python or Ruby this course is for you. Dynamic languages are generally thought of as easier for total beginners\n to learn because they’re flexible and fun. If you’re an existing Java Developer (Beginner or Experienced) this course is also for you.\u003C/p>\n\n \u003Cp>This course is packed with almost 14 hours of content. We are going to start off with getting your development environment up and running\n and then go through the very basics of the language. From there we are going to build on that in each section cover topics like closures, meta-programming,\n builders and so much more. I feel like this is one of the most complete courses around and I am excited for you to join me on this adventure.\u003C/p>\n '''\n cover 'groovy-course-cover.png'\n }\n}\n\n","javascript",[676,25136,25137,25142,25154,25162,25169,25177,25185,25214,25239,25261,25265,25274,25279,25288,25293,25312,25316],{"__ignoreMap":674},[679,25138,25139],{"class":681,"line":682},[679,25140,25141],{"class":693},"courses {\n",[679,25143,25144,25147,25149,25152],{"class":681,"line":790},[679,25145,25146],{"class":880}," course",[679,25148,745],{"class":693},[679,25150,25151],{"class":689},"'The Complete Apache Groovy Developer Course'",[679,25153,4390],{"class":693},[679,25155,25156,25159],{"class":681,"line":892},[679,25157,25158],{"class":693}," instructor ",[679,25160,25161],{"class":689},"'Dan Vega'\n",[679,25163,25164,25166],{"class":681,"line":901},[679,25165,2847],{"class":693},[679,25167,25168],{"class":689},"'https://www.udemy.com/apache-groovy'\n",[679,25170,25171,25174],{"class":681,"line":909},[679,25172,25173],{"class":693}," description ",[679,25175,25176],{"class":689},"'''\n",[679,25178,25179,25182],{"class":681,"line":918},[679,25180,25181],{"class":689}," \u003Cp>I am going to teach you everything you need to know to start using The Groovy Programming language. This course is really designe",[679,25183,25184],{"class":6561},"d\n",[679,25186,25187,25190,25192,25195,25197,25200,25202,25205,25208,25211],{"class":681,"line":935},[679,25188,25189],{"class":693}," for ",[679,25191,17262],{"class":931},[679,25193,25194],{"class":693}," different types ",[679,25196,16672],{"class":685},[679,25198,25199],{"class":693}," people and ",[679,25201,7518],{"class":931},[679,25203,25204],{"class":693}," think both will benefit from it. If you’re a beginner programmer ",[679,25206,25207],{"class":685},"with",[679,25209,25210],{"class":693}," a some experience ",[679,25212,25213],{"class":685},"in\n",[679,25215,25216,25219,25221,25224,25226,25228,25231,25233,25236],{"class":681,"line":944},[679,25217,25218],{"class":693}," another language like Python or Ruby ",[679,25220,4732],{"class":931},[679,25222,25223],{"class":693}," course is for you. Dynamic languages are generally thought ",[679,25225,16672],{"class":685},[679,25227,24745],{"class":685},[679,25229,25230],{"class":880}," easier",[679,25232,6818],{"class":880},[679,25234,25235],{"class":880}," total",[679,25237,25238],{"class":880}," beginners\n",[679,25240,25241,25244,25247,25250,25252,25255,25257,25259],{"class":681,"line":959},[679,25242,25243],{"class":693}," to learn because they’re flexible and fun. If you’re an existing Java ",[679,25245,25246],{"class":880},"Developer",[679,25248,25249],{"class":693}," (Beginner or Experienced) ",[679,25251,4732],{"class":931},[679,25253,25254],{"class":693}," course is also for you.",[679,25256,4586],{"class":685},[679,25258,651],{"class":693},[679,25260,4519],{"class":685},[679,25262,25263],{"class":681,"line":964},[679,25264,889],{"emptyLinePlaceholder":797},[679,25266,25267,25269,25271],{"class":681,"line":977},[679,25268,5392],{"class":693},[679,25270,651],{"class":4508},[679,25272,25273],{"class":693},">This course is packed with almost 14 hours of content. We are going to start off with getting your development environment up and running\n",[679,25275,25276],{"class":681,"line":982},[679,25277,25278],{"class":693}," and then go through the very basics of the language. From there we are going to build on that in each section cover topics like closures, meta-programming,\n",[679,25280,25281,25284,25286],{"class":681,"line":988},[679,25282,25283],{"class":693}," builders and so much more. I feel like this is one of the most complete courses around and I am excited for you to join me on this adventure.\u003C/",[679,25285,651],{"class":4508},[679,25287,4519],{"class":693},[679,25289,25290],{"class":681,"line":993},[679,25291,25292],{"class":689}," '''\n",[679,25294,25295,25298,25300,25302,25305,25307,25310],{"class":681,"line":2129},[679,25296,25297],{"class":689}," cover '",[679,25299,870],{"class":693},[679,25301,6453],{"class":685},[679,25303,25304],{"class":693},"course",[679,25306,6453],{"class":685},[679,25308,25309],{"class":693},"cover.png",[679,25311,3076],{"class":689},[679,25313,25314],{"class":681,"line":2140},[679,25315,985],{"class":6561},[679,25317,25318],{"class":681,"line":2145},[679,25319,996],{"class":693},[651,25321,25322],{},"Finally, on the learn page, I will loop over and display my course.",[669,25324,25326],{"className":25132,"code":25325,"language":25134,"meta":674,"style":674},"hr(class: 'divider')\n\na(name: 'courses') {}\nh2 {\n i(class: 'fa fa-film') {}\n yield ' Courses'\n}\np '''\n Another great resource for learning Groovy is by watching a course. You could spend time hunting down\n various videos on the web but these courses have all the information you need packed into one place.\n '''\n\ncourses.each { Course course ->\n div(class: 'courses') {\n a(href: course.url, target: \"\\_blank\") {\n img(class: 'screenshot', src: \"img/courses/${course.cover}\")\n }\n div(class: 'metadata') {\n a(href: course.url, target: '\\_blank') {\n h1(class: 'title', course.title)\n }\n span(class: 'instructor', \"By ${course.instructor}\")\n div(class: 'description') {\n yieldUnescaped course.description\n }\n }\n }\n}\n",[676,25327,25328,25341,25345,25358,25363,25375,25383,25387,25394,25401,25406,25411,25415,25422,25433,25450,25468,25472,25484,25500,25513,25518,25535,25547,25552,25556,25560,25564],{"__ignoreMap":674},[679,25329,25330,25333,25336,25339],{"class":681,"line":682},[679,25331,25332],{"class":880},"hr",[679,25334,25335],{"class":693},"(class: ",[679,25337,25338],{"class":689},"'divider'",[679,25340,1339],{"class":693},[679,25342,25343],{"class":681,"line":790},[679,25344,889],{"emptyLinePlaceholder":797},[679,25346,25347,25349,25352,25355],{"class":681,"line":892},[679,25348,812],{"class":880},[679,25350,25351],{"class":693},"(name: ",[679,25353,25354],{"class":689},"'courses'",[679,25356,25357],{"class":693},") {}\n",[679,25359,25360],{"class":681,"line":901},[679,25361,25362],{"class":693},"h2 {\n",[679,25364,25365,25368,25370,25373],{"class":681,"line":909},[679,25366,25367],{"class":880}," i",[679,25369,25335],{"class":693},[679,25371,25372],{"class":689},"'fa fa-film'",[679,25374,25357],{"class":693},[679,25376,25377,25380],{"class":681,"line":918},[679,25378,25379],{"class":685}," yield",[679,25381,25382],{"class":689}," ' Courses'\n",[679,25384,25385],{"class":681,"line":935},[679,25386,996],{"class":693},[679,25388,25389,25392],{"class":681,"line":944},[679,25390,25391],{"class":693},"p ",[679,25393,25176],{"class":689},[679,25395,25396,25399],{"class":681,"line":959},[679,25397,25398],{"class":689}," Another great resource for learning Groovy is by watching a course. You could spend time hunting dow",[679,25400,7252],{"class":6561},[679,25402,25403],{"class":681,"line":964},[679,25404,25405],{"class":693}," various videos on the web but these courses have all the information you need packed into one place.\n",[679,25407,25408],{"class":681,"line":977},[679,25409,25410],{"class":689}," '''\n",[679,25412,25413],{"class":681,"line":982},[679,25414,889],{"emptyLinePlaceholder":797},[679,25416,25417,25420],{"class":681,"line":988},[679,25418,25419],{"class":689},"courses.each { Course course -",[679,25421,4519],{"class":6561},[679,25423,25424,25427,25429,25431],{"class":681,"line":993},[679,25425,25426],{"class":880}," div",[679,25428,25335],{"class":693},[679,25430,25354],{"class":689},[679,25432,4390],{"class":693},[679,25434,25435,25438,25441,25443,25445,25448],{"class":681,"line":2129},[679,25436,25437],{"class":880}," a",[679,25439,25440],{"class":693},"(href: course.url, target: ",[679,25442,4857],{"class":689},[679,25444,3422],{"class":931},[679,25446,25447],{"class":689},"blank\"",[679,25449,4390],{"class":693},[679,25451,25452,25455,25457,25460,25463,25466],{"class":681,"line":2140},[679,25453,25454],{"class":880}," img",[679,25456,25335],{"class":693},[679,25458,25459],{"class":689},"'screenshot'",[679,25461,25462],{"class":693},", src: ",[679,25464,25465],{"class":689},"\"img/courses/${course.cover}\"",[679,25467,1339],{"class":693},[679,25469,25470],{"class":681,"line":2145},[679,25471,1290],{"class":693},[679,25473,25474,25477,25479,25482],{"class":681,"line":2154},[679,25475,25476],{"class":880}," div",[679,25478,25335],{"class":693},[679,25480,25481],{"class":689},"'metadata'",[679,25483,4390],{"class":693},[679,25485,25486,25489,25491,25493,25495,25498],{"class":681,"line":2159},[679,25487,25488],{"class":880}," a",[679,25490,25440],{"class":693},[679,25492,3056],{"class":689},[679,25494,3422],{"class":931},[679,25496,25497],{"class":689},"blank'",[679,25499,4390],{"class":693},[679,25501,25502,25505,25507,25510],{"class":681,"line":2164},[679,25503,25504],{"class":880}," h1",[679,25506,25335],{"class":693},[679,25508,25509],{"class":689},"'title'",[679,25511,25512],{"class":693},", course.title)\n",[679,25514,25515],{"class":681,"line":3134},[679,25516,25517],{"class":693}," }\n",[679,25519,25520,25523,25525,25528,25530,25533],{"class":681,"line":3139},[679,25521,25522],{"class":880}," span",[679,25524,25335],{"class":693},[679,25526,25527],{"class":689},"'instructor'",[679,25529,2797],{"class":693},[679,25531,25532],{"class":689},"\"By ${course.instructor}\"",[679,25534,1339],{"class":693},[679,25536,25537,25540,25542,25545],{"class":681,"line":3144},[679,25538,25539],{"class":880}," div",[679,25541,25335],{"class":693},[679,25543,25544],{"class":689},"'description'",[679,25546,4390],{"class":693},[679,25548,25549],{"class":681,"line":3149},[679,25550,25551],{"class":693}," yieldUnescaped course.description\n",[679,25553,25554],{"class":681,"line":3169},[679,25555,25517],{"class":693},[679,25557,25558],{"class":681,"line":3185},[679,25559,1290],{"class":693},[679,25561,25562],{"class":681,"line":3194},[679,25563,985],{"class":693},[679,25565,25566],{"class":681,"line":3199},[679,25567,996],{"class":693},[4542,25569,25571],{"id":25570},"generating-a-static-site","Generating a static site",[651,25573,25574],{},"Now that we made the changes to our source code we need to generate our website. The entire website is a static website that is generated from a whole bunch of groovy code. To generate the website go into the root of the project and run the following command. ",[669,25576,25578],{"className":5851,"code":25577,"language":5853,"meta":674,"style":674},"./gradlew generate\n",[676,25579,25580],{"__ignoreMap":674},[679,25581,25582,25585],{"class":681,"line":682},[679,25583,25584],{"class":880},"./gradlew",[679,25586,25587],{"class":689}," generate\n",[651,25589,25590],{},[660,25591],{"alt":25592,"src":25593},"Static Site Generation","./2017-05-24_08-00-52-1024x551.png",[651,25595,25596],{},"If you look in the build/site directory you will see the generated website. If you go to that location in your browser you can check out the site and make sure everything looks ok.",[4542,25598,25600],{"id":25599},"contributing-to-the-apache-groovy-website-screencast","Contributing to the Apache Groovy Website Screencast.",[651,25602,25603],{},"In this screencast, I am going to show you how I did that. I really enjoyed learning how the entire website was built in Groovy and then they simply generate a static site.",[651,25605,25606],{},[812,25607,25608],{"href":25608,"rel":25609},"https://www.youtube.com/watch?v=BXw-YKxr94w",[816],[4542,25611,24640],{"id":25612},"the-complete-apache-groovy-developer-course",[651,25614,25615,25616,664],{},"Are you looking to learn a new language? Learning a new language helps expand your skill set as a developer and make you more marketable. If you aren't learning a new language because it's not the one you use at work I think you're missing out on the benefits. I often found that seeing how other languages solve problems makes me a better developer with the languages I use on a day to day basis. I am here to tell you all about an awesome dynamic language called ",[812,25617,25620],{"href":25618,"rel":25619},"http://www.groovy-lang.org",[816],"Apache Groovy",[651,25622,25623],{},[660,25624],{"alt":24640,"src":25625},"./756634_ad7b_3.jpg",[651,25627,25628],{},[812,25629,24640],{"href":24638,"rel":25630},[816],[4542,25632,9042],{"id":9041},[651,25634,25635],{},"I said this earlier in the article and I would like to repeat it again. You don't always have to contribute code to the core of a project to help an open source project out. I have contributed documentation, static websites, plugins and even just offering up suggestions on the mailing lists. There are tons of ways to get involved so If you really enjoy using an open source project contact them and see how you can get involved.",[651,25637,25638],{},[7300,25639,25640,25642],{},[2939,25641,11650],{}," What are ways that you like to contribute to a project?",[786,25644,25645],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":25647},[25648,25649,25650,25653,25654,25655,25656],{"id":24644,"depth":790,"text":24645},{"id":24657,"depth":790,"text":24658},{"id":24680,"depth":790,"text":24681,"children":25651},[25652],{"id":24882,"depth":892,"text":24883},{"id":25570,"depth":790,"text":25571},{"id":25599,"depth":790,"text":25600},{"id":25612,"depth":790,"text":24640},{"id":9041,"depth":790,"text":9042},{"slug":25658,"published":797,"date":25659,"tags":25660,"cover":25661},"contributing-groovy-website","2017-05-24T08:12:01-04:00",[870],"./2017-05-23_19-50-54-760x462.png",{"title":159,"description":159},"blog/2017/05/24/contributing-groovy-website","a6vk6LxMqyTK0Wfy3LCfBTw1PkmW5A3aYYDFyFMucH4",{"id":25666,"title":156,"body":25667,"description":156,"extension":793,"meta":26450,"navigation":797,"path":157,"seo":26455,"stem":26456,"__hash__":26457},"content/blog/2017/05/26/so-you-want-to-create-an-online-course.md",{"type":648,"value":25668,"toc":26431},[25669,25678,25681,25684,25689,25692,25697,25703,25706,25722,25725,25729,25732,25738,25742,25745,25748,25751,25755,25758,25761,25767,25770,25773,25778,25781,25785,25788,25791,25797,25800,25803,25806,25810,25813,25816,25822,25826,25829,25834,25837,25843,25847,25850,25853,25873,25879,25882,25896,25899,25904,25908,25911,25937,25941,25944,25967,25971,25974,25979,25982,26005,26010,26013,26036,26040,26049,26059,26069,26072,26076,26079,26085,26103,26109,26126,26130,26133,26136,26146,26150,26153,26156,26179,26183,26186,26209,26213,26216,26220,26223,26231,26234,26251,26255,26257,26271,26273,26278,26282,26285,26288,26298,26302,26305,26308,26312,26315,26321,26327,26330,26335,26341,26347,26351,26354,26358,26374,26379,26388,26393,26415,26418,26422,26425],[651,25670,25671,25672,25677],{},"I recently wrote this article on ",[812,25673,25676],{"href":25674,"rel":25675},"https://medium.com/@therealdanvega/so-you-want-to-create-an-online-course-8c2519da1863",[816],"Medium"," and thought I would share it here with this audience today. I know there are a lot of Software Developers out there that want to get into creating courses and if that's you I think you're really going to enjoy this article. ",[651,25679,25680],{},"I get really fired up about teaching online because I enjoy doing something that is improving the life of someone else. It might be on a very small scale but I still like to think I am making a difference. I also enjoy that, on occasion, I get someone else fired up about teaching online.",[651,25682,25683],{},"Lately, I have been receiving messages from friends and family about how they want to create their own courses. This week I got a message from a friend that I would like to share with you.",[1004,25685,25686],{},[651,25687,25688],{},"Now that I don’t work at Apple anymore I’m not contractually locked to exclusively teaching in an Apple Store. So I’m currently working on my first Udemy course. Based on what I’ve seen you do I’ve been wanting to do this for years. I’m pretty excited about it! Just thought I’d shoot you a quick message and let you know.",[651,25690,25691],{},"I was not only excited that my friend was going to create his first course but that I had some small part in inspiring him to do so. This reminding me of a quote I love and a great way to look at how helping others can lead us to accomplish our goals.",[1004,25693,25694],{},[651,25695,25696],{},"“You will get all you want in life if you help enough other people get what they want.” — Zig Ziglar",[651,25698,25699],{},[660,25700],{"alt":25701,"src":25702},"Lady Video Shooting","./pexels-photo-322045-683x1024.jpeg",[651,25704,25705],{},"Now that you have a little idea of why I sat down and took some to write this article, here's what we are going to cover in it.",[5316,25707,25708,25711,25714,25717,25720],{},[5332,25709,25710],{},"What are you going to teach?",[5332,25712,25713],{},"Creating your course",[5332,25715,25716],{},"Where will you publish?",[5332,25718,25719],{},"Building an audience",[5332,25721,21931],{},[651,25723,25724],{},"I am going to give you a little information about each of these topics. This is meant as an introduction and I could probably write chapters to a book on each of these subjects so just remember that if we don’t go too far down the rabbit hole.",[4542,25726,25728],{"id":25727},"what-are-you-going-toteach","What are you going to teach?",[651,25730,25731],{},"The first thing we need to figure out is what your course is going to be about. You probably have some ideas already and are just wondering if these ideas will translate into courses. In this section, we are going to talk about how finding your passion and validating your subject.",[651,25733,25734],{},[660,25735],{"alt":25736,"src":25737},"Creating Online Courses - What are you going to teach?","./startup-photos-1024x683.jpg",[5909,25739,25741],{"id":25740},"find-yourpassion","Find your Passion",[651,25743,25744],{},"The first thing I need everyone reading this to do is to stop assuming that what you know is common knowledge. I run across so many people that think what they know or how they perform a specific task a certain way is just how the world works and that just isn’t true. You do have something to offer the world and it's about time you start realizing it.",[651,25746,25747],{},"The easiest subjects to build courses on are things you’re passionate about. If you love to share your experiences in health and fitness, for example, this is what you should be talking about. Talking about cats might be a popular subject but if you aren’t into it, you will get called out on it.",[651,25749,25750],{},"Your students will know if you’re passionate about a subject or not. If you’re not excited about teaching something, how do you expect students to be excited to learn it? So with that my #1 tip when it comes to teaching is you have to pick something you’re passionate about.",[5909,25752,25754],{"id":25753},"selecting-aniche","Selecting a Niche",[651,25756,25757],{},"A big mistake new instructors often make is the belief that just because there are other courses in the marketplace already, that they can’t succeed. When I hear this I quickly turn this into a positive thing and get them even more excited.",[651,25759,25760],{},"Existing courses in the marketplace simply tell us that someone else has already done the hard work for us and validated that there is a demand for our course. Now if there 75 courses on the exact same thing then we need to go a layer deeper. In fact no matter how many courses there are I always suggest going at least 2 levels and maybe event 3 levels deep.",[651,25762,25763],{},[660,25764],{"alt":25765,"src":25766},"Creating Online Courses - Finding your niche","./pexels-photo-256541-1024x682.jpeg",[651,25768,25769],{},"This sounds great Dan, but what do you mean to go a layer deeper? Going back to our example of health and fitness let's say that I wanted to create a course on yoga. It’s safe to assume that yoga has a very wide range of topics and not only will you run into competition there but if you were going to teach a course on everything you knew about yoga that would be a huge course.",[651,25771,25772],{},"So we start thinking about topics inside of yoga and realize that we are very good at standing our head. This is often tough for people new to yoga so we are going to teach them all about getting upside down. We just took a subject we are passionate about, yoga, and found a specific subject inside of that niche and created our Headstand Workshop class. This is what I mean by going to the 2nd or even 3rd level.",[651,25774,25775],{},[660,25776],{"alt":25765,"src":25777},"./pexels-photo-169762-1024x684.jpeg",[651,25779,25780],{},"Sticking with our example, what if we wanted to create a course called “How to do a headstand for 20 min straight”. I am just making this up at this point but you get the idea. This is a very specific topic and if there are no other courses on the market we should probably validate this further before we run off and create a course on this subject.",[5909,25782,25784],{"id":25783},"validating-your-courseidea","Validating your course idea",[651,25786,25787],{},"We already looked at how to select your niche if there is a market for your course but what if there are no existing courses on your topic? This doesn’t mean that people still don’t want to learn about it. This just means that we should do a little work upfront to validate our idea before we run off and create a huge course on the subject.",[651,25789,25790],{},"The first place you can always start is with friends and family. Going back to our yoga example it's probably safe to assume that if you’re passionate about yoga that you might do it with friends, take a class or even teach classes. In that case, it's pretty simple, ask those people what they think of your idea. If you come up with a good plan for the workshop you might even be able to teach it to them one day and get their opinion.",[651,25792,25793],{},[660,25794],{"alt":25795,"src":25796},"Creating Online Courses - Validating your ideas","./pexels-photo-26135-1024x681.jpg",[651,25798,25799],{},"After running it by friends and family you can start to ask any social networks you’re involved in. Throw something on Facebook on your wall or in a specific group and see what they say.",[651,25801,25802],{},"If all of these things go well you could create a YouTube channel and test a couple videos out. If you get more interest you could create a blog and ask people to signup if there interested in your course.",[651,25804,25805],{},"This is, of course, the simplified version of this but hopefully, you get the idea. Make sure you validate your idea before you just run in blind. Some of the feedback might be great and you might get ideas you would have never thought of if you didn’t do this step first.",[5909,25807,25809],{"id":25808},"how-long-should-itbe","How long Should it be?",[651,25811,25812],{},"Another question I hear a lot is how long does my course need to be. Your course should probably be at least an hour in length. After that, there are no restrictions. You can create a course that is 1 hour, I have created courses that are 14 hours and top online instructors have courses in the 40–60-hour range.",[651,25814,25815],{},"The key here is making sure your course has a solid title and description. Your students should be very clear on what they are going to get out of this course before they take it. At that point, your course should be as long as it needs to be to deliver on what you promised them they would learn.",[651,25817,25818],{},[660,25819],{"alt":25820,"src":25821},"Creating Online Courses - Course Length","./pexels-photo-66134-1024x683.jpeg",[4542,25823,25825],{"id":25824},"creating-yourcourse","Creating your Course",[651,25827,25828],{},"When I start talking to people about creating courses they start dreaming of big budget Hollywood sets when all they need is a home office. There might be a few things you need to purchase but you can always start off small and scale up later on. In this section, I will give you a good, better and best_*_options for equipment and my suggestions for starting off.",[651,25830,25831],{},[7300,25832,25833],{},"* Best doesn’t mean a $50,000 camera it just means it’s my suggestion for the next logical step.",[651,25835,25836],{},"This is obviously an important step so take your time here and get this one right.",[651,25838,25839],{},[660,25840],{"alt":25841,"src":25842},"Creating Online Courses - Creating your course.","https://cdn-images-1.medium.com/max/800/1*mdUW2rOKrVPk5zxc1aonEQ.jpeg",[5909,25844,25846],{"id":25845},"creating-a-curriculum","Creating a Curriculum",[651,25848,25849],{},"The first thing we need to do is create a course curriculum. The important part here to remember is that this is probably going to be fluid and can change during the course creation process.",[651,25851,25852],{},"I like to start with an outline of what each module or section will look like. It is probably a good idea to start with an introduction to you the instructor and what they can plan to get out of the course. If I was teaching a course on how to create your own course my initial outline might look like this.",[5316,25854,25855,25858,25861,25864,25867,25870],{},[5332,25856,25857],{},"Introduction",[5332,25859,25860],{},"What are you going to teach",[5332,25862,25863],{},"Creating your Course",[5332,25865,25866],{},"Where will you publish",[5332,25868,25869],{},"Marketing your course",[5332,25871,25872],{},"Goodbye",[651,25874,25875],{},[660,25876],{"alt":25877,"src":25878},"Creating Online Courses - Create your curriculum","./pexels-photo-139496-1024x769.jpeg",[651,25880,25881],{},"This would be my initial outline but just looking at this I can already tell that the creating your course section would get very long. I would probably break curriculum, video, audio & lighting into their own sections. Here are a few tips that I have seen in other courses and try to stick to in any of my new courses.",[5316,25883,25884,25887,25890,25893],{},[5332,25885,25886],{},"Mix talking head and screen share videos",[5332,25888,25889],{},"Keep your videos 4–7 min and no longer than 10 min unless needed",[5332,25891,25892],{},"Try for 10 videos or less in a section",[5332,25894,25895],{},"Keep students engaged with exercises & quizzes",[651,25897,25898],{},"Those are of course just guidelines. You have to remember that you are here to help your students accomplish a certain goal. If you need to go outside the lines to do that, then have at it.",[651,25900,25901],{},[7300,25902,25903],{},"Some of my links below are affiliate links and if you’re going to purchase something please use my links to support this article.",[5909,25905,25907],{"id":25906},"video-equipment","Video Equipment",[651,25909,25910],{},"I shot my first course entirely on my iPhone 6. The only downside to this is you can’t see yourself in the shot so you have to keep running around to see if the shot looks good. I eventually upgraded to the Canon 70d and I love the flip screen and auto focus on that camera.",[5316,25912,25913,25921,25929],{},[5332,25914,25915,25916],{},"(Good) ",[812,25917,25920],{"href":25918,"rel":25919},"http://amzn.to/2pH94eH",[816],"iPhone 7 Plus",[5332,25922,25923,25924],{},"(Better) ",[812,25925,25928],{"href":25926,"rel":25927},"http://amzn.to/2pHsPCX",[816],"Canon 70D",[5332,25930,25931,25932],{},"(Best) ",[812,25933,25936],{"href":25934,"rel":25935},"http://amzn.to/2rb0zd8",[816],"Sony Alpha a7s Mirrorless Digital Camera",[5259,25938,25940],{"id":25939},"lighting-equipment","Lighting Equipment",[651,25942,25943],{},"Lighting is one of those things that everyone forgets about. If you can get some good natural light that is great but that limits your production time to those perfect days.",[5316,25945,25946,25953,25960],{},[5332,25947,25915,25948],{},[812,25949,25952],{"href":25950,"rel":25951},"http://amzn.to/2pIuZlP",[816],"Photography Photo Portrait Studio Kit",[5332,25954,25923,25955],{},[812,25956,25959],{"href":25957,"rel":25958},"http://amzn.to/2pvxprS",[816],"Fancierstudio 2400 Watt Lighting Kit",[5332,25961,25931,25962],{},[812,25963,25966],{"href":25964,"rel":25965},"http://amzn.to/2qdHSqW",[816],"LimoStudio LED Lighting Kit",[5259,25968,25970],{"id":25969},"audio-equipment","Audio Equipment",[651,25972,25973],{},"Nothing ruins a video for me more than bad audio. If you asked me where you should spend money for a better quality product its here.",[651,25975,25976],{},[2939,25977,25978],{},"Talking Head Videos",[651,25980,25981],{},"These are videos where it is just you standing in front of the camera and having a conversation with your students.",[5316,25983,25984,25991,25998],{},[5332,25985,25915,25986],{},[812,25987,25990],{"href":25988,"rel":25989},"http://amzn.to/2pIQyCF",[816],"Stony-Edge Lavalier Microphone",[5332,25992,25923,25993],{},[812,25994,25997],{"href":25995,"rel":25996},"http://amzn.to/2qDiCf7",[816],"Rode VideoMic Pro",[5332,25999,25931,26000],{},[812,26001,26004],{"href":26002,"rel":26003},"http://amzn.to/2qDkZOX",[816],"Sennheiser Wireless Mic Kit",[651,26006,26007],{},[2939,26008,26009],{},"Screencast",[651,26011,26012],{},"These are videos where you record your screen showing them a presentation or exactly what you’re doing on screen.",[5316,26014,26015,26022,26029],{},[5332,26016,25915,26017],{},[812,26018,26021],{"href":26019,"rel":26020},"http://amzn.to/2r3RN3x",[816],"Blue Snowball iCE Condenser Microphone",[5332,26023,25923,26024],{},[812,26025,26028],{"href":26026,"rel":26027},"http://amzn.to/2rbyrWB",[816],"Audio-Technica AT2020 Condenser Microphone",[5332,26030,25931,26031],{},[812,26032,26035],{"href":26033,"rel":26034},"http://amzn.to/2qDjtfx",[816],"Heil PR-40 Dynamic Studio Recording Microphone",[5259,26037,26039],{"id":26038},"backgrounds","Backgrounds",[651,26041,26042,26043,26048],{},"If you have a great looking place around the house to shoot you should take advantage of that. If you don’t you can use ",[812,26044,26047],{"href":26045,"rel":26046},"http://amzn.to/2r4sP45",[816],"seamless background"," paper to give you a really good looking video. To hang that background paper you need either a stand or some type of wall mount.",[651,26050,26051],{},[812,26052,26055,26058],{"href":26053,"rel":26054,"title":26053},"http://amzn.to/2qDfmQQ",[816],[2939,26056,26057],{},"LimoStudio Photo Video Studio 10Ft Adjustable Muslin Background Backdrop Support System Stand…"," _Backdrop Support System with 10' Crossbar and Case 2 x Premire Muslin Backdrop Support System Stand * Easy to set up…_amzn.to",[651,26060,26061],{},[812,26062,26065,26068],{"href":26063,"rel":26064,"title":26063},"http://amzn.to/2pIKKso",[816],[2939,26066,26067],{},"CowboyStudio Photography 3-Roller Wall Mounting Manual Background Support System"," _This background support system is a smart choice for substitute of expensive electrical roller system. Durable yet…_amzn.to",[651,26070,26071],{},"I have the 3 roller wall mount in my home studio and it works out great.",[5259,26073,26075],{"id":26074},"software","Software",[651,26077,26078],{},"There are a few pieces of software that you are going to need to create and publish your course. There are some free options in this category but as you can imagine a little money spent here goes a long way.",[651,26080,26081,26084],{},[2939,26082,26083],{},"Video Editing Software"," When you create your videos you will take them from the source and there will be some editing needed.",[5316,26086,26087,26095],{},[5332,26088,26089,26094],{},[812,26090,26093],{"href":26091,"rel":26092},"https://www.apple.com/final-cut-pro/",[816],"Final Cut Pro"," (Mac)",[5332,26096,26097,26102],{},[812,26098,26101],{"href":26099,"rel":26100},"http://www.adobe.com/products/premiere.html",[816],"Adobe Premiere Pro"," (Mac & Windows)",[651,26104,26105,26108],{},[2939,26106,26107],{},"Screen Recording Software"," There are a bunch of options for recording screen presentations but I am going to leave you with 2 of my favorites for Mac and Windows.",[5316,26110,26111,26118],{},[5332,26112,26113,26094],{},[812,26114,26117],{"href":26115,"rel":26116},"https://www.telestream.net/screenflow/overview.htm",[816],"ScreenFlow",[5332,26119,26120,26125],{},[812,26121,26124],{"href":26122,"rel":26123},"https://www.techsmith.com/camtasia.html",[816],"Camtasia Studio"," (Windows)",[5909,26127,26129],{"id":26128},"where-to-publish-yourcourse","Where to publish your course?",[651,26131,26132],{},"Now that we have created our course the question is where do we publish it. There are plenty of options depending on your needs and what features you want. This decision is going to depend on what type of existing following you or your brand has or doesn’t have.",[651,26134,26135],{},"If you have an existing audience that comes with a large email list and social media following you are going to be self-hosting your course. If you don’t have an existing following we are going to start with marketplaces. The beauty of starting here is we can build our audience and then begin to move to a self-hosted platform.",[651,26137,26138],{},[812,26139,26142],{"href":26140,"rel":26141},"https://danvega.dev/wp-content/uploads/2017/05/1-Ouwik01c13p99QCkzRT7AA.jpeg",[816],[660,26143],{"alt":26144,"src":26145},"Creating Online Course - Where to publish your course?","./1-Ouwik01c13p99QCkzRT7AA-1024x682.jpeg",[5259,26147,26149],{"id":26148},"marketplace","Marketplace",[651,26151,26152],{},"A marketplace is a site that contains a collection of courses and already has an existing user base in place. What this means to you is that all you need to focus on is publishing the course. If you create a good title, description and eventually get some good reviews the course will sell itself.",[651,26154,26155],{},"Some examples of marketplaces that you can publish your course on are:",[5316,26157,26158,26165,26172],{},[5332,26159,26160],{},[812,26161,26164],{"href":26162,"rel":26163},"http://www.udemy.com/",[816],"Udemy",[5332,26166,26167],{},[812,26168,26171],{"href":26169,"rel":26170},"https://www.skillshare.com/",[816],"SkillShare",[5332,26173,26174],{},[812,26175,26178],{"href":26176,"rel":26177},"https://www.pluralsight.com/",[816],"Pluralsight",[5259,26180,26182],{"id":26181},"self-hosted","Self-Hosted",[651,26184,26185],{},"A self-hosted course is one that we host on our own website or we can use one the platforms below. The key in a self-hosted course is we have full control over all aspects of the course from collecting email addresses to what it will be priced at.",[5316,26187,26188,26195,26202],{},[5332,26189,26190],{},[812,26191,26194],{"href":26192,"rel":26193},"http://www.teachable.com/",[816],"Teachable",[5332,26196,26197],{},[812,26198,26201],{"href":26199,"rel":26200},"https://www.thinkific.com/",[816],"Thinkific",[5332,26203,26204],{},[812,26205,26208],{"href":26206,"rel":26207},"https://zippycoursesplugin.com/",[816],"Zippy Courses",[5259,26210,26212],{"id":26211},"marketplace-vs-selfhosted","Marketplace vs Self Hosted",[651,26214,26215],{},"There are pros and cons to each of our platforms. As I said earlier if you don’t have much of a following yet, that is ok, but I would start on a marketplace. With that said you should make the move that you think is right for you and your brand.",[651,26217,26218],{},[2939,26219,26149],{},[651,26221,26222],{},"Pros",[5316,26224,26225,26228],{},[5332,26226,26227],{},"Existing Student Base",[5332,26229,26230],{},"They will promote your courses",[651,26232,26233],{},"Cons",[5316,26235,26236,26239,26242,26245,26248],{},[5332,26237,26238],{},"You can’t build your email list",[5332,26240,26241],{},"Usually going to sell courses for less",[5332,26243,26244],{},"Paid on a monthly basis",[5332,26246,26247],{},"You have to go through an approval process",[5332,26249,26250],{},"They have control over pricing limits",[651,26252,26253],{},[2939,26254,26182],{},[651,26256,26222],{},[5316,26258,26259,26262,26265,26268],{},[5332,26260,26261],{},"Build your email list",[5332,26263,26264],{},"You can usually make more per sale",[5332,26266,26267],{},"You can bundle courses together to create new products",[5332,26269,26270],{},"You can get paid right away.",[651,26272,26233],{},[5316,26274,26275],{},[5332,26276,26277],{},"You have to do all the promotion for your course",[4542,26279,26281],{"id":26280},"building-anaudience","Building an Audience",[651,26283,26284],{},"The best thing you can do for yourself after you launch your course is to stay involved with your students. This is where I always tell people that there is no such thing as 100% passive income. You need stay involved and respond to questions and feedback you are going to establish trust with your audience.",[651,26286,26287],{},"When you can establish trust and build an audience everyone wins. Your students win because they get out of your course exactly what you promised. You win in both monetary value and knowing that you helped to improve someone's life.",[651,26289,26290],{},[812,26291,26294],{"href":26292,"rel":26293},"https://danvega.dev/wp-content/uploads/2017/05/pexels-photo-66463.jpeg",[816],[660,26295],{"alt":26296,"src":26297},"Creating Online Course - Building an audience?","./pexels-photo-66463-1024x683.jpeg",[5909,26299,26301],{"id":26300},"course-feedback","Course Feedback",[651,26303,26304],{},"The first thing you need to do when you launch your course is to get it in front of some people to get some initial feedback. I usually start with friends or family. In my case, my courses were programming related so I created free coupons for my coworkers. This is a great time to get some feedback and see if you can improve on anything.",[651,26306,26307],{},"If your course is in a marketplace this is also a good time to start getting reviews. Potential students are looking at your reviews to see what other people think of your course. I would send out free coupons and say in exchange for the free coupon please give me a review. It doesn’t need to be good just your honest review of the course.",[4542,26309,26311],{"id":26310},"promoting-yourcourse","Promoting your Course",[651,26313,26314],{},"No matter where you publish your course you want to be out there promoting your course.",[651,26316,26317],{},[660,26318],{"alt":26319,"src":26320},"Creating Online Course - Promoting your course","./marketing-man-person-communication-1024x683.jpg",[651,26322,26323,26326],{},[2939,26324,26325],{},"Blog, Social Media & Email List"," If you have a blog this is obviously the best place to start. I would write up a summary of what your course is about and what potential students can get out of it. Most of the time you will already have this copy in the course description.",[651,26328,26329],{},"If you have your own blog this a great chance to start building your email list. This list will help you market future courses to students who are already interested in what you’re doing.",[651,26331,26332,26334],{},[2939,26333,15432],{}," This is one of the best places to market your course. Start a YouTube channel and post some of the videos on your channel. In the video you will want to link to your course landing page so they know where to go to purchase the full course.",[651,26336,26337,26340],{},[2939,26338,26339],{},"Webinar"," Webinars are another great resource for promoting your course. This is a chance to give some real value to anyone attending your webinar and to teach them something. At the beginning of the webinar let them know that you have a special offer for them coming at the end of the presentation. This is where you can let them know about the course and good strategy here is to give them some kind of discount.",[651,26342,26343,26346],{},[2939,26344,26345],{},"Paid Advertising"," I won’t get too far into strategy here because there is a lot that goes into this. You can use paid advertising (Google/Facebook/YouTube/Pinterest) to drive people to landing pages that will allow the to sign up for webinars or lead magnets related to your course. When you have their email address you can slowly introduce them to your course and eventually ask them to purchase it. Again this is for another article but pushing paid advertising directly to a course landing page usually doesn’t work.",[4542,26348,26350],{"id":26349},"learning-resources","Learning / Resources",[651,26352,26353],{},"I really hope this article was useful for you. Now that you have a good idea what it’s going to take to build your course I want to leave you with some resources. These are all great ways to learn more about building your course.",[651,26355,26356],{},[2939,26357,21973],{},[5316,26359,26360,26367],{},[5332,26361,26362],{},[812,26363,26366],{"href":26364,"rel":26365},"https://www.udemy.com/online-course-masters/?couponCode=DANVEGA19",[816],"Udemy Masters 2017: Online Course Creation",[5332,26368,26369],{},[812,26370,26373],{"href":26371,"rel":26372},"https://www.udemy.com/user/udemymanager/",[816],"Official Udemy Insights",[651,26375,26376],{},[2939,26377,26378],{},"Podcasts",[5316,26380,26381],{},[5332,26382,26383],{},[812,26384,26387],{"href":26385,"rel":26386},"https://onlinecoursemasters.com/blog",[816],"Online Course Masters",[651,26389,26390],{},[2939,26391,26392],{},"Facebook Groups",[5316,26394,26395,26401,26408],{},[5332,26396,26397],{},[812,26398,26387],{"href":26399,"rel":26400},"https://www.facebook.com/groups/onlinecoursemasters/",[816],[5332,26402,26403],{},[812,26404,26407],{"href":26405,"rel":26406},"https://www.facebook.com/groups/thefedorafamily/",[816],"Teachable Tribe",[5332,26409,26410],{},[812,26411,26414],{"href":26412,"rel":26413},"https://www.facebook.com/groups/udemyfacultylounge/",[816],"Udemy Instructors Club",[651,26416,26417],{},"While I do believe that investing in yourself and continuing to learn is the greatest gift you can give yourself you do need to take action at some point. Spend the time to learn but don’t procrastinate creating your first course. What do you have to lose? Take action and build something great!",[4542,26419,26421],{"id":26420},"whats-nextdan","What’s Next Dan?",[651,26423,26424],{},"If you enjoyed this article you are really going to enjoy my next project. Subscribe to my mailing list and I will keep you in the loop on my next big thing. If you signup today I will give you a free eBook that will give you my top 10 side hustles to build a passive income.",[651,26426,26427],{},[812,26428,26429],{"href":26429,"rel":26430},"https://danvega.dev/sidehustle",[816],{"title":674,"searchDepth":790,"depth":790,"links":26432},[26433,26439,26444,26447,26448,26449],{"id":25727,"depth":790,"text":25728,"children":26434},[26435,26436,26437,26438],{"id":25740,"depth":892,"text":25741},{"id":25753,"depth":892,"text":25754},{"id":25783,"depth":892,"text":25784},{"id":25808,"depth":892,"text":25809},{"id":25824,"depth":790,"text":25825,"children":26440},[26441,26442,26443],{"id":25845,"depth":892,"text":25846},{"id":25906,"depth":892,"text":25907},{"id":26128,"depth":892,"text":26129},{"id":26280,"depth":790,"text":26281,"children":26445},[26446],{"id":26300,"depth":892,"text":26301},{"id":26310,"depth":790,"text":26311},{"id":26349,"depth":790,"text":26350},{"id":26420,"depth":790,"text":26421},{"slug":26451,"published":797,"date":26452,"tags":26453,"cover":26454},"so-you-want-to-create-an-online-course","2017-05-26T12:34:08-04:00",[25304],"./carl-heyerdahl-181868-1-760x507.jpg",{"title":156,"description":156},"blog/2017/05/26/so-you-want-to-create-an-online-course","cfyZ2MNy0qDXmHorB5G-LC7PrXm0UkieYUud0i84EH4",{"id":26459,"title":153,"body":26460,"description":153,"extension":793,"meta":27279,"navigation":797,"path":154,"seo":27284,"stem":27285,"__hash__":27286},"content/blog/2017/05/31/spring-boot-1-question-students-asking-right-now.md",{"type":648,"value":26461,"toc":27271},[26462,26469,26472,26475,26481,26485,26488,26514,26516,26519,26962,26965,27023,27026,27208,27212,27219,27225,27235,27240,27243,27256,27258,27261,27268],[651,26463,26464,26465,26468],{},"Today's Question & Answer came in from a student in my ",[812,26466,18347],{"href":22101,"rel":26467},[816]," and it is one that I get a lot. This tells me that I probably need to address this in the course but it's also an opportunity for me to share this with you. The question is: \"I created a new application from scratch using the Web, Spring Data JPA & H2 dependencies. If I create an entity and then start the application up I don't see the tables created in the H2 database for me. What is going wrong?\"",[4542,26470,18978],{"id":26471},"spring-boot-application",[651,26473,26474],{},"The first thing we are going to do is to create our Spring Boot Application using the Spring Initializer. In this demo, we are going to select the Web, JPA & H2 dependencies. ",[651,26476,26477],{},[660,26478],{"alt":26479,"src":26480},"Sping Boot Application","./2017-05-31_08-15-43-1024x645.png",[5909,26482,26484],{"id":26483},"h2-database-settings","H2 Database Settings",[651,26486,26487],{},"The next thing we have to do is enable the H2 database console in our application.properties. You have to enable this if you want to be able to use this awesome little tool. There is also a property to change the path for the H2 console if you don't want to use the default. ",[669,26489,26491],{"className":25132,"code":26490,"language":25134,"meta":674,"style":674},"spring.h2.console.enabled=true\n#spring.h2.console.path=/h2-console\n",[676,26492,26493,26501],{"__ignoreMap":674},[679,26494,26495,26497,26499],{"class":681,"line":682},[679,26496,7323],{"class":693},[679,26498,686],{"class":685},[679,26500,5134],{"class":931},[679,26502,26503,26506,26508,26510,26512],{"class":681,"line":790},[679,26504,26505],{"class":693},"#spring.h2.console.path",[679,26507,7335],{"class":685},[679,26509,4542],{"class":693},[679,26511,6453],{"class":685},[679,26513,7338],{"class":693},[5909,26515,11183],{"id":11182},[651,26517,26518],{},"Now we need to create our entity. I am going to keep it simple here but you can create whatever entity you like. ",[669,26520,26522],{"className":4107,"code":26521,"language":4109,"meta":674,"style":674},"package com.therealdanvega.domain;\n\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.Id;\n\n@Entity\npublic class Subscriber {\n\n @Id @GeneratedValue\n private Long id;\n private String first;\n private String last;\n private String email;\n\n public Subscriber(String first, String last, String email) {\n this.first = first;\n this.last = last;\n this.email = email;\n }\n\n public Long getId() {\n return id;\n }\n\n public void setId(Long id) {\n this.id = id;\n }\n\n public String getFirst() {\n return first;\n }\n\n public void setFirst(String first) {\n this.first = first;\n }\n\n public String getLast() {\n return last;\n }\n\n public void setLast(String last) {\n this.last = last;\n }\n\n public String getEmail() {\n return email;\n }\n\n public void setEmail(String email) {\n this.email = email;\n }\n\n @Override\n public String toString() {\n return \"Subscriber{\" +\n \"first='\" + first + '\\\\'' +\n \", last='\" + last + '\\\\'' +\n '}';\n }\n}\n",[676,26523,26524,26531,26535,26541,26547,26553,26557,26563,26574,26578,26588,26594,26600,26606,26612,26616,26636,26646,26656,26666,26670,26674,26684,26690,26694,26698,26712,26722,26726,26730,26740,26746,26750,26754,26768,26778,26782,26786,26796,26802,26806,26810,26824,26834,26838,26842,26852,26858,26862,26866,26880,26890,26894,26898,26904,26914,26923,26940,26949,26954,26958],{"__ignoreMap":674},[679,26525,26526,26528],{"class":681,"line":682},[679,26527,2543],{"class":685},[679,26529,26530],{"class":693}," com.therealdanvega.domain;\n",[679,26532,26533],{"class":681,"line":790},[679,26534,889],{"emptyLinePlaceholder":797},[679,26536,26537,26539],{"class":681,"line":892},[679,26538,1999],{"class":685},[679,26540,11209],{"class":693},[679,26542,26543,26545],{"class":681,"line":901},[679,26544,1999],{"class":685},[679,26546,11216],{"class":693},[679,26548,26549,26551],{"class":681,"line":909},[679,26550,1999],{"class":685},[679,26552,11223],{"class":693},[679,26554,26555],{"class":681,"line":918},[679,26556,889],{"emptyLinePlaceholder":797},[679,26558,26559,26561],{"class":681,"line":935},[679,26560,4116],{"class":693},[679,26562,11234],{"class":685},[679,26564,26565,26567,26569,26572],{"class":681,"line":944},[679,26566,6073],{"class":685},[679,26568,4512],{"class":685},[679,26570,26571],{"class":880}," Subscriber",[679,26573,884],{"class":693},[679,26575,26576],{"class":681,"line":959},[679,26577,889],{"emptyLinePlaceholder":797},[679,26579,26580,26582,26584,26586],{"class":681,"line":964},[679,26581,6872],{"class":693},[679,26583,11256],{"class":685},[679,26585,6475],{"class":693},[679,26587,11261],{"class":685},[679,26589,26590,26592],{"class":681,"line":977},[679,26591,9232],{"class":685},[679,26593,11268],{"class":693},[679,26595,26596,26598],{"class":681,"line":982},[679,26597,9232],{"class":685},[679,26599,13605],{"class":693},[679,26601,26602,26604],{"class":681,"line":988},[679,26603,9232],{"class":685},[679,26605,13619],{"class":693},[679,26607,26608,26610],{"class":681,"line":993},[679,26609,9232],{"class":685},[679,26611,13626],{"class":693},[679,26613,26614],{"class":681,"line":2129},[679,26615,889],{"emptyLinePlaceholder":797},[679,26617,26618,26620,26622,26624,26626,26628,26630,26632,26634],{"class":681,"line":2140},[679,26619,6089],{"class":685},[679,26621,26571],{"class":880},[679,26623,11400],{"class":693},[679,26625,13805],{"class":2099},[679,26627,20006],{"class":693},[679,26629,13927],{"class":2099},[679,26631,20006],{"class":693},[679,26633,13988],{"class":2099},[679,26635,4390],{"class":693},[679,26637,26638,26640,26642,26644],{"class":681,"line":2145},[679,26639,7862],{"class":931},[679,26641,13814],{"class":693},[679,26643,686],{"class":685},[679,26645,13783],{"class":693},[679,26647,26648,26650,26652,26654],{"class":681,"line":2154},[679,26649,7862],{"class":931},[679,26651,13936],{"class":693},[679,26653,686],{"class":685},[679,26655,13905],{"class":693},[679,26657,26658,26660,26662,26664],{"class":681,"line":2159},[679,26659,7862],{"class":931},[679,26661,13997],{"class":693},[679,26663,686],{"class":685},[679,26665,13966],{"class":693},[679,26667,26668],{"class":681,"line":2164},[679,26669,985],{"class":693},[679,26671,26672],{"class":681,"line":3134},[679,26673,889],{"emptyLinePlaceholder":797},[679,26675,26676,26678,26680,26682],{"class":681,"line":3139},[679,26677,6089],{"class":685},[679,26679,11306],{"class":693},[679,26681,11309],{"class":880},[679,26683,2667],{"class":693},[679,26685,26686,26688],{"class":681,"line":3144},[679,26687,9444],{"class":685},[679,26689,11318],{"class":693},[679,26691,26692],{"class":681,"line":3149},[679,26693,985],{"class":693},[679,26695,26696],{"class":681,"line":3169},[679,26697,889],{"emptyLinePlaceholder":797},[679,26699,26700,26702,26704,26706,26708,26710],{"class":681,"line":3185},[679,26701,6089],{"class":685},[679,26703,6095],{"class":685},[679,26705,11335],{"class":880},[679,26707,11338],{"class":693},[679,26709,11341],{"class":2099},[679,26711,4390],{"class":693},[679,26713,26714,26716,26718,26720],{"class":681,"line":3194},[679,26715,7862],{"class":931},[679,26717,11350],{"class":693},[679,26719,686],{"class":685},[679,26721,11318],{"class":693},[679,26723,26724],{"class":681,"line":3199},[679,26725,985],{"class":693},[679,26727,26728],{"class":681,"line":3212},[679,26729,889],{"emptyLinePlaceholder":797},[679,26731,26732,26734,26736,26738],{"class":681,"line":3217},[679,26733,6089],{"class":685},[679,26735,9289],{"class":693},[679,26737,13774],{"class":880},[679,26739,2667],{"class":693},[679,26741,26742,26744],{"class":681,"line":3222},[679,26743,9444],{"class":685},[679,26745,13783],{"class":693},[679,26747,26748],{"class":681,"line":3227},[679,26749,985],{"class":693},[679,26751,26752],{"class":681,"line":3232},[679,26753,889],{"emptyLinePlaceholder":797},[679,26755,26756,26758,26760,26762,26764,26766],{"class":681,"line":3499},[679,26757,6089],{"class":685},[679,26759,6095],{"class":685},[679,26761,13800],{"class":880},[679,26763,11400],{"class":693},[679,26765,13805],{"class":2099},[679,26767,4390],{"class":693},[679,26769,26770,26772,26774,26776],{"class":681,"line":3509},[679,26771,7862],{"class":931},[679,26773,13814],{"class":693},[679,26775,686],{"class":685},[679,26777,13783],{"class":693},[679,26779,26780],{"class":681,"line":3516},[679,26781,985],{"class":693},[679,26783,26784],{"class":681,"line":3531},[679,26785,889],{"emptyLinePlaceholder":797},[679,26787,26788,26790,26792,26794],{"class":681,"line":3536},[679,26789,6089],{"class":685},[679,26791,9289],{"class":693},[679,26793,13896],{"class":880},[679,26795,2667],{"class":693},[679,26797,26798,26800],{"class":681,"line":3541},[679,26799,9444],{"class":685},[679,26801,13905],{"class":693},[679,26803,26804],{"class":681,"line":3546},[679,26805,985],{"class":693},[679,26807,26808],{"class":681,"line":3551},[679,26809,889],{"emptyLinePlaceholder":797},[679,26811,26812,26814,26816,26818,26820,26822],{"class":681,"line":3557},[679,26813,6089],{"class":685},[679,26815,6095],{"class":685},[679,26817,13922],{"class":880},[679,26819,11400],{"class":693},[679,26821,13927],{"class":2099},[679,26823,4390],{"class":693},[679,26825,26826,26828,26830,26832],{"class":681,"line":3567},[679,26827,7862],{"class":931},[679,26829,13936],{"class":693},[679,26831,686],{"class":685},[679,26833,13905],{"class":693},[679,26835,26836],{"class":681,"line":3574},[679,26837,985],{"class":693},[679,26839,26840],{"class":681,"line":3589},[679,26841,889],{"emptyLinePlaceholder":797},[679,26843,26844,26846,26848,26850],{"class":681,"line":3594},[679,26845,6089],{"class":685},[679,26847,9289],{"class":693},[679,26849,13957],{"class":880},[679,26851,2667],{"class":693},[679,26853,26854,26856],{"class":681,"line":3602},[679,26855,9444],{"class":685},[679,26857,13966],{"class":693},[679,26859,26860],{"class":681,"line":3608},[679,26861,985],{"class":693},[679,26863,26864],{"class":681,"line":3619},[679,26865,889],{"emptyLinePlaceholder":797},[679,26867,26868,26870,26872,26874,26876,26878],{"class":681,"line":3624},[679,26869,6089],{"class":685},[679,26871,6095],{"class":685},[679,26873,13983],{"class":880},[679,26875,11400],{"class":693},[679,26877,13988],{"class":2099},[679,26879,4390],{"class":693},[679,26881,26882,26884,26886,26888],{"class":681,"line":3629},[679,26883,7862],{"class":931},[679,26885,13997],{"class":693},[679,26887,686],{"class":685},[679,26889,13966],{"class":693},[679,26891,26892],{"class":681,"line":3639},[679,26893,985],{"class":693},[679,26895,26896],{"class":681,"line":3644},[679,26897,889],{"emptyLinePlaceholder":797},[679,26899,26900,26902],{"class":681,"line":3649},[679,26901,6872],{"class":693},[679,26903,10723],{"class":685},[679,26905,26906,26908,26910,26912],{"class":681,"line":3659},[679,26907,6089],{"class":685},[679,26909,9289],{"class":693},[679,26911,14391],{"class":880},[679,26913,2667],{"class":693},[679,26915,26916,26918,26921],{"class":681,"line":3664},[679,26917,9444],{"class":685},[679,26919,26920],{"class":689}," \"Subscriber{\"",[679,26922,14403],{"class":685},[679,26924,26925,26927,26929,26931,26933,26935,26937],{"class":681,"line":3669},[679,26926,14408],{"class":689},[679,26928,3059],{"class":685},[679,26930,14413],{"class":693},[679,26932,3065],{"class":685},[679,26934,14418],{"class":689},[679,26936,4412],{"class":931},[679,26938,26939],{"class":689},"'' +\n",[679,26941,26942,26945,26947],{"class":681,"line":3679},[679,26943,26944],{"class":689}," \", last='\" + last + '",[679,26946,4412],{"class":931},[679,26948,26939],{"class":689},[679,26950,26951],{"class":681,"line":3684},[679,26952,26953],{"class":689}," '}';\n",[679,26955,26956],{"class":681,"line":3689},[679,26957,985],{"class":689},[679,26959,26960],{"class":681,"line":3699},[679,26961,996],{"class":689},[651,26963,26964],{},"Next, I will create a simple repository so that I can create some dummy data on startup. ",[669,26966,26968],{"className":4107,"code":26967,"language":4109,"meta":674,"style":674},"package com.therealdanvega.domain;\n\nimport org.springframework.data.repository.CrudRepository;\n\npublic interface SubscriberRepository extends CrudRepository\u003CSubscriber,Long> {\n\n}\n",[676,26969,26970,26976,26980,26987,26991,27015,27019],{"__ignoreMap":674},[679,26971,26972,26974],{"class":681,"line":682},[679,26973,2543],{"class":685},[679,26975,26530],{"class":693},[679,26977,26978],{"class":681,"line":790},[679,26979,889],{"emptyLinePlaceholder":797},[679,26981,26982,26984],{"class":681,"line":892},[679,26983,1999],{"class":685},[679,26985,26986],{"class":693}," org.springframework.data.repository.CrudRepository;\n",[679,26988,26989],{"class":681,"line":901},[679,26990,889],{"emptyLinePlaceholder":797},[679,26992,26993,26995,26997,27000,27002,27004,27006,27009,27011,27013],{"class":681,"line":909},[679,26994,6073],{"class":685},[679,26996,6994],{"class":685},[679,26998,26999],{"class":880}," SubscriberRepository",[679,27001,2767],{"class":685},[679,27003,16385],{"class":880},[679,27005,4505],{"class":693},[679,27007,27008],{"class":685},"Subscriber",[679,27010,1202],{"class":693},[679,27012,1094],{"class":685},[679,27014,16397],{"class":693},[679,27016,27017],{"class":681,"line":918},[679,27018,889],{"emptyLinePlaceholder":797},[679,27020,27021],{"class":681,"line":935},[679,27022,996],{"class":693},[651,27024,27025],{},"Finally, I will load some data using the Command Line Runner. ",[669,27027,27029],{"className":4107,"code":27028,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport com.therealdanvega.domain.Subscriber;\nimport com.therealdanvega.domain.SubscriberRepository;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\n\n@SpringBootApplication\npublic class H2demoApplication {\n\n public static void main(String\\[\\] args) {\n SpringApplication.run(H2demoApplication.class, args);\n }\n\n @Bean\n CommandLineRunner runner(SubscriberRepository repository){\n return args -> {\n repository.save( new Subscriber(\"Dan\", \"Vega\", \"danvega@gmail.com\"));\n };\n }\n}\n\n",[676,27030,27031,27037,27041,27048,27055,27061,27067,27073,27079,27083,27089,27100,27104,27124,27133,27137,27141,27147,27160,27170,27196,27200,27204],{"__ignoreMap":674},[679,27032,27033,27035],{"class":681,"line":682},[679,27034,2543],{"class":685},[679,27036,6039],{"class":693},[679,27038,27039],{"class":681,"line":790},[679,27040,889],{"emptyLinePlaceholder":797},[679,27042,27043,27045],{"class":681,"line":892},[679,27044,1999],{"class":685},[679,27046,27047],{"class":693}," com.therealdanvega.domain.Subscriber;\n",[679,27049,27050,27052],{"class":681,"line":901},[679,27051,1999],{"class":685},[679,27053,27054],{"class":693}," com.therealdanvega.domain.SubscriberRepository;\n",[679,27056,27057,27059],{"class":681,"line":909},[679,27058,1999],{"class":685},[679,27060,19558],{"class":693},[679,27062,27063,27065],{"class":681,"line":918},[679,27064,1999],{"class":685},[679,27066,6050],{"class":693},[679,27068,27069,27071],{"class":681,"line":935},[679,27070,1999],{"class":685},[679,27072,6057],{"class":693},[679,27074,27075,27077],{"class":681,"line":944},[679,27076,1999],{"class":685},[679,27078,6362],{"class":693},[679,27080,27081],{"class":681,"line":959},[679,27082,889],{"emptyLinePlaceholder":797},[679,27084,27085,27087],{"class":681,"line":964},[679,27086,4116],{"class":693},[679,27088,6068],{"class":685},[679,27090,27091,27093,27095,27098],{"class":681,"line":977},[679,27092,6073],{"class":685},[679,27094,4512],{"class":685},[679,27096,27097],{"class":880}," H2demoApplication",[679,27099,884],{"class":693},[679,27101,27102],{"class":681,"line":982},[679,27103,889],{"emptyLinePlaceholder":797},[679,27105,27106,27108,27110,27112,27114,27116,27118,27120,27122],{"class":681,"line":988},[679,27107,6089],{"class":685},[679,27109,6092],{"class":685},[679,27111,6095],{"class":685},[679,27113,6098],{"class":880},[679,27115,745],{"class":693},[679,27117,4758],{"class":2099},[679,27119,6105],{"class":693},[679,27121,6108],{"class":2099},[679,27123,4390],{"class":693},[679,27125,27126,27128,27130],{"class":681,"line":993},[679,27127,6115],{"class":693},[679,27129,6118],{"class":880},[679,27131,27132],{"class":693},"(H2demoApplication.class, args);\n",[679,27134,27135],{"class":681,"line":2129},[679,27136,985],{"class":693},[679,27138,27139],{"class":681,"line":2140},[679,27140,889],{"emptyLinePlaceholder":797},[679,27142,27143,27145],{"class":681,"line":2145},[679,27144,6872],{"class":693},[679,27146,16929],{"class":685},[679,27148,27149,27151,27153,27156,27158],{"class":681,"line":2154},[679,27150,20982],{"class":693},[679,27152,16939],{"class":880},[679,27154,27155],{"class":693},"(SubscriberRepository ",[679,27157,16596],{"class":2099},[679,27159,9533],{"class":693},[679,27161,27162,27164,27166,27168],{"class":681,"line":2159},[679,27163,9444],{"class":685},[679,27165,16952],{"class":693},[679,27167,16955],{"class":685},[679,27169,884],{"class":693},[679,27171,27172,27174,27176,27178,27180,27182,27184,27186,27188,27190,27192,27194],{"class":681,"line":2164},[679,27173,17009],{"class":693},[679,27175,7629],{"class":880},[679,27177,1234],{"class":693},[679,27179,8930],{"class":685},[679,27181,26571],{"class":880},[679,27183,745],{"class":693},[679,27185,1414],{"class":689},[679,27187,2797],{"class":693},[679,27189,9343],{"class":689},[679,27191,2797],{"class":693},[679,27193,9679],{"class":689},[679,27195,1669],{"class":693},[679,27197,27198],{"class":681,"line":3134},[679,27199,17018],{"class":693},[679,27201,27202],{"class":681,"line":3139},[679,27203,985],{"class":693},[679,27205,27206],{"class":681,"line":3144},[679,27207,996],{"class":693},[4542,27209,27211],{"id":27210},"running-your-spring-boot-application","Running your Spring Boot Application",[651,27213,27214,27215,27218],{},"With all of that in place, this is where people usually run into this issue. If you were to start your application up and visit the H2 console using ",[812,27216,16733],{"href":16733,"rel":27217},[816]," you would see the following screen. What I would expect is when I login to the console to see my Subscriber table with a new row inserted. ",[651,27220,27221],{},[660,27222],{"alt":27223,"src":27224},"H2 Database Console","./2017-05-31_08-29-29.png",[651,27226,27227,27228,27232,27234],{},"The issue here is usually with the JDBC URL that students use to connect to their database. If you put anything in the place of subscribers it would let you connect, but you wouldn't see anything. The URL is usually the last visited URL on your machine but I am not sure what comes up by default. The problem is not understanding what the default datasource name is. In a Spring Boot application, the default datasource is named ",[2939,27229,27230],{},[7300,27231,11149],{},[7300,27233,664],{}," If you were to use the URL \"jdbc:h2:mem:testdb\" and connect to the database you would see your table created. ",[651,27236,27237],{},[660,27238],{"alt":27223,"src":27239},"./2017-05-31_08-46-40.png",[651,27241,27242],{},"If you don't want to use testdb as the datasource name for every application you build (and you shouldn't) you can change it using the following property. ",[669,27244,27246],{"className":5851,"code":27245,"language":5853,"meta":674,"style":674},"spring.datasource.name=subscribers\n",[676,27247,27248],{"__ignoreMap":674},[679,27249,27250,27253],{"class":681,"line":682},[679,27251,27252],{"class":880},"spring.datasource.name",[679,27254,27255],{"class":689},"=subscribers\n",[4542,27257,9042],{"id":9041},[651,27259,27260],{},"As we saw in this demo it usually isn't the Spring Boot application not working correctly but more often than not my students were connecting to the wrong database. I hope this helped clear up this issue for someone out there and I would like to leave you with a question.",[651,27262,27263],{},[7300,27264,27265,27267],{},[2939,27266,11650],{}," What are some common mistakes you run into in your Spring Boot Applications?",[786,27269,27270],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":27272},[27273,27277,27278],{"id":26471,"depth":790,"text":18978,"children":27274},[27275,27276],{"id":26483,"depth":892,"text":26484},{"id":11182,"depth":892,"text":11183},{"id":27210,"depth":790,"text":27211},{"id":9041,"depth":790,"text":9042},{"slug":27280,"published":797,"date":27281,"tags":27282,"cover":27283},"spring-boot-1-question-students-asking-right-now","2017-05-31T08:48:35-04:00",[673],"./pexels-photo-92028-760x599.jpeg",{"title":153,"description":153},"blog/2017/05/31/spring-boot-1-question-students-asking-right-now","SAJDugCEvDDGdvcd-vjZky5lT4LnYjgAuk9fpkbUYD0",{"id":27288,"title":150,"body":27289,"description":150,"extension":793,"meta":27624,"navigation":797,"path":151,"seo":27629,"stem":27630,"__hash__":27631},"content/blog/2017/06/05/getting-started-angular-cli.md",{"type":648,"value":27290,"toc":27615},[27291,27297,27301,27308,27311,27325,27343,27349,27352,27368,27371,27383,27389,27393,27396,27410,27413,27419,27422,27434,27442,27446,27449,27464,27467,27530,27533,27550,27556,27565,27571,27575,27596,27602,27604,27607,27612],[651,27292,27293,27294,664],{},"I have worked on a variety of Angular applications over the last few months and it truly has been a blast. I know a lot of my readers out there are Java + Spring developers and I want to make sure that I make this very clear in this post and the ones that follow. If you played around with Angular 1.xx and didn't really enjoy writing those applications you need to come back for another look. Writing angular 2/4 applications with Typescript is a familiar setting for Java Developers. I really do love working with the Angular framework and in today's article, I am going to help you get started quickly by using the ",[812,27295,21522],{"href":21520,"rel":27296},[816],[4542,27298,27300],{"id":27299},"what-is-the-angular-cli","What is the Angular CLI",[651,27302,27303,27304,27307],{},"The Angular CLI is a tool to initialize, develop, scaffold and maintain ",[812,27305,17866],{"href":17864,"rel":27306},[816]," applications. The CLI in Angular CLI stands for Command Line Interface and you have probably come across similar tools before. If you're using Spring Boot we use the Spring Initializr to initialize our applications and there is a Spring CLI to accomplish this from the command line. ",[4542,27309,150],{"id":27310},"getting-started-with-the-angular-cli",[651,27312,27313,27314,27319,27320,27324],{},"The first thing you need to do is to make sure you have ",[812,27315,27318],{"href":27316,"rel":27317},"https://nodejs.org/en/",[816],"node"," & ",[812,27321,24568],{"href":27322,"rel":27323},"https://www.npmjs.com/",[816]," installed. You can check the versions for each by opening a command line and running the following commands. ",[669,27326,27328],{"className":5851,"code":27327,"language":5853,"meta":674,"style":674},"node -v\nnpm -v\n",[676,27329,27330,27337],{"__ignoreMap":674},[679,27331,27332,27334],{"class":681,"line":682},[679,27333,27318],{"class":880},[679,27335,27336],{"class":931}," -v\n",[679,27338,27339,27341],{"class":681,"line":790},[679,27340,24568],{"class":880},[679,27342,27336],{"class":931},[651,27344,27345],{},[660,27346],{"alt":27347,"src":27348},"Angular CLI Check - Node & NPM Versions","./2017-06-05_10-02-47.png",[651,27350,27351],{},"With those in place, you can install the Angular CLI by running the following command. ",[669,27353,27355],{"className":5851,"code":27354,"language":5853,"meta":674,"style":674},"npm install -g @angular/cli\n",[676,27356,27357],{"__ignoreMap":674},[679,27358,27359,27361,27363,27365],{"class":681,"line":682},[679,27360,24568],{"class":880},[679,27362,24571],{"class":689},[679,27364,24574],{"class":931},[679,27366,27367],{"class":689}," @angular/cli\n",[651,27369,27370],{},"Once installed you can always run the following command to see what version of the Angular CLI you're running. ",[669,27372,27374],{"className":5851,"code":27373,"language":5853,"meta":674,"style":674},"ng -v\n",[676,27375,27376],{"__ignoreMap":674},[679,27377,27378,27381],{"class":681,"line":682},[679,27379,27380],{"class":880},"ng",[679,27382,27336],{"class":931},[651,27384,27385],{},[660,27386],{"alt":27387,"src":27388},"Angular CLI Checking the version","./2017-06-05_10-05-08.png",[5909,27390,27392],{"id":27391},"creating-and-running-your-new-project","Creating and Running your new Project",[651,27394,27395],{},"The first thing you're going to want to do with your shiny new toy is to create a new project. To do that we are simply going to run the following command. ",[669,27397,27399],{"className":5851,"code":27398,"language":5853,"meta":674,"style":674},"ng new your-project-name-here\n",[676,27400,27401],{"__ignoreMap":674},[679,27402,27403,27405,27407],{"class":681,"line":682},[679,27404,27380],{"class":880},[679,27406,2054],{"class":689},[679,27408,27409],{"class":689}," your-project-name-here\n",[651,27411,27412],{},"You will want to run this from the directory where you want your new project to be created. The Angular CLI will create the folder and all the files you need to get started. ",[651,27414,27415],{},[660,27416],{"alt":27417,"src":27418},"Angular CLI Generating a new project","./2017-06-05_10-10-56.png",[651,27420,27421],{},"When that is finished you are ready to go. Navigate to the project folder (cd hello-angular) and run the following command. ",[669,27423,27425],{"className":5851,"code":27424,"language":5853,"meta":674,"style":674},"ng serve\n",[676,27426,27427],{"__ignoreMap":674},[679,27428,27429,27431],{"class":681,"line":682},[679,27430,27380],{"class":880},[679,27432,27433],{"class":689}," serve\n",[651,27435,27436,27437,27441],{},"Navigate to ",[812,27438,27439],{"href":27439,"rel":27440},"http://localhost:4200/",[816],". The app will automatically reload if you change any of the source files.",[5909,27443,27445],{"id":27444},"generatingblueprints","Generating Blueprints",[651,27447,27448],{},"Another very useful command is the generate command. ",[669,27450,27452],{"className":5851,"code":27451,"language":5853,"meta":674,"style":674},"ng generate [name]\n",[676,27453,27454],{"__ignoreMap":674},[679,27455,27456,27458,27461],{"class":681,"line":682},[679,27457,27380],{"class":880},[679,27459,27460],{"class":689}," generate",[679,27462,27463],{"class":693}," [name]\n",[651,27465,27466],{},"This will allow you to generate the following blueprints. ",[5316,27468,27469,27475,27482,27489,27496,27503,27509,27516,27523],{},[5332,27470,27471],{},[812,27472,877],{"href":27473,"rel":27474},"https://github.com/angular/angular-cli/wiki/generate-class",[816],[5332,27476,27477],{},[812,27478,27481],{"href":27479,"rel":27480},"https://github.com/angular/angular-cli/wiki/generate-component",[816],"component",[5332,27483,27484],{},[812,27485,27488],{"href":27486,"rel":27487},"https://github.com/angular/angular-cli/wiki/generate-directive",[816],"directive",[5332,27490,27491],{},[812,27492,27495],{"href":27493,"rel":27494},"https://github.com/angular/angular-cli/wiki/generate-enum",[816],"enum",[5332,27497,27498],{},[812,27499,27502],{"href":27500,"rel":27501},"https://github.com/angular/angular-cli/wiki/generate-guard",[816],"guard",[5332,27504,27505],{},[812,27506,6630],{"href":27507,"rel":27508},"https://github.com/angular/angular-cli/wiki/generate-interface",[816],[5332,27510,27511],{},[812,27512,27515],{"href":27513,"rel":27514},"https://github.com/angular/angular-cli/wiki/generate-module",[816],"module",[5332,27517,27518],{},[812,27519,27522],{"href":27520,"rel":27521},"https://github.com/angular/angular-cli/wiki/generate-pipe",[816],"pipe",[5332,27524,27525],{},[812,27526,27529],{"href":27527,"rel":27528},"https://github.com/angular/angular-cli/wiki/generate-service",[816],"service",[651,27531,27532],{},"We won't go through them all but I will walk through one example here. When you get into Angular development you will quickly realize that everything is component based. When we want to create a new component like a users component to manage the users we can run the following command. ",[669,27534,27536],{"className":5851,"code":27535,"language":5853,"meta":674,"style":674},"ng generate component users\n",[676,27537,27538],{"__ignoreMap":674},[679,27539,27540,27542,27544,27547],{"class":681,"line":682},[679,27541,27380],{"class":880},[679,27543,27460],{"class":689},[679,27545,27546],{"class":689}," component",[679,27548,27549],{"class":689}," users\n",[651,27551,27552],{},[660,27553],{"alt":27554,"src":27555},"Angular CLI Generating Components","./2017-06-05_10-28-11.png",[651,27557,27558,27564],{},[7300,27559,27560,27561],{},"* We can also use the shortcut ",[2939,27562,27563],{},"ng g c users"," When you run this command you can see that it will generate CSS & HTML along with the component and test typescript files. If you want to generate a child component just give the full path for your component. ",[651,27566,27567],{},[660,27568],{"alt":27569,"src":27570},"Angular CLI Generate Components","./2017-06-05_10-31-30.png",[5909,27572,27574],{"id":27573},"testing","Testing",[651,27576,27577,27578,27583,27584,27589,27590,22947,27593,22733],{},"Angular uses the ",[812,27579,27582],{"href":27580,"rel":27581},"https://karma-runner.github.io/1.0/index.html",[816],"Karma Test Runner"," for all of its testing needs. If you run the following command the Angular CLI will spin up a test runner for you and run all of your tests. Tests will execute after a build is executed via ",[812,27585,27588],{"href":27586,"rel":27587},"http://karma-runner.github.io/0.13/index.html",[816],"Karma",", and it will automatically watch your files for changes. You can run tests a single time via ",[676,27591,27592],{},"--watch=false",[676,27594,27595],{},"--single-run",[651,27597,27598],{},[660,27599],{"alt":27600,"src":27601},"Angular CLI Tests","./2017-06-05_10-20-41.png",[4542,27603,9042],{"id":9041},[651,27605,27606],{},"The Angular CLI is the best way to get started with Angular development. I hope anyone who looked at Angular in the past and decided it wasn't for them gives it another shot. It is a really great framework with a set of tools that makes writing Angular applications a breeze. ",[651,27608,13453,27609,27611],{},[2939,27610,11650],{}," Have you looked at the latest version of Angular and if so what are your thoughts? _",[786,27613,27614],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}",{"title":674,"searchDepth":790,"depth":790,"links":27616},[27617,27618,27623],{"id":27299,"depth":790,"text":27300},{"id":27310,"depth":790,"text":150,"children":27619},[27620,27621,27622],{"id":27391,"depth":892,"text":27392},{"id":27444,"depth":892,"text":27445},{"id":27573,"depth":892,"text":27574},{"id":9041,"depth":790,"text":9042},{"slug":27625,"published":797,"date":27626,"tags":27627,"cover":27628},"getting-started-angular-cli","2017-06-05T10:34:50-04:00",[17866],"./2017-06-05_09-34-25-760x226.png",{"title":150,"description":150},"blog/2017/06/05/getting-started-angular-cli","4tL39UkDDAMmDffylRUBPqzAmZhnX9kqHN4pJ4EsR6c",{"id":27633,"title":147,"body":27634,"description":147,"extension":793,"meta":28381,"navigation":797,"path":148,"seo":28386,"stem":28387,"__hash__":28388},"content/blog/2017/06/07/angular-forms-clear-input-field.md",{"type":648,"value":27635,"toc":28370},[27636,27643,27647,27650,27654,27657,27661,27664,27676,27682,27689,27693,27696,27771,27774,27890,27893,27912,27915,27991,27994,28104,28107,28111,28114,28144,28147,28151,28160,28165,28172,28246,28249,28357,28359,28362,28367],[651,27637,27638,27639,27642],{},"Recently I was working on an ",[812,27640,17866],{"href":17864,"rel":27641},[816]," Forms application and I needed the ability to clear an input field.",[4542,27644,27646],{"id":27645},"how-to-clear-an-input-field-in-angular-forms","How to clear an input field in Angular Forms.",[651,27648,27649],{},"In a normal HTML form you would use find the input on the page and just clear it's text value. In Angular you want to bind the value of a property to the input box and reset the properties value.",[4542,27651,27653],{"id":27652},"angular-forms-project","Angular Forms Project",[651,27655,27656],{},"I have been learning a lot lately when it comes to building Angular applications. In this post we are going to talk about a tasks application that I was working on for my latest course. In the tasks application you can list out all of your tasks but I also allow you to add new ones. When a new task was added I wanted to clear the form out so you could quickly add another one. I ran into a problem doing so and I wanted to share that problem and my solution with you today.",[5909,27658,27660],{"id":27659},"my-tasks-project","My Tasks Project",[651,27662,27663],{},"The application I am working on is a simple tasks application. It is broken down into 3 components",[27665,27666,27667,27670,27673],"ol",{},[5332,27668,27669],{},"Tasks - Main tasks component that has 2 sub components.",[5332,27671,27672],{},"Add Task - A way for you to add a new task",[5332,27674,27675],{},"Task List - A way to display all of the components. ",[651,27677,27678],{},[660,27679],{"alt":27680,"src":27681},"Tasks Project","./2017-06-07_08-39-40.png",[651,27683,27684,27685,664],{},"If you want to check out the project you can grab it on ",[812,27686,17458],{"href":27687,"rel":27688},"https://github.com/danvega/spring-angular2-tasks",[816],[5909,27690,27692],{"id":27691},"angular-forms","Angular Forms",[651,27694,27695],{},"The first thing we have is the template for the add task form and it looks something like this. You will notice that we have an event handler for when the user hits the enter key. When that happens we will call a method onTaskAdd and pass in the event. ",[669,27697,27699],{"className":4496,"code":27698,"language":4498,"meta":674,"style":674},"\u003Cdiv class=\"form-group\">\n \u003Cinput type=\"text\"\n class=\"form-control\"\n placeholder=\"Add New Task\"\n (keyup.enter)=\"onTaskAdd($event)\">\n\u003C/div>\n",[676,27700,27701,27716,27731,27741,27751,27763],{"__ignoreMap":674},[679,27702,27703,27705,27707,27709,27711,27714],{"class":681,"line":682},[679,27704,4505],{"class":693},[679,27706,4509],{"class":4508},[679,27708,4512],{"class":880},[679,27710,686],{"class":693},[679,27712,27713],{"class":689},"\"form-group\"",[679,27715,4519],{"class":693},[679,27717,27718,27720,27723,27726,27728],{"class":681,"line":790},[679,27719,4524],{"class":693},[679,27721,27722],{"class":4508},"input",[679,27724,27725],{"class":880}," type",[679,27727,686],{"class":693},[679,27729,27730],{"class":689},"\"text\"\n",[679,27732,27733,27736,27738],{"class":681,"line":892},[679,27734,27735],{"class":880}," class",[679,27737,686],{"class":693},[679,27739,27740],{"class":689},"\"form-control\"\n",[679,27742,27743,27746,27748],{"class":681,"line":901},[679,27744,27745],{"class":880}," placeholder",[679,27747,686],{"class":693},[679,27749,27750],{"class":689},"\"Add New Task\"\n",[679,27752,27753,27756,27758,27761],{"class":681,"line":909},[679,27754,27755],{"class":880}," (keyup.enter)",[679,27757,686],{"class":693},[679,27759,27760],{"class":689},"\"onTaskAdd($event)\"",[679,27762,4519],{"class":693},[679,27764,27765,27767,27769],{"class":681,"line":918},[679,27766,4586],{"class":693},[679,27768,4509],{"class":4508},[679,27770,4519],{"class":693},[651,27772,27773],{},"In this simple demo, we are creating a new task with the value from the input and calling a service to save the task. ",[669,27775,27777],{"className":25132,"code":27776,"language":25134,"meta":674,"style":674},"onTaskAdd(event) {\n let task: Task = new Task(event.target.value,false, this.getTodayAsString());\n this.taskService.addTask(task)\n .subscribe(\n (newTask: Task) => {\n this.taskService.onTaskAdded.emit(newTask);\n }\n );\n}\n",[676,27778,27779,27787,27821,27835,27844,27862,27876,27881,27886],{"__ignoreMap":674},[679,27780,27781,27784],{"class":681,"line":682},[679,27782,27783],{"class":880},"onTaskAdd",[679,27785,27786],{"class":693},"(event) {\n",[679,27788,27789,27792,27795,27797,27799,27801,27803,27805,27808,27810,27812,27814,27816,27819],{"class":681,"line":790},[679,27790,27791],{"class":685}," let",[679,27793,27794],{"class":693}," task",[679,27796,2391],{"class":685},[679,27798,21014],{"class":880},[679,27800,6883],{"class":685},[679,27802,2054],{"class":685},[679,27804,21014],{"class":880},[679,27806,27807],{"class":693},"(event.target.value,",[679,27809,1135],{"class":931},[679,27811,2797],{"class":693},[679,27813,4732],{"class":931},[679,27815,664],{"class":693},[679,27817,27818],{"class":880},"getTodayAsString",[679,27820,9431],{"class":693},[679,27822,27823,27826,27829,27832],{"class":681,"line":892},[679,27824,27825],{"class":931}," this",[679,27827,27828],{"class":693},".taskService.",[679,27830,27831],{"class":880},"addTask",[679,27833,27834],{"class":693},"(task)\n",[679,27836,27837,27840,27842],{"class":681,"line":901},[679,27838,27839],{"class":693}," .",[679,27841,21334],{"class":880},[679,27843,21337],{"class":693},[679,27845,27846,27849,27852,27854,27856,27858,27860],{"class":681,"line":909},[679,27847,27848],{"class":693}," (",[679,27850,27851],{"class":2099},"newTask",[679,27853,2391],{"class":685},[679,27855,21014],{"class":880},[679,27857,2378],{"class":693},[679,27859,21350],{"class":685},[679,27861,884],{"class":693},[679,27863,27864,27867,27870,27873],{"class":681,"line":918},[679,27865,27866],{"class":931}," this",[679,27868,27869],{"class":693},".taskService.onTaskAdded.",[679,27871,27872],{"class":880},"emit",[679,27874,27875],{"class":693},"(newTask);\n",[679,27877,27878],{"class":681,"line":935},[679,27879,27880],{"class":693}," }\n",[679,27882,27883],{"class":681,"line":944},[679,27884,27885],{"class":693}," );\n",[679,27887,27888],{"class":681,"line":959},[679,27889,996],{"class":693},[651,27891,27892],{},"This was working great but after the task was saved I need a way to clear the input so the user could type another task and save it. This seemed like a great opportunity for property binding to work its magic. In the add task component, I would set up a value for the input field and initialize it to an empty string. ",[669,27894,27896],{"className":25132,"code":27895,"language":25134,"meta":674,"style":674},"addTaskValue: string = \"\";\n",[676,27897,27898],{"__ignoreMap":674},[679,27899,27900,27903,27906,27908,27910],{"class":681,"line":682},[679,27901,27902],{"class":880},"addTaskValue",[679,27904,27905],{"class":693},": string ",[679,27907,686],{"class":685},[679,27909,1183],{"class":689},[679,27911,1186],{"class":693},[651,27913,27914],{},"Then in the template, I would set the value to that property. ",[669,27916,27918],{"className":4496,"code":27917,"language":4498,"meta":674,"style":674},"\u003Cdiv class=\"form-group\">\n \u003Cinput type=\"text\"\n class=\"form-control\"\n placeholder=\"Add New Task\"\n (keyup.enter)=\"onTaskAdd($event)\"\n \\[value\\]=\"addTaskValue\">\n\u003C/div>\n",[676,27919,27920,27934,27946,27954,27962,27971,27983],{"__ignoreMap":674},[679,27921,27922,27924,27926,27928,27930,27932],{"class":681,"line":682},[679,27923,4505],{"class":693},[679,27925,4509],{"class":4508},[679,27927,4512],{"class":880},[679,27929,686],{"class":693},[679,27931,27713],{"class":689},[679,27933,4519],{"class":693},[679,27935,27936,27938,27940,27942,27944],{"class":681,"line":790},[679,27937,4524],{"class":693},[679,27939,27722],{"class":4508},[679,27941,27725],{"class":880},[679,27943,686],{"class":693},[679,27945,27730],{"class":689},[679,27947,27948,27950,27952],{"class":681,"line":892},[679,27949,27735],{"class":880},[679,27951,686],{"class":693},[679,27953,27740],{"class":689},[679,27955,27956,27958,27960],{"class":681,"line":901},[679,27957,27745],{"class":880},[679,27959,686],{"class":693},[679,27961,27750],{"class":689},[679,27963,27964,27966,27968],{"class":681,"line":909},[679,27965,27755],{"class":880},[679,27967,686],{"class":693},[679,27969,27970],{"class":689},"\"onTaskAdd($event)\"\n",[679,27972,27973,27976,27978,27981],{"class":681,"line":918},[679,27974,27975],{"class":880}," \\[value\\]",[679,27977,686],{"class":693},[679,27979,27980],{"class":689},"\"addTaskValue\"",[679,27982,4519],{"class":693},[679,27984,27985,27987,27989],{"class":681,"line":935},[679,27986,4586],{"class":693},[679,27988,4509],{"class":4508},[679,27990,4519],{"class":693},[651,27992,27993],{},"Finally, when we add a new task I thought I could simply set that value back to an empty string. ",[669,27995,27997],{"className":25132,"code":27996,"language":25134,"meta":674,"style":674},"onTaskAdd(event) {\n let task: Task = new Task(event.target.value,false, this.getTodayAsString());\n this.taskService.addTask(task)\n .subscribe(\n (newTask: Task) => {\n this.addTaskValue = '';\n this.taskService.onTaskAdded.emit(newTask);\n }\n );\n}\n",[676,27998,27999,28005,28035,28045,28053,28069,28082,28092,28096,28100],{"__ignoreMap":674},[679,28000,28001,28003],{"class":681,"line":682},[679,28002,27783],{"class":880},[679,28004,27786],{"class":693},[679,28006,28007,28009,28011,28013,28015,28017,28019,28021,28023,28025,28027,28029,28031,28033],{"class":681,"line":790},[679,28008,27791],{"class":685},[679,28010,27794],{"class":693},[679,28012,2391],{"class":685},[679,28014,21014],{"class":880},[679,28016,6883],{"class":685},[679,28018,2054],{"class":685},[679,28020,21014],{"class":880},[679,28022,27807],{"class":693},[679,28024,1135],{"class":931},[679,28026,2797],{"class":693},[679,28028,4732],{"class":931},[679,28030,664],{"class":693},[679,28032,27818],{"class":880},[679,28034,9431],{"class":693},[679,28036,28037,28039,28041,28043],{"class":681,"line":892},[679,28038,27825],{"class":931},[679,28040,27828],{"class":693},[679,28042,27831],{"class":880},[679,28044,27834],{"class":693},[679,28046,28047,28049,28051],{"class":681,"line":901},[679,28048,27839],{"class":693},[679,28050,21334],{"class":880},[679,28052,21337],{"class":693},[679,28054,28055,28057,28059,28061,28063,28065,28067],{"class":681,"line":909},[679,28056,27848],{"class":693},[679,28058,27851],{"class":2099},[679,28060,2391],{"class":685},[679,28062,21014],{"class":880},[679,28064,2378],{"class":693},[679,28066,21350],{"class":685},[679,28068,884],{"class":693},[679,28070,28071,28073,28076,28078,28080],{"class":681,"line":918},[679,28072,27866],{"class":931},[679,28074,28075],{"class":693},".addTaskValue ",[679,28077,686],{"class":685},[679,28079,2216],{"class":689},[679,28081,1186],{"class":693},[679,28083,28084,28086,28088,28090],{"class":681,"line":935},[679,28085,27866],{"class":931},[679,28087,27869],{"class":693},[679,28089,27872],{"class":880},[679,28091,27875],{"class":693},[679,28093,28094],{"class":681,"line":944},[679,28095,27880],{"class":693},[679,28097,28098],{"class":681,"line":959},[679,28099,27885],{"class":693},[679,28101,28102],{"class":681,"line":964},[679,28103,996],{"class":693},[651,28105,28106],{},"This didn't work and it left me scratching my head. ",[4542,28108,28110],{"id":28109},"angular-forms-clearing-an-input-field","Angular Forms: Clearing an input field",[651,28112,28113],{},"While the solution above didn't work I knew I was headed in the right direction. The next thing I tried was to set the string to a null value or an empty string with a space in it. ",[669,28115,28117],{"className":25132,"code":28116,"language":25134,"meta":674,"style":674},"this.addTaskValue = ' ';\nthis.addTaskValue = null;\n",[676,28118,28119,28132],{"__ignoreMap":674},[679,28120,28121,28123,28125,28127,28130],{"class":681,"line":682},[679,28122,4732],{"class":931},[679,28124,28075],{"class":693},[679,28126,686],{"class":685},[679,28128,28129],{"class":689}," ' '",[679,28131,1186],{"class":693},[679,28133,28134,28136,28138,28140,28142],{"class":681,"line":790},[679,28135,4732],{"class":931},[679,28137,28075],{"class":693},[679,28139,686],{"class":685},[679,28141,2307],{"class":931},[679,28143,1186],{"class":693},[651,28145,28146],{},"This actually works the very first time that you try it but it will not work on subsequent tries. ",[5909,28148,28150],{"id":28149},"ngmodel","ngModel",[651,28152,28153,28154,28159],{},"If you have done any work with forms in Angular you have probably come across the ",[812,28155,28158],{"href":28156,"rel":28157},"https://angular.io/docs/ts/latest/api/forms/index/NgModel-directive.html",[816],"ngModel directive",". What does the ngModle directive do? ",[1004,28161,28162],{},[651,28163,28164],{},"Creates a FormControl instance from a domain model and binds it to a form control element. The FormControl instance will track the value, user interaction, and validation status of the control and keep the view synced with the model. If used within a parent form, the directive will also register itself with the form as a child control.",[651,28166,28167,28168,28171],{},"The problem with the approach we did above is that it is only property binding and not event binding. Angular doesn't run change detection because no event is fired. If you we use ngModel to bind our value to our form control we can get the answer we were looking for. Now we are using 2-way data binding with ",[676,28169,28170],{},"[()]"," syntax (also known as 'banana-box syntax'), the value in the UI will always be synced back to the domain model in your class as well.",[669,28173,28175],{"className":4496,"code":28174,"language":4498,"meta":674,"style":674},"\u003Cdiv class=\"form-group\">\n \u003Cinput type=\"text\"\n class=\"form-control\"\n placeholder=\"Add New Task\"\n (keyup.enter)=\"onTaskAdd($event)\"\n \\[(ngModel)\\]=\"addTaskValue\">\n\u003C/div>\n",[676,28176,28177,28191,28203,28211,28219,28227,28238],{"__ignoreMap":674},[679,28178,28179,28181,28183,28185,28187,28189],{"class":681,"line":682},[679,28180,4505],{"class":693},[679,28182,4509],{"class":4508},[679,28184,4512],{"class":880},[679,28186,686],{"class":693},[679,28188,27713],{"class":689},[679,28190,4519],{"class":693},[679,28192,28193,28195,28197,28199,28201],{"class":681,"line":790},[679,28194,4524],{"class":693},[679,28196,27722],{"class":4508},[679,28198,27725],{"class":880},[679,28200,686],{"class":693},[679,28202,27730],{"class":689},[679,28204,28205,28207,28209],{"class":681,"line":892},[679,28206,27735],{"class":880},[679,28208,686],{"class":693},[679,28210,27740],{"class":689},[679,28212,28213,28215,28217],{"class":681,"line":901},[679,28214,27745],{"class":880},[679,28216,686],{"class":693},[679,28218,27750],{"class":689},[679,28220,28221,28223,28225],{"class":681,"line":909},[679,28222,27755],{"class":880},[679,28224,686],{"class":693},[679,28226,27970],{"class":689},[679,28228,28229,28232,28234,28236],{"class":681,"line":918},[679,28230,28231],{"class":880}," \\[(ngModel)\\]",[679,28233,686],{"class":693},[679,28235,27980],{"class":689},[679,28237,4519],{"class":693},[679,28239,28240,28242,28244],{"class":681,"line":935},[679,28241,4586],{"class":693},[679,28243,4509],{"class":4508},[679,28245,4519],{"class":693},[651,28247,28248],{},"Now when we add a new task we can set our value to an empty string and Angular updates the input for us.",[669,28250,28251],{"className":25132,"code":27996,"language":25134,"meta":674,"style":674},[676,28252,28253,28259,28289,28299,28307,28323,28335,28345,28349,28353],{"__ignoreMap":674},[679,28254,28255,28257],{"class":681,"line":682},[679,28256,27783],{"class":880},[679,28258,27786],{"class":693},[679,28260,28261,28263,28265,28267,28269,28271,28273,28275,28277,28279,28281,28283,28285,28287],{"class":681,"line":790},[679,28262,27791],{"class":685},[679,28264,27794],{"class":693},[679,28266,2391],{"class":685},[679,28268,21014],{"class":880},[679,28270,6883],{"class":685},[679,28272,2054],{"class":685},[679,28274,21014],{"class":880},[679,28276,27807],{"class":693},[679,28278,1135],{"class":931},[679,28280,2797],{"class":693},[679,28282,4732],{"class":931},[679,28284,664],{"class":693},[679,28286,27818],{"class":880},[679,28288,9431],{"class":693},[679,28290,28291,28293,28295,28297],{"class":681,"line":892},[679,28292,27825],{"class":931},[679,28294,27828],{"class":693},[679,28296,27831],{"class":880},[679,28298,27834],{"class":693},[679,28300,28301,28303,28305],{"class":681,"line":901},[679,28302,27839],{"class":693},[679,28304,21334],{"class":880},[679,28306,21337],{"class":693},[679,28308,28309,28311,28313,28315,28317,28319,28321],{"class":681,"line":909},[679,28310,27848],{"class":693},[679,28312,27851],{"class":2099},[679,28314,2391],{"class":685},[679,28316,21014],{"class":880},[679,28318,2378],{"class":693},[679,28320,21350],{"class":685},[679,28322,884],{"class":693},[679,28324,28325,28327,28329,28331,28333],{"class":681,"line":918},[679,28326,27866],{"class":931},[679,28328,28075],{"class":693},[679,28330,686],{"class":685},[679,28332,2216],{"class":689},[679,28334,1186],{"class":693},[679,28336,28337,28339,28341,28343],{"class":681,"line":935},[679,28338,27866],{"class":931},[679,28340,27869],{"class":693},[679,28342,27872],{"class":880},[679,28344,27875],{"class":693},[679,28346,28347],{"class":681,"line":944},[679,28348,27880],{"class":693},[679,28350,28351],{"class":681,"line":959},[679,28352,27885],{"class":693},[679,28354,28355],{"class":681,"line":964},[679,28356,996],{"class":693},[4542,28358,9042],{"id":9041},[651,28360,28361],{},"Angular gives us some really awesome ways to handle forms but there are some gotchas when you're trying to move from a submitting to a server to a single page application approach. ",[651,28363,13453,28364,28366],{},[2939,28365,11650],{}," What do you like or don't like about working with forms in Angular? What are your toughest challenges? _",[786,28368,28369],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":28371},[28372,28373,28377,28380],{"id":27645,"depth":790,"text":27646},{"id":27652,"depth":790,"text":27653,"children":28374},[28375,28376],{"id":27659,"depth":892,"text":27660},{"id":27691,"depth":892,"text":27692},{"id":28109,"depth":790,"text":28110,"children":28378},[28379],{"id":28149,"depth":892,"text":28150},{"id":9041,"depth":790,"text":9042},{"slug":28382,"published":797,"date":28383,"tags":28384,"cover":28385},"angular-forms-clear-input-field","2017-06-07T09:50:47-04:00",[17866],"./angular-forms.png",{"title":147,"description":147},"blog/2017/06/07/angular-forms-clear-input-field","24qxooRnQOiaM3ga7Bd5HAlOL6SNgGAvRM7Kp-isHaU",{"id":28390,"title":144,"body":28391,"description":144,"extension":793,"meta":29397,"navigation":797,"path":145,"seo":29402,"stem":29403,"__hash__":29404},"content/blog/2017/06/12/bootstrapping-angular-application.md",{"type":648,"value":28392,"toc":29392},[28393,28396,28400,28403,28408,28411,28421,28424,28428,28437,28868,28871,29253,29256,29269,29272,29363,29372,29377,29381,29384,29389],[651,28394,28395],{},"I recently wrote an article about how to get started with the Angular CLI. This is an awesome productivity tool when it comes to getting started with Angular. With it we can quickly create, build and run our applications. This is really helpful but it also adds a little layer of magic to your application. If you always use the CLI but don't take the time to appreciate what is happening behind the scenes you won't understand how to fix problems when they come up. In this article, we are going to look at how Angular boots up your application. ",[4542,28397,28399],{"id":28398},"creating-a-new-angular-application","Creating a new Angular Application",[651,28401,28402],{},"When you use the Angular CLI to create a new application you get a bunch of files and directories created for you. ",[651,28404,28405],{},[660,28406],{"alt":28407,"src":27418},"Creating Angular App",[651,28409,28410],{},"We also know that we can use the CLI to run the application using the following command. ",[669,28412,28413],{"className":5851,"code":27424,"language":5853,"meta":674,"style":674},[676,28414,28415],{"__ignoreMap":674},[679,28416,28417,28419],{"class":681,"line":682},[679,28418,27380],{"class":880},[679,28420,27433],{"class":689},[651,28422,28423],{},"This is great and makes our lives easier as we don't have to worry about the nuts and bolts of the application. When we start our application though we don't know what Angular is doing under the hood so let's actually walk through that now. ",[4542,28425,28427],{"id":28426},"bootstrapping-angular","Bootstrapping Angular",[651,28429,28430,28431,28436],{},"Every application we build has an entry point. When we run the hello-angular application above using ",[2939,28432,28433],{},[7300,28434,28435],{},"ng serve"," it will first look at the file angular-cli.json. This file contains some information about the application but the main setting we want to look at here is line 15 which tells us \"main.ts\" is our main file.",[669,28438,28442],{"className":28439,"code":28440,"language":28441,"meta":674,"style":674},"language-json shiki shiki-themes github-light github-dark github-light","{\n \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n \"project\": {\n \"name\": \"hello-angular\"\n },\n \"apps\": [\n {\n \"root\": \"src\",\n \"outDir\": \"dist\",\n \"assets\": [\n \"assets\",\n \"favicon.ico\"\n ],\n \"index\": \"index.html\",\n \"main\": \"main.ts\",\n \"polyfills\": \"polyfills.ts\",\n \"test\": \"test.ts\",\n \"tsconfig\": \"tsconfig.app.json\",\n \"testTsconfig\": \"tsconfig.spec.json\",\n \"prefix\": \"app\",\n \"styles\": [\n \"styles.css\"\n ],\n \"scripts\": [],\n \"environmentSource\": \"environments/environment.ts\",\n \"environments\": {\n \"dev\": \"environments/environment.ts\",\n \"prod\": \"environments/environment.prod.ts\"\n }\n }\n ],\n \"e2e\": {\n \"protractor\": {\n \"config\": \"./protractor.conf.js\"\n }\n },\n \"lint\": [\n {\n \"project\": \"src/tsconfig.app.json\"\n },\n {\n \"project\": \"src/tsconfig.spec.json\"\n },\n {\n \"project\": \"e2e/tsconfig.e2e.json\"\n }\n ],\n \"test\": {\n \"karma\": {\n \"config\": \"./karma.conf.js\"\n }\n },\n \"defaults\": {\n \"styleExt\": \"css\",\n \"component\": {}\n }\n}\n","json",[676,28443,28444,28449,28461,28469,28479,28484,28492,28497,28509,28521,28528,28535,28540,28545,28557,28569,28581,28593,28605,28617,28629,28636,28641,28645,28653,28665,28672,28683,28693,28697,28701,28706,28713,28720,28730,28734,28738,28745,28749,28759,28764,28768,28777,28781,28785,28794,28798,28802,28809,28816,28825,28829,28833,28840,28852,28860,28864],{"__ignoreMap":674},[679,28445,28446],{"class":681,"line":682},[679,28447,28448],{"class":693},"{\n",[679,28450,28451,28454,28456,28459],{"class":681,"line":790},[679,28452,28453],{"class":931}," \"$schema\"",[679,28455,4282],{"class":693},[679,28457,28458],{"class":689},"\"./node_modules/@angular/cli/lib/config/schema.json\"",[679,28460,12083],{"class":693},[679,28462,28463,28466],{"class":681,"line":892},[679,28464,28465],{"class":931}," \"project\"",[679,28467,28468],{"class":693},": {\n",[679,28470,28471,28474,28476],{"class":681,"line":901},[679,28472,28473],{"class":931}," \"name\"",[679,28475,4282],{"class":693},[679,28477,28478],{"class":689},"\"hello-angular\"\n",[679,28480,28481],{"class":681,"line":909},[679,28482,28483],{"class":693}," },\n",[679,28485,28486,28489],{"class":681,"line":918},[679,28487,28488],{"class":931}," \"apps\"",[679,28490,28491],{"class":693},": [\n",[679,28493,28494],{"class":681,"line":935},[679,28495,28496],{"class":693}," {\n",[679,28498,28499,28502,28504,28507],{"class":681,"line":944},[679,28500,28501],{"class":931}," \"root\"",[679,28503,4282],{"class":693},[679,28505,28506],{"class":689},"\"src\"",[679,28508,12083],{"class":693},[679,28510,28511,28514,28516,28519],{"class":681,"line":959},[679,28512,28513],{"class":931}," \"outDir\"",[679,28515,4282],{"class":693},[679,28517,28518],{"class":689},"\"dist\"",[679,28520,12083],{"class":693},[679,28522,28523,28526],{"class":681,"line":964},[679,28524,28525],{"class":931}," \"assets\"",[679,28527,28491],{"class":693},[679,28529,28530,28533],{"class":681,"line":977},[679,28531,28532],{"class":689}," \"assets\"",[679,28534,12083],{"class":693},[679,28536,28537],{"class":681,"line":982},[679,28538,28539],{"class":689}," \"favicon.ico\"\n",[679,28541,28542],{"class":681,"line":988},[679,28543,28544],{"class":693}," ],\n",[679,28546,28547,28550,28552,28555],{"class":681,"line":993},[679,28548,28549],{"class":931}," \"index\"",[679,28551,4282],{"class":693},[679,28553,28554],{"class":689},"\"index.html\"",[679,28556,12083],{"class":693},[679,28558,28559,28562,28564,28567],{"class":681,"line":2129},[679,28560,28561],{"class":931}," \"main\"",[679,28563,4282],{"class":693},[679,28565,28566],{"class":689},"\"main.ts\"",[679,28568,12083],{"class":693},[679,28570,28571,28574,28576,28579],{"class":681,"line":2140},[679,28572,28573],{"class":931}," \"polyfills\"",[679,28575,4282],{"class":693},[679,28577,28578],{"class":689},"\"polyfills.ts\"",[679,28580,12083],{"class":693},[679,28582,28583,28586,28588,28591],{"class":681,"line":2145},[679,28584,28585],{"class":931}," \"test\"",[679,28587,4282],{"class":693},[679,28589,28590],{"class":689},"\"test.ts\"",[679,28592,12083],{"class":693},[679,28594,28595,28598,28600,28603],{"class":681,"line":2154},[679,28596,28597],{"class":931}," \"tsconfig\"",[679,28599,4282],{"class":693},[679,28601,28602],{"class":689},"\"tsconfig.app.json\"",[679,28604,12083],{"class":693},[679,28606,28607,28610,28612,28615],{"class":681,"line":2159},[679,28608,28609],{"class":931}," \"testTsconfig\"",[679,28611,4282],{"class":693},[679,28613,28614],{"class":689},"\"tsconfig.spec.json\"",[679,28616,12083],{"class":693},[679,28618,28619,28622,28624,28627],{"class":681,"line":2164},[679,28620,28621],{"class":931}," \"prefix\"",[679,28623,4282],{"class":693},[679,28625,28626],{"class":689},"\"app\"",[679,28628,12083],{"class":693},[679,28630,28631,28634],{"class":681,"line":3134},[679,28632,28633],{"class":931}," \"styles\"",[679,28635,28491],{"class":693},[679,28637,28638],{"class":681,"line":3139},[679,28639,28640],{"class":689}," \"styles.css\"\n",[679,28642,28643],{"class":681,"line":3144},[679,28644,28544],{"class":693},[679,28646,28647,28650],{"class":681,"line":3149},[679,28648,28649],{"class":931}," \"scripts\"",[679,28651,28652],{"class":693},": [],\n",[679,28654,28655,28658,28660,28663],{"class":681,"line":3169},[679,28656,28657],{"class":931}," \"environmentSource\"",[679,28659,4282],{"class":693},[679,28661,28662],{"class":689},"\"environments/environment.ts\"",[679,28664,12083],{"class":693},[679,28666,28667,28670],{"class":681,"line":3185},[679,28668,28669],{"class":931}," \"environments\"",[679,28671,28468],{"class":693},[679,28673,28674,28677,28679,28681],{"class":681,"line":3194},[679,28675,28676],{"class":931}," \"dev\"",[679,28678,4282],{"class":693},[679,28680,28662],{"class":689},[679,28682,12083],{"class":693},[679,28684,28685,28688,28690],{"class":681,"line":3199},[679,28686,28687],{"class":931}," \"prod\"",[679,28689,4282],{"class":693},[679,28691,28692],{"class":689},"\"environments/environment.prod.ts\"\n",[679,28694,28695],{"class":681,"line":3212},[679,28696,11804],{"class":693},[679,28698,28699],{"class":681,"line":3217},[679,28700,985],{"class":693},[679,28702,28703],{"class":681,"line":3222},[679,28704,28705],{"class":693}," ],\n",[679,28707,28708,28711],{"class":681,"line":3227},[679,28709,28710],{"class":931}," \"e2e\"",[679,28712,28468],{"class":693},[679,28714,28715,28718],{"class":681,"line":3232},[679,28716,28717],{"class":931}," \"protractor\"",[679,28719,28468],{"class":693},[679,28721,28722,28725,28727],{"class":681,"line":3499},[679,28723,28724],{"class":931}," \"config\"",[679,28726,4282],{"class":693},[679,28728,28729],{"class":689},"\"./protractor.conf.js\"\n",[679,28731,28732],{"class":681,"line":3509},[679,28733,985],{"class":693},[679,28735,28736],{"class":681,"line":3516},[679,28737,28483],{"class":693},[679,28739,28740,28743],{"class":681,"line":3531},[679,28741,28742],{"class":931}," \"lint\"",[679,28744,28491],{"class":693},[679,28746,28747],{"class":681,"line":3536},[679,28748,28496],{"class":693},[679,28750,28751,28754,28756],{"class":681,"line":3541},[679,28752,28753],{"class":931}," \"project\"",[679,28755,4282],{"class":693},[679,28757,28758],{"class":689},"\"src/tsconfig.app.json\"\n",[679,28760,28761],{"class":681,"line":3546},[679,28762,28763],{"class":693}," },\n",[679,28765,28766],{"class":681,"line":3551},[679,28767,28496],{"class":693},[679,28769,28770,28772,28774],{"class":681,"line":3557},[679,28771,28753],{"class":931},[679,28773,4282],{"class":693},[679,28775,28776],{"class":689},"\"src/tsconfig.spec.json\"\n",[679,28778,28779],{"class":681,"line":3567},[679,28780,28763],{"class":693},[679,28782,28783],{"class":681,"line":3574},[679,28784,28496],{"class":693},[679,28786,28787,28789,28791],{"class":681,"line":3589},[679,28788,28753],{"class":931},[679,28790,4282],{"class":693},[679,28792,28793],{"class":689},"\"e2e/tsconfig.e2e.json\"\n",[679,28795,28796],{"class":681,"line":3594},[679,28797,985],{"class":693},[679,28799,28800],{"class":681,"line":3602},[679,28801,28705],{"class":693},[679,28803,28804,28807],{"class":681,"line":3608},[679,28805,28806],{"class":931}," \"test\"",[679,28808,28468],{"class":693},[679,28810,28811,28814],{"class":681,"line":3619},[679,28812,28813],{"class":931}," \"karma\"",[679,28815,28468],{"class":693},[679,28817,28818,28820,28822],{"class":681,"line":3624},[679,28819,28724],{"class":931},[679,28821,4282],{"class":693},[679,28823,28824],{"class":689},"\"./karma.conf.js\"\n",[679,28826,28827],{"class":681,"line":3629},[679,28828,985],{"class":693},[679,28830,28831],{"class":681,"line":3639},[679,28832,28483],{"class":693},[679,28834,28835,28838],{"class":681,"line":3644},[679,28836,28837],{"class":931}," \"defaults\"",[679,28839,28468],{"class":693},[679,28841,28842,28845,28847,28850],{"class":681,"line":3649},[679,28843,28844],{"class":931}," \"styleExt\"",[679,28846,4282],{"class":693},[679,28848,28849],{"class":689},"\"css\"",[679,28851,12083],{"class":693},[679,28853,28854,28857],{"class":681,"line":3659},[679,28855,28856],{"class":931}," \"component\"",[679,28858,28859],{"class":693},": {}\n",[679,28861,28862],{"class":681,"line":3664},[679,28863,21405],{"class":693},[679,28865,28866],{"class":681,"line":3669},[679,28867,996],{"class":693},[651,28869,28870],{},"If we look at the file src/main.ts we will see the following code. ",[669,28872,28876],{"className":28873,"code":28874,"language":28875,"meta":674,"style":674},"language-typescript shiki shiki-themes github-light github-dark github-light","import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n enableProdMode();\n}\n\nplatformBrowserDynamic().bootstrapModule(AppModule);\n\nThe last line is where the magic happens and the bootstrap process boots an Angular module called AppModule. If you look under src/app you will see a file, app.module.ts and this is our AppModule that gets called. \n\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpModule } from '@angular/http';\n\nimport { AppComponent } from './app.component';\nimport { UsersComponent } from './users/users.component';\nimport { UsersListComponent } from './users/users-list/users-list.component';\n\n@NgModule({\n declarations: [\n AppComponent,\n UsersComponent,\n UsersListComponent\n ],\n imports: [\n BrowserModule,\n FormsModule,\n HttpModule\n ],\n providers: [],\n bootstrap: [AppComponent]\n})\nexport class AppModule { }\n","typescript",[676,28877,28878,28893,28907,28911,28925,28939,28943,28950,28957,28961,28965,28978,28982,29061,29065,29079,29092,29106,29120,29124,29138,29152,29166,29170,29179,29184,29189,29194,29199,29203,29208,29213,29218,29223,29227,29232,29237,29241],{"__ignoreMap":674},[679,28879,28880,28882,28885,28888,28891],{"class":681,"line":682},[679,28881,1999],{"class":685},[679,28883,28884],{"class":693}," { enableProdMode } ",[679,28886,28887],{"class":685},"from",[679,28889,28890],{"class":689}," '@angular/core'",[679,28892,1186],{"class":693},[679,28894,28895,28897,28900,28902,28905],{"class":681,"line":790},[679,28896,1999],{"class":685},[679,28898,28899],{"class":693}," { platformBrowserDynamic } ",[679,28901,28887],{"class":685},[679,28903,28904],{"class":689}," '@angular/platform-browser-dynamic'",[679,28906,1186],{"class":693},[679,28908,28909],{"class":681,"line":892},[679,28910,889],{"emptyLinePlaceholder":797},[679,28912,28913,28915,28918,28920,28923],{"class":681,"line":901},[679,28914,1999],{"class":685},[679,28916,28917],{"class":693}," { AppModule } ",[679,28919,28887],{"class":685},[679,28921,28922],{"class":689}," './app/app.module'",[679,28924,1186],{"class":693},[679,28926,28927,28929,28932,28934,28937],{"class":681,"line":909},[679,28928,1999],{"class":685},[679,28930,28931],{"class":693}," { environment } ",[679,28933,28887],{"class":685},[679,28935,28936],{"class":689}," './environments/environment'",[679,28938,1186],{"class":693},[679,28940,28941],{"class":681,"line":918},[679,28942,889],{"emptyLinePlaceholder":797},[679,28944,28945,28947],{"class":681,"line":935},[679,28946,1217],{"class":685},[679,28948,28949],{"class":693}," (environment.production) {\n",[679,28951,28952,28955],{"class":681,"line":944},[679,28953,28954],{"class":880}," enableProdMode",[679,28956,9317],{"class":693},[679,28958,28959],{"class":681,"line":959},[679,28960,996],{"class":693},[679,28962,28963],{"class":681,"line":964},[679,28964,889],{"emptyLinePlaceholder":797},[679,28966,28967,28970,28972,28975],{"class":681,"line":977},[679,28968,28969],{"class":880},"platformBrowserDynamic",[679,28971,10541],{"class":693},[679,28973,28974],{"class":880},"bootstrapModule",[679,28976,28977],{"class":693},"(AppModule);\n",[679,28979,28980],{"class":681,"line":982},[679,28981,889],{"emptyLinePlaceholder":797},[679,28983,28984,28987,28989,28992,28995,28998,29000,29002,29005,29008,29010,29012,29015,29017,29019,29021,29023,29026,29028,29030,29032,29034,29036,29039,29041,29043,29046,29049,29051,29053,29056,29058],{"class":681,"line":988},[679,28985,28986],{"class":693},"The last line is where the magic happens and the bootstrap process boots an Angular ",[679,28988,27515],{"class":685},[679,28990,28991],{"class":880}," called",[679,28993,28994],{"class":880}," AppModule",[679,28996,28997],{"class":693},". ",[679,28999,22334],{"class":880},[679,29001,22337],{"class":880},[679,29003,29004],{"class":880}," look",[679,29006,29007],{"class":880}," under",[679,29009,5361],{"class":880},[679,29011,4408],{"class":693},[679,29013,29014],{"class":880},"app",[679,29016,22337],{"class":880},[679,29018,22359],{"class":880},[679,29020,22362],{"class":880},[679,29022,21697],{"class":880},[679,29024,29025],{"class":880}," file",[679,29027,2797],{"class":693},[679,29029,29014],{"class":880},[679,29031,664],{"class":693},[679,29033,27515],{"class":880},[679,29035,664],{"class":693},[679,29037,29038],{"class":880},"ts",[679,29040,22346],{"class":880},[679,29042,21353],{"class":880},[679,29044,29045],{"class":880}," is",[679,29047,29048],{"class":880}," our",[679,29050,28994],{"class":880},[679,29052,6417],{"class":880},[679,29054,29055],{"class":880}," gets",[679,29057,28991],{"class":880},[679,29059,29060],{"class":693},". \n",[679,29062,29063],{"class":681,"line":993},[679,29064,889],{"emptyLinePlaceholder":797},[679,29066,29067,29069,29072,29074,29077],{"class":681,"line":2129},[679,29068,1999],{"class":685},[679,29070,29071],{"class":693}," { BrowserModule } ",[679,29073,28887],{"class":685},[679,29075,29076],{"class":689}," '@angular/platform-browser'",[679,29078,1186],{"class":693},[679,29080,29081,29083,29086,29088,29090],{"class":681,"line":2140},[679,29082,1999],{"class":685},[679,29084,29085],{"class":693}," { NgModule } ",[679,29087,28887],{"class":685},[679,29089,28890],{"class":689},[679,29091,1186],{"class":693},[679,29093,29094,29096,29099,29101,29104],{"class":681,"line":2145},[679,29095,1999],{"class":685},[679,29097,29098],{"class":693}," { FormsModule } ",[679,29100,28887],{"class":685},[679,29102,29103],{"class":689}," '@angular/forms'",[679,29105,1186],{"class":693},[679,29107,29108,29110,29113,29115,29118],{"class":681,"line":2154},[679,29109,1999],{"class":685},[679,29111,29112],{"class":693}," { HttpModule } ",[679,29114,28887],{"class":685},[679,29116,29117],{"class":689}," '@angular/http'",[679,29119,1186],{"class":693},[679,29121,29122],{"class":681,"line":2159},[679,29123,889],{"emptyLinePlaceholder":797},[679,29125,29126,29128,29131,29133,29136],{"class":681,"line":2164},[679,29127,1999],{"class":685},[679,29129,29130],{"class":693}," { AppComponent } ",[679,29132,28887],{"class":685},[679,29134,29135],{"class":689}," './app.component'",[679,29137,1186],{"class":693},[679,29139,29140,29142,29145,29147,29150],{"class":681,"line":3134},[679,29141,1999],{"class":685},[679,29143,29144],{"class":693}," { UsersComponent } ",[679,29146,28887],{"class":685},[679,29148,29149],{"class":689}," './users/users.component'",[679,29151,1186],{"class":693},[679,29153,29154,29156,29159,29161,29164],{"class":681,"line":3139},[679,29155,1999],{"class":685},[679,29157,29158],{"class":693}," { UsersListComponent } ",[679,29160,28887],{"class":685},[679,29162,29163],{"class":689}," './users/users-list/users-list.component'",[679,29165,1186],{"class":693},[679,29167,29168],{"class":681,"line":3144},[679,29169,889],{"emptyLinePlaceholder":797},[679,29171,29172,29174,29177],{"class":681,"line":3149},[679,29173,4116],{"class":693},[679,29175,29176],{"class":880},"NgModule",[679,29178,21218],{"class":693},[679,29180,29181],{"class":681,"line":3169},[679,29182,29183],{"class":693}," declarations: [\n",[679,29185,29186],{"class":681,"line":3185},[679,29187,29188],{"class":693}," AppComponent,\n",[679,29190,29191],{"class":681,"line":3194},[679,29192,29193],{"class":693}," UsersComponent,\n",[679,29195,29196],{"class":681,"line":3199},[679,29197,29198],{"class":693}," UsersListComponent\n",[679,29200,29201],{"class":681,"line":3212},[679,29202,28705],{"class":693},[679,29204,29205],{"class":681,"line":3217},[679,29206,29207],{"class":693}," imports: [\n",[679,29209,29210],{"class":681,"line":3222},[679,29211,29212],{"class":693}," BrowserModule,\n",[679,29214,29215],{"class":681,"line":3227},[679,29216,29217],{"class":693}," FormsModule,\n",[679,29219,29220],{"class":681,"line":3232},[679,29221,29222],{"class":693}," HttpModule\n",[679,29224,29225],{"class":681,"line":3499},[679,29226,28705],{"class":693},[679,29228,29229],{"class":681,"line":3509},[679,29230,29231],{"class":693}," providers: [],\n",[679,29233,29234],{"class":681,"line":3516},[679,29235,29236],{"class":693}," bootstrap: [AppComponent]\n",[679,29238,29239],{"class":681,"line":3531},[679,29240,6240],{"class":693},[679,29242,29243,29246,29248,29250],{"class":681,"line":3536},[679,29244,29245],{"class":685},"export",[679,29247,4512],{"class":685},[679,29249,28994],{"class":880},[679,29251,29252],{"class":693}," { }\n",[651,29254,29255],{},"The AppModule contains some declarations and imports but it also contains one very important line. ",[669,29257,29259],{"className":28873,"code":29258,"language":28875,"meta":674,"style":674},"bootstrap: [AppComponent]\n",[676,29260,29261],{"__ignoreMap":674},[679,29262,29263,29266],{"class":681,"line":682},[679,29264,29265],{"class":880},"bootstrap",[679,29267,29268],{"class":693},": [AppComponent]\n",[651,29270,29271],{},"This is telling Angular what component it should load as the application's top level component. So now the app component is loaded that looks like this. ",[669,29273,29275],{"className":28873,"code":29274,"language":28875,"meta":674,"style":674},"import { Component } from '@angular/core';\n\n@Component({\n selector: 'app-root',\n templateUrl: './app.component.html',\n styleUrls: \\['./app.component.css'\\]\n})\nexport class AppComponent {\n title = 'app works!';\n}\n",[676,29276,29277,29290,29294,29302,29312,29322,29332,29336,29347,29359],{"__ignoreMap":674},[679,29278,29279,29281,29284,29286,29288],{"class":681,"line":682},[679,29280,1999],{"class":685},[679,29282,29283],{"class":693}," { Component } ",[679,29285,28887],{"class":685},[679,29287,28890],{"class":689},[679,29289,1186],{"class":693},[679,29291,29292],{"class":681,"line":790},[679,29293,889],{"emptyLinePlaceholder":797},[679,29295,29296,29298,29300],{"class":681,"line":892},[679,29297,4116],{"class":693},[679,29299,21215],{"class":880},[679,29301,21218],{"class":693},[679,29303,29304,29307,29310],{"class":681,"line":901},[679,29305,29306],{"class":693}," selector: ",[679,29308,29309],{"class":689},"'app-root'",[679,29311,12083],{"class":693},[679,29313,29314,29317,29320],{"class":681,"line":909},[679,29315,29316],{"class":693}," templateUrl: ",[679,29318,29319],{"class":689},"'./app.component.html'",[679,29321,12083],{"class":693},[679,29323,29324,29327,29330],{"class":681,"line":918},[679,29325,29326],{"class":693}," styleUrls: \\[",[679,29328,29329],{"class":689},"'./app.component.css'",[679,29331,1720],{"class":693},[679,29333,29334],{"class":681,"line":935},[679,29335,6240],{"class":693},[679,29337,29338,29340,29342,29345],{"class":681,"line":944},[679,29339,29245],{"class":685},[679,29341,4512],{"class":685},[679,29343,29344],{"class":880}," AppComponent",[679,29346,884],{"class":693},[679,29348,29349,29352,29354,29357],{"class":681,"line":959},[679,29350,29351],{"class":2099}," title",[679,29353,6883],{"class":685},[679,29355,29356],{"class":689}," 'app works!'",[679,29358,1186],{"class":693},[679,29360,29361],{"class":681,"line":964},[679,29362,996],{"class":693},[651,29364,29365,29366,29371],{},"The class sets a title variable and displays it in the template and this is why you see '",[7300,29367,29368],{},[2939,29369,29370],{},"app works","' when you fire up your application for the first time. ",[651,29373,29374],{},[660,29375],{"alt":28427,"src":29376},"./2017-06-12_11-40-00.png",[4542,29378,29380],{"id":29379},"bootstrapping-angular-conclusion","Bootstrapping Angular Conclusion",[651,29382,29383],{},"If you have done any work in Angular 2/4 you will know that we build out our applications using this component model. What you might not of understand is how that top-level component was called so I really hope this quick tutorial helped clear that up for you. ",[651,29385,13453,29386,29388],{},[2939,29387,11650],{}," Do you have any questions about the initial application the Angular CLI creates for you? _",[786,29390,29391],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":29393},[29394,29395,29396],{"id":28398,"depth":790,"text":28399},{"id":28426,"depth":790,"text":28427},{"id":29379,"depth":790,"text":29380},{"slug":29398,"published":797,"date":29399,"tags":29400,"cover":29401},"bootstrapping-angular-application","2017-06-12T11:40:51-04:00",[17866],"./pexels-photo-177598-760x506.jpeg",{"title":144,"description":144},"blog/2017/06/12/bootstrapping-angular-application","rYs4avv7hju_lO8hYxTUH5t2mejpgGEf_MHBXO2WgEY",{"id":29406,"title":141,"body":29407,"description":141,"extension":793,"meta":29938,"navigation":797,"path":142,"seo":29943,"stem":29944,"__hash__":29945},"content/blog/2017/06/14/spring-boot-defining-requestmapping-handler-methods.md",{"type":648,"value":29408,"toc":29932},[29409,29412,29456,29460,29463,29668,29671,29675,29682,29859,29868,29873,29876,29910,29917,29919,29922,29929],[651,29410,29411],{},"In this article, we are going to take a look at what happens when you define a method that is annotated with @RequestMapping. @RequestMapping is one of the most widely used Spring MVC annotation. RequestMapping annotation is used to map web requests onto specific handler classes and/or handler methods. If you have done any work with Spring MVC you have undoubtedly come across this annotation before. The reason we are talking about this is that I had a student ask me the following question.",[1004,29413,29414],{},[651,29415,29416,29417,29420,29421,29424,29425,29428,29429,29432,29433,29436,29437,29440,29441,29443,29444,29447,29448,29451,29452,29455],{},"Hi Dan, I'm really enjoying the course so far but there are a few things that don't quite fit to my head. In the lesson 43 (Error Handling) we have ",[676,29418,29419],{},"CustomErrorController"," class where we are injecting ",[676,29422,29423],{},"ErrorAttributes"," inside the constructor via ",[676,29426,29427],{},"@AutoWired"," so I assume that Spring boot will initialize that object automatically for us. Now what about the ",[676,29430,29431],{},"error()"," method? How did the parameters ",[676,29434,29435],{},"Model model"," and ",[676,29438,29439],{},"HttpServletRequest request"," came up? Is spring boot going to initialize those objects for us too? (apparently yes cause they just work) But then why ",[676,29442,29427],{}," is not needed to automatically inject those objects just like what happens in constructor? Furthermore how could I know that I need the above 2 parameters? Could there be any further parameters in the ",[676,29445,29446],{},"error"," method that I should be aware of? If yes would they be initialized automagically like ",[676,29449,29450],{},"Model"," and ",[676,29453,29454],{},"HttpServletRequest"," ? Thanks",[4542,29457,29459],{"id":29458},"requestmapping-examples","@RequestMapping Examples",[651,29461,29462],{},"Before we dive into the solution and explain why this happening I want to catch the rest of you up with what is going on. I had a custom error controller that I was using that looked something like this. ",[669,29464,29466],{"className":4107,"code":29465,"language":4109,"meta":674,"style":674},"@Controller\npublic class CustomErrorController implements ErrorController {\n\n private static final String ERROR_PATH = \"/error\";\n private static final String ERROR_TEMPLATE = \"customError\";\n\n private final ErrorAttributes errorAttributes;\n\n @Autowired\n public CustomErrorController(ErrorAttributes errorAttributes) {\n this.errorAttributes = errorAttributes;\n }\n\n @RequestMapping(ERROR_PATH)\n public String error(Model model, HttpServletRequest request) {\n return \"SOME_ERROR_STRING\";\n }\n\n @Override\n public String getErrorPath() {\n return null;\n }\n\n}\n",[676,29467,29468,29474,29490,29494,29512,29530,29534,29543,29547,29553,29567,29579,29583,29587,29596,29614,29623,29627,29631,29637,29648,29656,29660,29664],{"__ignoreMap":674},[679,29469,29470,29472],{"class":681,"line":682},[679,29471,4116],{"class":693},[679,29473,9942],{"class":685},[679,29475,29476,29478,29480,29483,29485,29488],{"class":681,"line":790},[679,29477,6073],{"class":685},[679,29479,4512],{"class":685},[679,29481,29482],{"class":880}," CustomErrorController",[679,29484,4661],{"class":685},[679,29486,29487],{"class":880}," ErrorController",[679,29489,884],{"class":693},[679,29491,29492],{"class":681,"line":892},[679,29493,889],{"emptyLinePlaceholder":797},[679,29495,29496,29498,29500,29502,29505,29507,29510],{"class":681,"line":901},[679,29497,9232],{"class":685},[679,29499,6092],{"class":685},[679,29501,12768],{"class":685},[679,29503,29504],{"class":693}," String ERROR_PATH ",[679,29506,686],{"class":685},[679,29508,29509],{"class":689}," \"/error\"",[679,29511,1186],{"class":693},[679,29513,29514,29516,29518,29520,29523,29525,29528],{"class":681,"line":909},[679,29515,9232],{"class":685},[679,29517,6092],{"class":685},[679,29519,12768],{"class":685},[679,29521,29522],{"class":693}," String ERROR_TEMPLATE ",[679,29524,686],{"class":685},[679,29526,29527],{"class":689}," \"customError\"",[679,29529,1186],{"class":693},[679,29531,29532],{"class":681,"line":918},[679,29533,889],{"emptyLinePlaceholder":797},[679,29535,29536,29538,29540],{"class":681,"line":935},[679,29537,9232],{"class":685},[679,29539,12768],{"class":685},[679,29541,29542],{"class":693}," ErrorAttributes errorAttributes;\n",[679,29544,29545],{"class":681,"line":944},[679,29546,889],{"emptyLinePlaceholder":797},[679,29548,29549,29551],{"class":681,"line":959},[679,29550,6872],{"class":693},[679,29552,9257],{"class":685},[679,29554,29555,29557,29559,29562,29565],{"class":681,"line":964},[679,29556,6089],{"class":685},[679,29558,29482],{"class":880},[679,29560,29561],{"class":693},"(ErrorAttributes ",[679,29563,29564],{"class":2099},"errorAttributes",[679,29566,4390],{"class":693},[679,29568,29569,29571,29574,29576],{"class":681,"line":977},[679,29570,7862],{"class":931},[679,29572,29573],{"class":693},".errorAttributes ",[679,29575,686],{"class":685},[679,29577,29578],{"class":693}," errorAttributes;\n",[679,29580,29581],{"class":681,"line":982},[679,29582,985],{"class":693},[679,29584,29585],{"class":681,"line":988},[679,29586,889],{"emptyLinePlaceholder":797},[679,29588,29589,29591,29593],{"class":681,"line":993},[679,29590,6872],{"class":693},[679,29592,9275],{"class":685},[679,29594,29595],{"class":693},"(ERROR_PATH)\n",[679,29597,29598,29600,29602,29604,29606,29608,29610,29612],{"class":681,"line":2129},[679,29599,6089],{"class":685},[679,29601,9289],{"class":693},[679,29603,29446],{"class":880},[679,29605,10045],{"class":693},[679,29607,10048],{"class":2099},[679,29609,10051],{"class":693},[679,29611,10054],{"class":2099},[679,29613,4390],{"class":693},[679,29615,29616,29618,29621],{"class":681,"line":2140},[679,29617,9444],{"class":685},[679,29619,29620],{"class":689}," \"SOME_ERROR_STRING\"",[679,29622,1186],{"class":693},[679,29624,29625],{"class":681,"line":2145},[679,29626,985],{"class":693},[679,29628,29629],{"class":681,"line":2154},[679,29630,889],{"emptyLinePlaceholder":797},[679,29632,29633,29635],{"class":681,"line":2159},[679,29634,6872],{"class":693},[679,29636,10723],{"class":685},[679,29638,29639,29641,29643,29646],{"class":681,"line":2164},[679,29640,6089],{"class":685},[679,29642,9289],{"class":693},[679,29644,29645],{"class":880},"getErrorPath",[679,29647,2667],{"class":693},[679,29649,29650,29652,29654],{"class":681,"line":3134},[679,29651,9444],{"class":685},[679,29653,2307],{"class":931},[679,29655,1186],{"class":693},[679,29657,29658],{"class":681,"line":3139},[679,29659,985],{"class":693},[679,29661,29662],{"class":681,"line":3144},[679,29663,889],{"emptyLinePlaceholder":797},[679,29665,29666],{"class":681,"line":3149},[679,29667,996],{"class":693},[651,29669,29670],{},"As the student pointed out when we create the constructor and annotated it with @Autowired (No longer needed in Spring 4.3+) Spring managed the constructor injection of the managed bean ErrorAttributes. This is one of those beans that the Spring Framework manages for us. So now the question is I have this method called error and in this method, I have some method arguments, so how are those being supplied for me? ",[5909,29672,29674],{"id":29673},"defining-requestmapping-handler-methods","Defining @RequestMapping handler methods",[651,29676,29677,29678,29681],{},"To understand the problem we must first understand that the ",[7300,29679,29680],{},"Custom Error Controller"," I created is nothing special. In fact, we can create a regular controller and it would act the same. This is an example of a Home Controller I used to respond to the \"/home\" request. If you look closely now though, I have 2 different method parameters being passed to my controller. What the heck is going on here? ",[669,29683,29685],{"className":4107,"code":29684,"language":4109,"meta":674,"style":674},"package com.therealdanvega.controller;\n\nimport com.therealdanvega.service.HomeService;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@Controller\npublic class HomeController {\n\n private HomeService homeService;\n\n public HomeController(HomeService homeService) {\n this.homeService = homeService;\n }\n\n @RequestMapping(\"/home\")\n public String home(HttpServletRequest request, HttpServletResponse response) {\n return this.homeService.getMsg(request);\n }\n\n}\n",[676,29686,29687,29693,29697,29704,29710,29716,29720,29726,29732,29736,29742,29752,29756,29763,29767,29781,29793,29797,29801,29814,29832,29847,29851,29855],{"__ignoreMap":674},[679,29688,29689,29691],{"class":681,"line":682},[679,29690,2543],{"class":685},[679,29692,12409],{"class":693},[679,29694,29695],{"class":681,"line":790},[679,29696,889],{"emptyLinePlaceholder":797},[679,29698,29699,29701],{"class":681,"line":892},[679,29700,1999],{"class":685},[679,29702,29703],{"class":693}," com.therealdanvega.service.HomeService;\n",[679,29705,29706,29708],{"class":681,"line":901},[679,29707,1999],{"class":685},[679,29709,12420],{"class":693},[679,29711,29712,29714],{"class":681,"line":909},[679,29713,1999],{"class":685},[679,29715,12545],{"class":693},[679,29717,29718],{"class":681,"line":918},[679,29719,889],{"emptyLinePlaceholder":797},[679,29721,29722,29724],{"class":681,"line":935},[679,29723,1999],{"class":685},[679,29725,10371],{"class":693},[679,29727,29728,29730],{"class":681,"line":944},[679,29729,1999],{"class":685},[679,29731,10378],{"class":693},[679,29733,29734],{"class":681,"line":959},[679,29735,889],{"emptyLinePlaceholder":797},[679,29737,29738,29740],{"class":681,"line":964},[679,29739,4116],{"class":693},[679,29741,9942],{"class":685},[679,29743,29744,29746,29748,29750],{"class":681,"line":977},[679,29745,6073],{"class":685},[679,29747,4512],{"class":685},[679,29749,18716],{"class":880},[679,29751,884],{"class":693},[679,29753,29754],{"class":681,"line":982},[679,29755,889],{"emptyLinePlaceholder":797},[679,29757,29758,29760],{"class":681,"line":988},[679,29759,9232],{"class":685},[679,29761,29762],{"class":693}," HomeService homeService;\n",[679,29764,29765],{"class":681,"line":993},[679,29766,889],{"emptyLinePlaceholder":797},[679,29768,29769,29771,29773,29776,29779],{"class":681,"line":2129},[679,29770,6089],{"class":685},[679,29772,18716],{"class":880},[679,29774,29775],{"class":693},"(HomeService ",[679,29777,29778],{"class":2099},"homeService",[679,29780,4390],{"class":693},[679,29782,29783,29785,29788,29790],{"class":681,"line":2140},[679,29784,7862],{"class":931},[679,29786,29787],{"class":693},".homeService ",[679,29789,686],{"class":685},[679,29791,29792],{"class":693}," homeService;\n",[679,29794,29795],{"class":681,"line":2145},[679,29796,985],{"class":693},[679,29798,29799],{"class":681,"line":2154},[679,29800,889],{"emptyLinePlaceholder":797},[679,29802,29803,29805,29807,29809,29812],{"class":681,"line":2159},[679,29804,6872],{"class":693},[679,29806,9275],{"class":685},[679,29808,745],{"class":693},[679,29810,29811],{"class":689},"\"/home\"",[679,29813,1339],{"class":693},[679,29815,29816,29818,29820,29822,29824,29826,29828,29830],{"class":681,"line":2164},[679,29817,6089],{"class":685},[679,29819,9289],{"class":693},[679,29821,12642],{"class":880},[679,29823,10439],{"class":693},[679,29825,10054],{"class":2099},[679,29827,10444],{"class":693},[679,29829,10447],{"class":2099},[679,29831,4390],{"class":693},[679,29833,29834,29836,29838,29841,29844],{"class":681,"line":3134},[679,29835,9444],{"class":685},[679,29837,21353],{"class":931},[679,29839,29840],{"class":693},".homeService.",[679,29842,29843],{"class":880},"getMsg",[679,29845,29846],{"class":693},"(request);\n",[679,29848,29849],{"class":681,"line":3139},[679,29850,985],{"class":693},[679,29852,29853],{"class":681,"line":3144},[679,29854,889],{"emptyLinePlaceholder":797},[679,29856,29857],{"class":681,"line":3149},[679,29858,996],{"class":693},[651,29860,29861,29862,29867],{},"The real secret to what is happening here is the @RequestMapping annotation on the method. If you look back to our CustomErrrorController you will see the same annotation on the method in question. If you take a look at the Spring MVC documentation on ",[812,29863,29866],{"href":29864,"rel":29865},"http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-methods",[816],"@RequestMapping handler Methods",": ",[1004,29869,29870],{},[651,29871,29872],{},"@RequestMapping handler methods can have very flexible signatures. Most arguments can be used in arbitrary order with the only exception being BindingResult arguments.",[651,29874,29875],{},"What this means is that when we define a method and use the annotation @RequestMapping we have access to a number of method arguments that help us get information like;",[5316,29877,29878,29881,29884,29887,29890,29893,29896,29899,29901,29904,29907],{},[5332,29879,29880],{},"Request & Response Objects",[5332,29882,29883],{},"Web Request",[5332,29885,29886],{},"Session Information",[5332,29888,29889],{},"Http Method",[5332,29891,29892],{},"Path Variables",[5332,29894,29895],{},"Request Parameters",[5332,29897,29898],{},"Request Body",[5332,29900,29450],{},[5332,29902,29903],{},"Errors",[5332,29905,29906],{},"Binding Results",[5332,29908,29909],{},"Much More...",[651,29911,29912,29913,664],{},"If you want to see all of the available ",[812,29914,29916],{"href":29864,"rel":29915},[816],"@RequestMapping handler methods check out the documentation here",[4542,29918,9042],{"id":9041},[651,29920,29921],{},"Spring does a lot of things behind the scenes for us and I completely understand how this can be confusing at times. You can usually find the answers in the documentation but at the same time, we don't always know what to look for. I can assure you I don't remember all of the method arguments and that is why I have that link above bookmarked. ",[651,29923,29924],{},[7300,29925,29926,29928],{},[2939,29927,11650],{}," Is there anything that happens behind the scenes in Spring you don't quite understand?",[786,29930,29931],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":29933},[29934,29937],{"id":29458,"depth":790,"text":29459,"children":29935},[29936],{"id":29673,"depth":892,"text":29674},{"id":9041,"depth":790,"text":9042},{"slug":29939,"published":797,"date":29940,"tags":29941,"cover":29942},"spring-boot-defining-requestmapping-handler-methods","2017-06-14T08:36:35-04:00",[7055],"./pexels-photo-374074-760x506.jpeg",{"title":141,"description":141},"blog/2017/06/14/spring-boot-defining-requestmapping-handler-methods","EyPB6crSOlabrD7Wh58RjGCVh0407DBBeCGYfXntiR4",{"id":29947,"title":642,"body":29948,"description":642,"extension":793,"meta":30405,"navigation":797,"path":643,"seo":30410,"stem":30411,"__hash__":30412},"content/blog/2017/06/16/migrating-grails-2-x-applications-grails-3-x.md",{"type":648,"value":29949,"toc":30394},[29950,29953,29957,29960,29966,29969,29973,29976,29981,29995,29999,30008,30014,30017,30031,30034,30038,30041,30056,30059,30182,30185,30227,30230,30234,30237,30265,30268,30344,30347,30364,30366,30369,30375,30381,30383,30386,30391],[651,29951,29952],{},"At we work we have a number of huge applications running on Grails 2. When we first heard about the Grails 3 release we couldn't wait to upgrade our applications and then we realized we couldn't. About a year ago our team started to do some investigation into what it would take to upgrade all of our applications. What we quickly found out is that our applications weren't the problem, it was all of the plugins. In this article, we are going to walk through the problems that faced us and I am proud to say that those problems no longer exist today. We are in the middle of upgrading all of our applications at work and I will let you know how we are doing it. ",[4542,29954,29956],{"id":29955},"grails-plugins","Grails Plugins",[651,29958,29959],{},"When I first go into Grails It was the plugin architecture of the framework that really drew me to it. I remember writing the same applications over and over again and having to spend days on boilerplate project code dealing with things like rest, security and data access. At work, we ended up moving a lot of our projects from ColdFusion over to Groovy & Grails. As I mentioned before about a year ago we decided that we wanted to move all of these applications over to Grails 3.x and ran into some problems.",[651,29961,29962],{},[660,29963],{"alt":29964,"src":29965},"Grails 3 Upgrade Problems","./pexels-photo-52608-1024x685.jpeg",[651,29967,29968],{},"Each Grails application is made up a bunch of plugins, some of which are public plugins and some are built in house. In Grails 3 there were a lot of changes to plugins and what this meant is that plugins that were written for Grails 2.x were not going to be compatible with 3.x We knew that all of the in-house plugins were under our control so as long as we wanted to put in the work we could get those moved over. It was all of the public plugins that our applications and even other plugins used. We found out that of the nearly 45-50 plugins that we were using in our applications that maybe 10 of them were compatible. With that, we would have to delay our migration project to Grails 3. ",[5909,29970,29972],{"id":29971},"grails-3-plugins","Grails 3 Plugins",[651,29974,29975],{},"If you look today there are 218 plugins in Grails 3 which isn't anything near the Grails 2 plugins but it is enough to get you going. All of the major plugins have been updated and I would say 98% of all the plugins that we use have been updated. ",[651,29977,29978],{},[660,29979],{"alt":29972,"src":29980},"./2017-06-16_07-37-18-1024x822.png",[651,29982,29983,29984,23212,29989,29994],{},"The other thing you have to remember is that Grails 3 now uses Gradle. This means that a lot of actions that might have been in a plugin before can now be moved to Gradle tasks. I know for us we built a code quality plugin in-house that was mainly made up of ",[812,29985,29988],{"href":29986,"rel":29987},"http://codenarc.sourceforge.net/",[816],"CodeNarc",[812,29990,29993],{"href":29991,"rel":29992},"https://www.atlassian.com/software/clover",[816],"Clover Code Coverage",". There are still no plugins for Grails 3 for either of these but there is a Gradle plugin for Clover and CodeNarc can be moved into a Gradle task. Grails 3 is also built on Spring Boot which gives us spring boot starters that provide functionality. All of these combined with the new features of Grails 3 means there are some plugins that are no longer needed so don't let the raw number of plugins fool you. ",[4542,29996,29998],{"id":29997},"migrate-from-grails-2x-to-3x","Migrate from Grails 2.x to 3.x",[651,30000,30001,30002,30007],{},"I am not going to go through every single step of how to migrate an application. The reason I am not going to is there is a great ",[812,30003,30006],{"href":30004,"rel":30005},"http://docs.grails.org/3.0.x/guide/upgrading.html",[816],"step by step migration guide on the Grails website"," that you can and should check out. Before you go jumping in the deep end and into a migration I thought I would just share a little bit of what I have learned throughout this process with you. ",[651,30009,30010],{},[660,30011],{"alt":30012,"src":30013},"Grails 3 Migration","./pexels-photo-207919-1024x683.jpeg",[651,30015,30016],{},"The first thing you will need to focus on is all of your plugins. I would go through each of your applications and plugins and make a list of all the plugins that you use. I divide these up into public plugins and in-house plugins. You will want to make sure all of the public plugins are supported. If they aren't there are a few questions you can ask yourself:",[5316,30018,30019,30022,30025,30028],{},[5332,30020,30021],{},"Can we move this functionality to our own plugin? ",[5332,30023,30024],{},"Is there another plugin that does something similar? ",[5332,30026,30027],{},"Is there a Gradle task that replaces this?",[5332,30029,30030],{},"Do we even need a plugin for this? ",[651,30032,30033],{},"In the case of that last question, we had a jQuery and jQuery-UI plugin that were no longer compatible. Since all they really did was include the jQuery libraries we knew that we could just do that manually and actually didn't need a plugin. ",[5909,30035,30037],{"id":30036},"grails-3-plugin","Grails 3 Plugin",[651,30039,30040],{},"The first step is to create a new Grails 3 plugin using the command line:",[669,30042,30044],{"className":5851,"code":30043,"language":5853,"meta":674,"style":674},"grails create-plugin quartz\n",[676,30045,30046],{"__ignoreMap":674},[679,30047,30048,30050,30053],{"class":681,"line":682},[679,30049,848],{"class":880},[679,30051,30052],{"class":689}," create-plugin",[679,30054,30055],{"class":689}," quartz\n",[651,30057,30058],{},"The next step is to copy the sources from the original Grails 2 plugin to the Grails 3 plugin:",[669,30060,30062],{"className":5851,"code":30061,"language":5853,"meta":674,"style":674},"# first the sources\ncp -rf ../quartz-2.x/src/groovy/ src/main/groovy\ncp -rf ../quartz-2.x/src/java/ src/main/groovy\ncp -rf ../quartz-2.x/grails-app/ grails-app\ncp -rf ../quartz-2.x/QuartzGrailsPlugin.groovy src/main/groovy/grails/plugins/quartz\n# then the tests\ncp -rf ../quartz-2.x/test/unit/\\* src/test/groovy\nmkdir -p src/integration-test/groovy\ncp -rf ../quartz-2.x/test/integration/\\* src/integration-test/groovy\n\n# then templates / other resources\ncp -rf ../quartz-2.x/src/templates/ src/main/templates\n",[676,30063,30064,30069,30083,30094,30106,30118,30123,30137,30148,30161,30165,30170],{"__ignoreMap":674},[679,30065,30066],{"class":681,"line":682},[679,30067,30068],{"class":1400},"# first the sources\n",[679,30070,30071,30074,30077,30080],{"class":681,"line":790},[679,30072,30073],{"class":880},"cp",[679,30075,30076],{"class":931}," -rf",[679,30078,30079],{"class":689}," ../quartz-2.x/src/groovy/",[679,30081,30082],{"class":689}," src/main/groovy\n",[679,30084,30085,30087,30089,30092],{"class":681,"line":892},[679,30086,30073],{"class":880},[679,30088,30076],{"class":931},[679,30090,30091],{"class":689}," ../quartz-2.x/src/java/",[679,30093,30082],{"class":689},[679,30095,30096,30098,30100,30103],{"class":681,"line":901},[679,30097,30073],{"class":880},[679,30099,30076],{"class":931},[679,30101,30102],{"class":689}," ../quartz-2.x/grails-app/",[679,30104,30105],{"class":689}," grails-app\n",[679,30107,30108,30110,30112,30115],{"class":681,"line":909},[679,30109,30073],{"class":880},[679,30111,30076],{"class":931},[679,30113,30114],{"class":689}," ../quartz-2.x/QuartzGrailsPlugin.groovy",[679,30116,30117],{"class":689}," src/main/groovy/grails/plugins/quartz\n",[679,30119,30120],{"class":681,"line":918},[679,30121,30122],{"class":1400},"# then the tests\n",[679,30124,30125,30127,30129,30132,30134],{"class":681,"line":935},[679,30126,30073],{"class":880},[679,30128,30076],{"class":931},[679,30130,30131],{"class":689}," ../quartz-2.x/test/unit/",[679,30133,13165],{"class":931},[679,30135,30136],{"class":689}," src/test/groovy\n",[679,30138,30139,30142,30145],{"class":681,"line":944},[679,30140,30141],{"class":880},"mkdir",[679,30143,30144],{"class":931}," -p",[679,30146,30147],{"class":689}," src/integration-test/groovy\n",[679,30149,30150,30152,30154,30157,30159],{"class":681,"line":959},[679,30151,30073],{"class":880},[679,30153,30076],{"class":931},[679,30155,30156],{"class":689}," ../quartz-2.x/test/integration/",[679,30158,13165],{"class":931},[679,30160,30147],{"class":689},[679,30162,30163],{"class":681,"line":964},[679,30164,889],{"emptyLinePlaceholder":797},[679,30166,30167],{"class":681,"line":977},[679,30168,30169],{"class":1400},"# then templates / other resources\n",[679,30171,30172,30174,30176,30179],{"class":681,"line":982},[679,30173,30073],{"class":880},[679,30175,30076],{"class":931},[679,30177,30178],{"class":689}," ../quartz-2.x/src/templates/",[679,30180,30181],{"class":689}," src/main/templates\n",[651,30183,30184],{},"While the above will work we realized this wasn't going to work for us. Like most developers, we use source control and if we just straight copy the files we would lose all revision history. If you're using Git this is probably going to be a little bit easier than we had it in Subversion. Either way, this is something you need to be thinking of if keeping that history is important to you. You will need to add a package declaration to the plugin descriptor. In this case, QuartzGrailsPlugin is modified as follows:",[669,30186,30188],{"className":4107,"code":30187,"language":4109,"meta":674,"style":674},"// add package declaration\npackage grails.plugins.quartz\nclass QuartzGrailsPlugin {\n}\n",[676,30189,30190,30195,30202,30223],{"__ignoreMap":674},[679,30191,30192],{"class":681,"line":682},[679,30193,30194],{"class":1400},"// add package declaration\n",[679,30196,30197,30199],{"class":681,"line":790},[679,30198,2543],{"class":685},[679,30200,30201],{"class":693}," grails.plugins.quartz\n",[679,30203,30204,30206,30209,30212,30215,30218,30220],{"class":681,"line":892},[679,30205,877],{"class":6561},[679,30207,30208],{"class":6561}," Q",[679,30210,30211],{"class":693},"uartz",[679,30213,30214],{"class":6561},"G",[679,30216,30217],{"class":693},"rails",[679,30219,7387],{"class":6561},[679,30221,30222],{"class":693},"lugin {\n",[679,30224,30225],{"class":681,"line":901},[679,30226,996],{"class":693},[651,30228,30229],{},"The rest of the steps can be found in the Grails Migration guide. Some of them will apply to you and some of them won't. Either way, I would make sure you guy step by step and make sure you do everything that they go through. ",[5909,30231,30233],{"id":30232},"grails-3-application","Grails 3 Application",[651,30235,30236],{},"Now that we have resolved all of our plugins we can move on to migrating our application. Just like the plugins, it is so much easier to create a new Grails 3 application and then move/merge/migrate code over from your Grails 2.x application. Once the plugins are Grails 3.x compatible you can upgrade the application. To upgrade an application it is again best to create a new Grails 3 application using the \"web\" profile:",[669,30238,30240],{"className":5851,"code":30239,"language":5853,"meta":674,"style":674},"$ grails create-app myapp\n$ cd myapp\n",[676,30241,30242,30256],{"__ignoreMap":674},[679,30243,30244,30247,30250,30253],{"class":681,"line":682},[679,30245,30246],{"class":880},"$",[679,30248,30249],{"class":689}," grails",[679,30251,30252],{"class":689}," create-app",[679,30254,30255],{"class":689}," myapp\n",[679,30257,30258,30260,30263],{"class":681,"line":790},[679,30259,30246],{"class":880},[679,30261,30262],{"class":689}," cd",[679,30264,30255],{"class":689},[651,30266,30267],{},"The next step is to copy the sources from the original Grails 2 application to the Grails 3 application:",[669,30269,30271],{"className":5851,"code":30270,"language":5853,"meta":674,"style":674},"# first the sources\ncp -rf ../old_app/src/groovy/ src/main/groovy\ncp -rf ../old_app/src/java/ src/main/groovy\ncp -rf ../old_app/grails-app/ grails-app\n# then the tests\ncp -rf ../old_app/test/unit/ src/test/groovy\nmkdir -p src/integration-test/groovy\ncp -rf ../old_app/test/integration/ src/integration-test/groovy\n",[676,30272,30273,30277,30288,30299,30310,30314,30325,30333],{"__ignoreMap":674},[679,30274,30275],{"class":681,"line":682},[679,30276,30068],{"class":1400},[679,30278,30279,30281,30283,30286],{"class":681,"line":790},[679,30280,30073],{"class":880},[679,30282,30076],{"class":931},[679,30284,30285],{"class":689}," ../old_app/src/groovy/",[679,30287,30082],{"class":689},[679,30289,30290,30292,30294,30297],{"class":681,"line":892},[679,30291,30073],{"class":880},[679,30293,30076],{"class":931},[679,30295,30296],{"class":689}," ../old_app/src/java/",[679,30298,30082],{"class":689},[679,30300,30301,30303,30305,30308],{"class":681,"line":901},[679,30302,30073],{"class":880},[679,30304,30076],{"class":931},[679,30306,30307],{"class":689}," ../old_app/grails-app/",[679,30309,30105],{"class":689},[679,30311,30312],{"class":681,"line":909},[679,30313,30122],{"class":1400},[679,30315,30316,30318,30320,30323],{"class":681,"line":918},[679,30317,30073],{"class":880},[679,30319,30076],{"class":931},[679,30321,30322],{"class":689}," ../old_app/test/unit/",[679,30324,30136],{"class":689},[679,30326,30327,30329,30331],{"class":681,"line":935},[679,30328,30141],{"class":880},[679,30330,30144],{"class":931},[679,30332,30147],{"class":689},[679,30334,30335,30337,30339,30342],{"class":681,"line":944},[679,30336,30073],{"class":880},[679,30338,30076],{"class":931},[679,30340,30341],{"class":689}," ../old_app/test/integration/",[679,30343,30147],{"class":689},[651,30345,30346],{},"_* Again I want to point out that if you are using source control this is a step that you really need to think about. _ As I mentioned earlier one of the best additions to Grails 3 is the addition of Gradle. The repositories and dependencies defined in grails-app/conf/BuildConfig.groovy of the original Grails 2.x application will need to be defined in build.gradle of the new Grails 3.x application. We aren't going to go step by step, that can be found in the official documentation but here are the steps remaining. ",[5316,30348,30349,30352,30355,30358,30361],{},[5332,30350,30351],{},"Modify Package Imports",[5332,30353,30354],{},"Migrate Configuration",[5332,30356,30357],{},"Migrate web.xml modifications to Spring",[5332,30359,30360],{},"Migrate static assets not handled by Asset Pipeline ",[5332,30362,30363],{},"Migrate Tests",[4542,30365,21931],{"id":21930},[651,30367,30368],{},"The Official Grails Migration Guide that I have mentioned at least 10x in this article is the best resource you have. Outside of that I found 2 really great presentations from GR8Conf Europe 2016",[651,30370,30371],{},[812,30372,30373],{"href":30373,"rel":30374},"https://www.youtube.com/watch?v=IhehO9aM5bk",[816],[651,30376,30377,11108],{},[812,30378,30379],{"href":30379,"rel":30380},"https://www.youtube.com/watch?v=Qfrp1cbXdVA&t=814s",[816],[4542,30382,9042],{"id":9041},[651,30384,30385],{},"If you looked at migrating in the past but felt you couldn't because of the plugin landscape I think it is about time you gave it another look. ",[651,30387,13453,30388,30390],{},[2939,30389,11650],{}," If you have already migrated what issues did you come across? If you haven't, what are you waiting for? _",[786,30392,30393],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}",{"title":674,"searchDepth":790,"depth":790,"links":30395},[30396,30399,30403,30404],{"id":29955,"depth":790,"text":29956,"children":30397},[30398],{"id":29971,"depth":892,"text":29972},{"id":29997,"depth":790,"text":29998,"children":30400},[30401,30402],{"id":30036,"depth":892,"text":30037},{"id":30232,"depth":892,"text":30233},{"id":21930,"depth":790,"text":21931},{"id":9041,"depth":790,"text":9042},{"slug":30406,"published":797,"date":30407,"tags":30408,"cover":30409},"migrating-grails-2-x-applications-grails-3-x","2017-06-16T08:53:25-04:00",[848],"./pexels-photo-436784-760x507.jpeg",{"title":642,"description":642},"blog/2017/06/16/migrating-grails-2-x-applications-grails-3-x","nFwBZA9ElMe15bZI_QYKOJMnMc98utgNsBoemcKN6lQ",{"id":30414,"title":639,"body":30415,"description":639,"extension":793,"meta":30533,"navigation":797,"path":640,"seo":30537,"stem":30538,"__hash__":30539},"content/blog/2017/06/19/spring-boot-2-0-m2-now-available.md",{"type":648,"value":30416,"toc":30521},[30417,30420,30424,30443,30448,30451,30455,30458,30462,30465,30469,30472,30476,30479,30483,30486,30490,30493,30495,30502,30509,30512,30515],[651,30418,30419],{},"A few weeks ago we got our first look at the next major version of Spring Boot. We talked about all kinds of exciting things such as a Java 8 baseline, Spring Framework 5 support and the new Gradle plugin. In this article, we take a quick look at the next iteration of Spring Boot 2.0, milestone 2. ",[4542,30421,30423],{"id":30422},"spring-boot-20-m2","Spring Boot 2.0 M2",[651,30425,30426,30427,30431,30432,30437,30438,30442],{},"If you want to read the full release notes you can do so ",[812,30428,18263],{"href":30429,"rel":30430},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M2-Release-Notes",[816],". This release covers over ",[812,30433,30436],{"href":30434,"rel":30435},"https://github.com/spring-projects/spring-boot/milestone/54?closed=1",[816],"90 issues and"," pull requests. If you want to test out any of the new features you can start a new project using ",[812,30439,30440],{"href":30440,"rel":30441},"http://start.spring.io",[816]," or by firing up my favorite IDE, IntelliJ. ",[651,30444,30445],{},[660,30446],{"alt":30423,"src":30447},"./2017-06-16_21-27-09-1024x645.png",[651,30449,30450],{},"We won't go through all of the features here but I will mention a few of them. ",[5909,30452,30454],{"id":30453},"quartz-scheduler","Quartz Scheduler",[651,30456,30457],{},"Spring Boot 2 provides support for the Quartz scheduler that can be used via the spring-boot-starter-quartz dedicated starter. Both in-memory and jdbc stores can be configured.",[5909,30459,30461],{"id":30460},"spring-data-web-configuration","Spring Data Web configuration",[651,30463,30464],{},"Spring Boot exposes a new spring.data.web configuration namespace that allows to easily configure paging and sorting.",[5909,30466,30468],{"id":30467},"json-starter","JSON starter",[651,30470,30471],{},"A new spring-boot-starter-json starter gathers the necessary bits to read and write json. It provides not only jackson-databind but also useful modules when working with Java8: jackson-datatype-jdk8, jackson-datatype-jsr310 and jackson-module-parameter-names. This new starter is now used where jackson-databind was previously defined.",[5909,30473,30475],{"id":30474},"thymeleaf-starter","Thymeleaf starter",[651,30477,30478],{},"The Thymeleaf starter now includes thymeleaf-extras-java8time out of the box. ",[5909,30480,30482],{"id":30481},"jdbctemplate","JdbcTemplate",[651,30484,30485],{},"The JdbcTemplate that Spring Boot auto-configures can now be customized via the spring.jdbc.template namespace. Also, the NamedParameterJdbcTemplate that is auto-configured reuses the JdbcTemplate behind the scenes.",[5909,30487,30489],{"id":30488},"jooq","jOOQ",[651,30491,30492],{},"Spring Boot detects the jOOQ dialect automatically based on the DataSource (similarly to what is done for the JPA dialect). Also a @JooqTest has been introduced to ease testing where only jOOQ has to be used. ",[4542,30494,24432],{"id":18339},[651,30496,24435,30497,30501],{},[812,30498,18347],{"href":30499,"rel":30500},"http://courses.www.danvega.dev/p/spring-boot-intro",[816]," please do. This course is based on Spring Boot 1.3 and was very much an introduction. I am already planning a curriculum for a Spring 2.0 course is coming and I will be releasing it sometime around SpringOne. ",[651,30503,18356,30504,30508],{},[812,30505,18361],{"href":30506,"rel":30507},"https://www.danvega.dev/spring-boot-2-0",[816]," and signup for updates. Anyone on this list will be the first to find out when it’s released and will receive a discount. ",[4542,30510,30511],{"id":9041},"CONCLUSION",[651,30513,30514],{},"We are starting to get a real good look at the next version of Spring Boot. I don't know about you but I am looking forward to everything Spring Framework 5 has to offer. ",[651,30516,30517],{},[7300,30518,30519,24461],{},[2939,30520,11650],{},{"title":674,"searchDepth":790,"depth":790,"links":30522},[30523,30531,30532],{"id":30422,"depth":790,"text":30423,"children":30524},[30525,30526,30527,30528,30529,30530],{"id":30453,"depth":892,"text":30454},{"id":30460,"depth":892,"text":30461},{"id":30467,"depth":892,"text":30468},{"id":30474,"depth":892,"text":30475},{"id":30481,"depth":892,"text":30482},{"id":30488,"depth":892,"text":30489},{"id":18339,"depth":790,"text":24432},{"id":9041,"depth":790,"text":30511},{"slug":30534,"published":797,"date":30535,"tags":30536,"cover":24506},"spring-boot-2-0-m2-now-available","2017-06-19T08:30:53-04:00",[7055],{"title":639,"description":639},"blog/2017/06/19/spring-boot-2-0-m2-now-available","ESGu5aCHl1E0v7rjTqJeIiI-xtbOPfAJ-Wg0E2wDEFI",{"id":30541,"title":636,"body":30542,"description":636,"extension":793,"meta":30816,"navigation":797,"path":637,"seo":30821,"stem":30822,"__hash__":30823},"content/blog/2017/06/21/spring-boot-properties-setting-locale.md",{"type":648,"value":30543,"toc":30810},[30544,30552,30562,30566,30569,30582,30585,30591,30594,30707,30714,30719,30723,30741,30769,30772,30777,30789,30793,30796,30802,30804,30807],[651,30545,30546,30547,30551],{},"In today's article, we are going to look at a question from a student in my ",[812,30548,18347],{"href":30549,"rel":30550},"https://www.danvega.dev/spring-boot",[816],". This question has to do with Internationalization and more specifically how to set the locale in the properties file of a Spring Boot application. This question comes in from Mykhaylo. ",[1004,30553,30554,30557,30561],{},[651,30555,30556],{},"Hi, I've put in my property file spring.mvc.locale=fr , but it is still messages from default messages.properties file that are displayed. If I display a value of current locale I see that this is en_GB, and not French: ",[651,30558,30560],{"th:text":30559},"${#locale}","....."," What is the way to set up a locale in SpringBoot?",[4542,30563,30565],{"id":30564},"setting-the-locale","Setting the locale",[651,30567,30568],{},"Before we dive into the solution let's take a look at what the student was doing here. If you want to override the locale and give it a fixed value you can do so by setting the following property in your application.properties. ",[669,30570,30572],{"className":5851,"code":30571,"language":5853,"meta":674,"style":674},"spring.mvc.locale=fr_FR\n",[676,30573,30574],{"__ignoreMap":674},[679,30575,30576,30579],{"class":681,"line":682},[679,30577,30578],{"class":880},"spring.mvc.locale",[679,30580,30581],{"class":689},"=fr_FR\n",[651,30583,30584],{},"In this example, we are setting the locale to French. If you aren't sure of the different locale settings, don't worry. IntelliJ will provide intelli-sense here and give you a list of choices to choose from.",[651,30586,30587],{},[660,30588],{"alt":30589,"src":30590},"Spring Boot Locale","./2017-06-21_07-58-13.png",[651,30592,30593],{},"With that in place, I went ahead and created a quick example. ",[669,30595,30597],{"className":4107,"code":30596,"language":4109,"meta":674,"style":674},"package com.therealdanvega.controller;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.Locale;\n\n@RestController\npublic class HomeController {\n\n @RequestMapping(\"/\")\n public String home(Locale locale) {\n return locale.toString();\n }\n\n}\n",[676,30598,30599,30605,30609,30615,30621,30625,30632,30636,30642,30652,30656,30668,30684,30695,30699,30703],{"__ignoreMap":674},[679,30600,30601,30603],{"class":681,"line":682},[679,30602,2543],{"class":685},[679,30604,12409],{"class":693},[679,30606,30607],{"class":681,"line":790},[679,30608,889],{"emptyLinePlaceholder":797},[679,30610,30611,30613],{"class":681,"line":892},[679,30612,1999],{"class":685},[679,30614,12545],{"class":693},[679,30616,30617,30619],{"class":681,"line":901},[679,30618,1999],{"class":685},[679,30620,12552],{"class":693},[679,30622,30623],{"class":681,"line":909},[679,30624,889],{"emptyLinePlaceholder":797},[679,30626,30627,30629],{"class":681,"line":918},[679,30628,1999],{"class":685},[679,30630,30631],{"class":693}," java.util.Locale;\n",[679,30633,30634],{"class":681,"line":935},[679,30635,889],{"emptyLinePlaceholder":797},[679,30637,30638,30640],{"class":681,"line":944},[679,30639,4116],{"class":693},[679,30641,9212],{"class":685},[679,30643,30644,30646,30648,30650],{"class":681,"line":959},[679,30645,6073],{"class":685},[679,30647,4512],{"class":685},[679,30649,18716],{"class":880},[679,30651,884],{"class":693},[679,30653,30654],{"class":681,"line":964},[679,30655,889],{"emptyLinePlaceholder":797},[679,30657,30658,30660,30662,30664,30666],{"class":681,"line":977},[679,30659,6872],{"class":693},[679,30661,9275],{"class":685},[679,30663,745],{"class":693},[679,30665,10032],{"class":689},[679,30667,1339],{"class":693},[679,30669,30670,30672,30674,30676,30679,30682],{"class":681,"line":982},[679,30671,6089],{"class":685},[679,30673,9289],{"class":693},[679,30675,12642],{"class":880},[679,30677,30678],{"class":693},"(Locale ",[679,30680,30681],{"class":2099},"locale",[679,30683,4390],{"class":693},[679,30685,30686,30688,30691,30693],{"class":681,"line":988},[679,30687,9444],{"class":685},[679,30689,30690],{"class":693}," locale.",[679,30692,14391],{"class":880},[679,30694,9317],{"class":693},[679,30696,30697],{"class":681,"line":993},[679,30698,985],{"class":693},[679,30700,30701],{"class":681,"line":2129},[679,30702,889],{"emptyLinePlaceholder":797},[679,30704,30705],{"class":681,"line":2140},[679,30706,996],{"class":693},[651,30708,30709,30710,30713],{},"I placed a debug marker on line 13, ran the application and visited ",[812,30711,11697],{"href":11697,"rel":30712},[816],". To my surprise, the locale was still what I would expect mine to show up as, en_US. ",[651,30715,30716],{},[660,30717],{"alt":30589,"src":30718},"./2017-06-21_08-04-10.png",[4542,30720,30722],{"id":30721},"locale-resolver","Locale Resolver",[651,30724,30725,30726,30731,30732,30740],{},"This is actually expected behavior and it has to do with something called the ",[812,30727,30730],{"href":30728,"rel":30729},"http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/LocaleResolver.html",[816],"LocaleResolver",". This interface allows for implementations based on the request, session, cookies, etc. The default implementation is,",[812,30733,13517,30737,13517],{"href":30734,"rel":30735,"title":30736},"http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.html",[816],"class in org.springframework.web.servlet.i18n",[676,30738,30739],{},"AcceptHeaderLocaleResolver"," simply using the request's locale provided by the respective HTTP header. This means that no matter what you set the locale to Spring is always going to look at the HTTP request header to determine the locale. Luckily for us we can override the LocaleResolver using another property in Spring Boot. ",[669,30742,30744],{"className":4107,"code":30743,"language":4109,"meta":674,"style":674},"spring.mvc.locale=fr_FR\nspring.mvc.locale-resolver=fixed\n",[676,30745,30746,30755],{"__ignoreMap":674},[679,30747,30748,30750,30752],{"class":681,"line":682},[679,30749,30578],{"class":693},[679,30751,686],{"class":685},[679,30753,30754],{"class":693},"fr_FR\n",[679,30756,30757,30759,30761,30764,30766],{"class":681,"line":790},[679,30758,30578],{"class":693},[679,30760,6453],{"class":685},[679,30762,30763],{"class":693},"resolver",[679,30765,686],{"class":685},[679,30767,30768],{"class":693},"fixed\n",[651,30770,30771],{},"With this setting in place, our application should now use the locale that you set in your properties file. ",[651,30773,30774],{},[660,30775],{"alt":30589,"src":30776},"./2017-06-21_08-12-21.png",[651,30778,30779,30782,30783,30788],{},[7300,30780,30781],{},"If you think you had this working at one point in your application you"," aren't_ crazy. This worked in Spring Boot 1.3.7 but then in 1.4 ",[812,30784,30787],{"href":30785,"rel":30786},"https://github.com/spring-projects/spring-boot/commit/d54474b81cd7e1ae08d1e7200da725df2fd23e13",[816],"there was a change"," and if you want it to work going forward you must set the locale resolver. _",[4542,30790,30792],{"id":30791},"setting-the-locale-screencast","Setting the locale Screencast",[651,30794,30795],{},"I created a quick screencast to walk through setting the locale in your Spring Boot application. Please subscribe to my YouTube channel to get notifications about new screencasts. ",[651,30797,30798],{},[812,30799,30800],{"href":30800,"rel":30801},"https://www.youtube.com/watch?v=nt27SPF10vY",[816],[4542,30803,9042],{"id":9041},[651,30805,30806],{},"I want to thank Mykhaylo again for the question. This is one of those things that has probably tripped most of us at some point.",[786,30808,30809],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":30811},[30812,30813,30814,30815],{"id":30564,"depth":790,"text":30565},{"id":30721,"depth":790,"text":30722},{"id":30791,"depth":790,"text":30792},{"id":9041,"depth":790,"text":9042},{"slug":30817,"published":797,"date":30818,"tags":30819,"cover":30820},"spring-boot-properties-setting-locale","2017-06-21T08:19:09-04:00",[7055],"./andre-benz-256762-760x507.jpg",{"title":636,"description":636},"blog/2017/06/21/spring-boot-properties-setting-locale","rv11927AIcjVV-9Ugo6FGh_QuluBs7xTnN7-mIWuW3M",{"id":30825,"title":633,"body":30826,"description":633,"extension":793,"meta":31230,"navigation":797,"path":634,"seo":31235,"stem":31236,"__hash__":31237},"content/blog/2017/06/23/building-angular-spring-boot.md",{"type":648,"value":30827,"toc":31224},[30828,30837,30841,30844,30851,30854,30858,30861,30947,30950,30964,30967,30982,30990,30996,30999,31014,31027,31042,31045,31154,31157,31169,31172,31189,31192,31198,31205,31207,31216,31221],[651,30829,30830,30831,30836],{},"I have been working on a ton of different Angular projects lately and some of them are going to be included in my ",[812,30832,30835],{"href":30833,"rel":30834},"https://www.danvega.dev/jhipster",[816],"Angular 4 Java Developers Course",". There might be times when you have one team working on the front end (Angular) and another working on the back end. There are certainly times when it makes sense to separate the tasks for these applications but there are also times when you as a developer wear many hats. Today we are going talk about the scenario where you are working on both the Angular & Spring Boot application side by side. ",[4542,30838,30840],{"id":30839},"angular-spring-boot-application","Angular & Spring Boot Application",[651,30842,30843],{},"If you haven't already checked out the tutorial below please check it out. In this tutorial, I walk you through a quick application that uses Angular and Spring Boot in the same project. ",[651,30845,30846],{},[812,30847,30850],{"href":30848,"rel":30849},"https://www.youtube.com/watch?v=v7X%5C_ZHdcNvc&t=5s",[816],"https://www.youtube.com/watch?v=v7X\\_ZHdcNvc&t=5s",[651,30852,30853],{},"I had someone that watched this video and reached out to me asking how the build process works. So this is a good time to talk through how the build works if you're working on both applications at once.",[5909,30855,30857],{"id":30856},"building-angular","Building Angular",[651,30859,30860],{},"If you saw towards the end of the tutorial we were building the Angular application so that it would place all of the files in the /src/main/resources directory. I want to dive into that process a little further and explain how we make that happen. If you look at the package.json file inside of your Angular application you will see a scripts block that looks something like this. ",[669,30862,30864],{"className":28439,"code":30863,"language":28441,"meta":674,"style":674}," \"scripts\": {\n \"ng\": \"ng\",\n \"start\": \"ng serve --proxy-config proxy-conf.json\",\n \"build\": \"ng build\",\n \"test\": \"ng test\",\n \"lint\": \"ng lint\",\n \"e2e\": \"ng e2e\"\n },\n",[676,30865,30866,30873,30885,30897,30909,30921,30933,30943],{"__ignoreMap":674},[679,30867,30868,30871],{"class":681,"line":682},[679,30869,30870],{"class":689}," \"scripts\"",[679,30872,28468],{"class":693},[679,30874,30875,30878,30880,30883],{"class":681,"line":790},[679,30876,30877],{"class":931}," \"ng\"",[679,30879,4282],{"class":693},[679,30881,30882],{"class":689},"\"ng\"",[679,30884,12083],{"class":693},[679,30886,30887,30890,30892,30895],{"class":681,"line":892},[679,30888,30889],{"class":931}," \"start\"",[679,30891,4282],{"class":693},[679,30893,30894],{"class":689},"\"ng serve --proxy-config proxy-conf.json\"",[679,30896,12083],{"class":693},[679,30898,30899,30902,30904,30907],{"class":681,"line":901},[679,30900,30901],{"class":931}," \"build\"",[679,30903,4282],{"class":693},[679,30905,30906],{"class":689},"\"ng build\"",[679,30908,12083],{"class":693},[679,30910,30911,30914,30916,30919],{"class":681,"line":909},[679,30912,30913],{"class":931}," \"test\"",[679,30915,4282],{"class":693},[679,30917,30918],{"class":689},"\"ng test\"",[679,30920,12083],{"class":693},[679,30922,30923,30926,30928,30931],{"class":681,"line":918},[679,30924,30925],{"class":931}," \"lint\"",[679,30927,4282],{"class":693},[679,30929,30930],{"class":689},"\"ng lint\"",[679,30932,12083],{"class":693},[679,30934,30935,30938,30940],{"class":681,"line":935},[679,30936,30937],{"class":931}," \"e2e\"",[679,30939,4282],{"class":693},[679,30941,30942],{"class":689},"\"ng e2e\"\n",[679,30944,30945],{"class":681,"line":944},[679,30946,28483],{"class":693},[651,30948,30949],{},"This is almost what your scripts section will look like and if we wanted to build the project we could run the following command. ",[669,30951,30953],{"className":5851,"code":30952,"language":5853,"meta":674,"style":674},"npm run build\n",[676,30954,30955],{"__ignoreMap":674},[679,30956,30957,30959,30961],{"class":681,"line":682},[679,30958,24568],{"class":880},[679,30960,16486],{"class":689},[679,30962,30963],{"class":689}," build\n",[651,30965,30966],{},"What we want to do is build upon that (pun intended) and to do so we are going to add a couple more scripts. When a build is called a script called post build will be called automatically. In our post-build script, we are going to reference another script. ",[669,30968,30970],{"className":28439,"code":30969,"language":28441,"meta":674,"style":674},"\"postbuild\": \"npm run deploy\"\n",[676,30971,30972],{"__ignoreMap":674},[679,30973,30974,30977,30979],{"class":681,"line":682},[679,30975,30976],{"class":689},"\"postbuild\"",[679,30978,4282],{"class":693},[679,30980,30981],{"class":689},"\"npm run deploy\"\n",[651,30983,30984,30985,15266],{},"With that in place, we need to create a deploy script and have it do something. What we want to do is copy all of the files from the result of the Angular (ng) build process to our /resources/static directory. To do so we need some functionality to copy those files. We could write that ourselves or we could use a package that already exists on npm called ",[812,30986,30989],{"href":30987,"rel":30988},"https://www.npmjs.com/package/copyfiles",[816],"copyfiles",[651,30991,30992],{},[660,30993],{"alt":30994,"src":30995},"npm copy files","./2017-06-22_15-50-54.png",[651,30997,30998],{},"You can install it using the instructions above or in my case I just used \"npm install copyfiles --save-dev\". With that in place, we can now write our deploy script. ",[669,31000,31002],{"className":28439,"code":31001,"language":28441,"meta":674,"style":674},"\"deploy\": \"copyfiles -f dist/** ../resources/static\"\n",[676,31003,31004],{"__ignoreMap":674},[679,31005,31006,31009,31011],{"class":681,"line":682},[679,31007,31008],{"class":689},"\"deploy\"",[679,31010,4282],{"class":693},[679,31012,31013],{"class":689},"\"copyfiles -f dist/** ../resources/static\"\n",[651,31015,31016,31017,17861,31022,15266],{},"This is great and will work the first time but what about subsequent builds? We need to make sure that the resources/static directory is clean for us to run a proper build & deploy. Just as with post build we can run a script prior to another one running. In this case, I am going to run a pre-deploy script that removes that directory and then creates it. We are going to use another two packages from npm called ",[812,31018,31021],{"href":31019,"rel":31020},"https://www.npmjs.com/package/rimraf",[816],"rimraf",[812,31023,31026],{"href":31024,"rel":31025},"https://www.npmjs.com/package/mkdirp",[816],"mkdirp",[669,31028,31030],{"className":28439,"code":31029,"language":28441,"meta":674,"style":674},"\"predeploy\": \"rimraf ../resources/static/ && mkdirp ../resources/static\"\n",[676,31031,31032],{"__ignoreMap":674},[679,31033,31034,31037,31039],{"class":681,"line":682},[679,31035,31036],{"class":689},"\"predeploy\"",[679,31038,4282],{"class":693},[679,31040,31041],{"class":689},"\"rimraf ../resources/static/ && mkdirp ../resources/static\"\n",[651,31043,31044],{},"Now your scripts should look something like this",[669,31046,31048],{"className":28439,"code":31047,"language":28441,"meta":674,"style":674}," \"scripts\": {\n \"ng\": \"ng\",\n \"start\": \"ng serve --proxy-config proxy-conf.json\",\n \"build\": \"ng build\",\n \"postbuild\": \"npm run deploy\",\n \"predeploy\": \"rimraf ../resources/static/ && mkdirp ../resources/static\",\n \"deploy\": \"copyfiles -f dist/** ../resources/static\",\n \"test\": \"ng test\",\n \"lint\": \"ng lint\",\n \"e2e\": \"ng e2e\"\n },\n",[676,31049,31050,31056,31066,31076,31086,31098,31110,31122,31132,31142,31150],{"__ignoreMap":674},[679,31051,31052,31054],{"class":681,"line":682},[679,31053,30870],{"class":689},[679,31055,28468],{"class":693},[679,31057,31058,31060,31062,31064],{"class":681,"line":790},[679,31059,30877],{"class":931},[679,31061,4282],{"class":693},[679,31063,30882],{"class":689},[679,31065,12083],{"class":693},[679,31067,31068,31070,31072,31074],{"class":681,"line":892},[679,31069,30889],{"class":931},[679,31071,4282],{"class":693},[679,31073,30894],{"class":689},[679,31075,12083],{"class":693},[679,31077,31078,31080,31082,31084],{"class":681,"line":901},[679,31079,30901],{"class":931},[679,31081,4282],{"class":693},[679,31083,30906],{"class":689},[679,31085,12083],{"class":693},[679,31087,31088,31091,31093,31096],{"class":681,"line":909},[679,31089,31090],{"class":931}," \"postbuild\"",[679,31092,4282],{"class":693},[679,31094,31095],{"class":689},"\"npm run deploy\"",[679,31097,12083],{"class":693},[679,31099,31100,31103,31105,31108],{"class":681,"line":918},[679,31101,31102],{"class":931}," \"predeploy\"",[679,31104,4282],{"class":693},[679,31106,31107],{"class":689},"\"rimraf ../resources/static/ && mkdirp ../resources/static\"",[679,31109,12083],{"class":693},[679,31111,31112,31115,31117,31120],{"class":681,"line":935},[679,31113,31114],{"class":931}," \"deploy\"",[679,31116,4282],{"class":693},[679,31118,31119],{"class":689},"\"copyfiles -f dist/** ../resources/static\"",[679,31121,12083],{"class":693},[679,31123,31124,31126,31128,31130],{"class":681,"line":944},[679,31125,30913],{"class":931},[679,31127,4282],{"class":693},[679,31129,30918],{"class":689},[679,31131,12083],{"class":693},[679,31133,31134,31136,31138,31140],{"class":681,"line":959},[679,31135,30925],{"class":931},[679,31137,4282],{"class":693},[679,31139,30930],{"class":689},[679,31141,12083],{"class":693},[679,31143,31144,31146,31148],{"class":681,"line":964},[679,31145,30937],{"class":931},[679,31147,4282],{"class":693},[679,31149,30942],{"class":689},[679,31151,31152],{"class":681,"line":977},[679,31153,28483],{"class":693},[651,31155,31156],{},"When you run a build simply use the following command and this will run build, postbuild, predeploy and deploy in that order. ",[669,31158,31159],{"className":5851,"code":30952,"language":5853,"meta":674,"style":674},[676,31160,31161],{"__ignoreMap":674},[679,31162,31163,31165,31167],{"class":681,"line":682},[679,31164,24568],{"class":880},[679,31166,16486],{"class":689},[679,31168,30963],{"class":689},[651,31170,31171],{},"This will perform the following steps",[5316,31173,31174,31180,31183,31186],{},[5332,31175,31176,31177,31179],{},"Runs ",[7300,31178,28435],{}," to build your angular app which among many things will bundle your assets. ",[5332,31181,31182],{},"Deletes out the /src/main/resources/static directory ",[5332,31184,31185],{},"Creates /src/main/resources/static directory ",[5332,31187,31188],{},"Copy all the files in the dist/ folder (result of ng build) to the /src/main/resources/static directory",[651,31190,31191],{},"All the files need will now be in the /static folder allowing you to run the Spring Boot Application.",[651,31193,31194],{},[660,31195],{"alt":31196,"src":31197},"Adding Angular Dist","./2017-06-22_16-02-14.png",[651,31199,31200,31201,31204],{},"When you visit ",[812,31202,11697],{"href":11697,"rel":31203},[816]," the index.html file of the static folder is served and your Angular application should run as intended. ",[4542,31206,9042],{"id":9041},[651,31208,31209,31210,31215],{},"I just want to give a huge shoutout to ",[812,31211,31214],{"href":31212,"rel":31213},"http://amzn.to/2tTMa5D",[816],"Yakov Fain, the author of Angular 2 Development with TypeScript",". I picked up this little tip from him and can't recommend his book enough. I own that book and LOVE it. ",[651,31217,13453,31218,31220],{},[2939,31219,11650],{}," Are you facing any issues developing Angular & Spring Boot applications? _",[786,31222,31223],{},"html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}",{"title":674,"searchDepth":790,"depth":790,"links":31225},[31226,31229],{"id":30839,"depth":790,"text":30840,"children":31227},[31228],{"id":30856,"depth":892,"text":30857},{"id":9041,"depth":790,"text":9042},{"slug":31231,"published":797,"date":31232,"tags":31233,"cover":31234},"building-angular-spring-boot","2017-06-23T08:00:27-04:00",[17866,7055],"./imac-apple-mockup-app-38544-760x505.jpeg",{"title":633,"description":633},"blog/2017/06/23/building-angular-spring-boot","EJlhiVQAqaOXV3Wfwcq9WGHYkHAfJMwMcDNEkZjtf6U",{"id":31239,"title":630,"body":31240,"description":630,"extension":793,"meta":31898,"navigation":797,"path":631,"seo":31902,"stem":31903,"__hash__":31904},"content/blog/2017/06/26/spring-boot-configuration-using-yaml.md",{"type":648,"value":31241,"toc":31890},[31242,31250,31255,31310,31477,31481,31484,31547,31550,31594,31598,31601,31606,31610,31613,31702,31711,31864,31867,31871,31877,31879,31882,31887],[651,31243,31244,31245,31249],{},"In this tutorial, we are going to look at a question from a student in my ",[812,31246,31248],{"href":30549,"rel":31247},[816],"Spring Boot Introduction"," course. This question had to do with using a YAML file for configuration and the best way about how to inject a single property for multiple environments. Let's take a look at the question and then we will talk through the solution. ",[1004,31251,31252],{},[651,31253,31254],{},"Hi! How can I access these properties from a single yml properties file in the DataSourceConfig? Thanks.",[669,31256,31258],{"className":5851,"code":31257,"language":5853,"meta":674,"style":674},"environments:\n development:\n name: Development setup\n url: http://dev.bar.com\n production:\n name: Production setup\n url: http://prod.bar.com\n",[676,31259,31260,31265,31270,31281,31289,31294,31303],{"__ignoreMap":674},[679,31261,31262],{"class":681,"line":682},[679,31263,31264],{"class":880},"environments:\n",[679,31266,31267],{"class":681,"line":790},[679,31268,31269],{"class":880}," development:\n",[679,31271,31272,31275,31278],{"class":681,"line":892},[679,31273,31274],{"class":880}," name:",[679,31276,31277],{"class":689}," Development",[679,31279,31280],{"class":689}," setup\n",[679,31282,31283,31286],{"class":681,"line":901},[679,31284,31285],{"class":880}," url:",[679,31287,31288],{"class":689}," http://dev.bar.com\n",[679,31290,31291],{"class":681,"line":909},[679,31292,31293],{"class":880}," production:\n",[679,31295,31296,31298,31301],{"class":681,"line":918},[679,31297,31274],{"class":880},[679,31299,31300],{"class":689}," Production",[679,31302,31280],{"class":689},[679,31304,31305,31307],{"class":681,"line":935},[679,31306,31285],{"class":880},[679,31308,31309],{"class":689}," http://prod.bar.com\n",[669,31311,31313],{"className":4107,"code":31312,"language":4109,"meta":674,"style":674},"@Configuration\npublic class DataSourceConfig\n{\n String url;\n\n @Bean(name=\"datasource\")\n @Profile(\"development\")\n DataSource development()\n {\n return new DataSource(url, 9999);\n }\n\n @Bean(name=\"datasource\")\n @Profile(\"production\")\n DataSource production()\n {\n return new DataSource(url, 9999);\n }\n}\n",[676,31314,31315,31321,31330,31334,31339,31343,31360,31374,31384,31388,31405,31409,31413,31429,31442,31451,31455,31469,31473],{"__ignoreMap":674},[679,31316,31317,31319],{"class":681,"line":682},[679,31318,4116],{"class":693},[679,31320,6212],{"class":685},[679,31322,31323,31325,31327],{"class":681,"line":790},[679,31324,6073],{"class":685},[679,31326,4512],{"class":685},[679,31328,31329],{"class":880}," DataSourceConfig\n",[679,31331,31332],{"class":681,"line":892},[679,31333,28448],{"class":693},[679,31335,31336],{"class":681,"line":901},[679,31337,31338],{"class":693}," String url;\n",[679,31340,31341],{"class":681,"line":909},[679,31342,889],{"emptyLinePlaceholder":797},[679,31344,31345,31347,31349,31351,31353,31355,31358],{"class":681,"line":918},[679,31346,6872],{"class":693},[679,31348,6433],{"class":685},[679,31350,745],{"class":693},[679,31352,16334],{"class":931},[679,31354,686],{"class":685},[679,31356,31357],{"class":689},"\"datasource\"",[679,31359,1339],{"class":693},[679,31361,31362,31364,31367,31369,31372],{"class":681,"line":935},[679,31363,6872],{"class":693},[679,31365,31366],{"class":685},"Profile",[679,31368,745],{"class":693},[679,31370,31371],{"class":689},"\"development\"",[679,31373,1339],{"class":693},[679,31375,31376,31379,31382],{"class":681,"line":944},[679,31377,31378],{"class":693}," DataSource ",[679,31380,31381],{"class":880},"development",[679,31383,17545],{"class":693},[679,31385,31386],{"class":681,"line":959},[679,31387,28496],{"class":693},[679,31389,31390,31392,31394,31397,31400,31403],{"class":681,"line":964},[679,31391,9444],{"class":685},[679,31393,2054],{"class":685},[679,31395,31396],{"class":880}," DataSource",[679,31398,31399],{"class":693},"(url, ",[679,31401,31402],{"class":931},"9999",[679,31404,1208],{"class":693},[679,31406,31407],{"class":681,"line":977},[679,31408,985],{"class":693},[679,31410,31411],{"class":681,"line":982},[679,31412,889],{"emptyLinePlaceholder":797},[679,31414,31415,31417,31419,31421,31423,31425,31427],{"class":681,"line":988},[679,31416,6872],{"class":693},[679,31418,6433],{"class":685},[679,31420,745],{"class":693},[679,31422,16334],{"class":931},[679,31424,686],{"class":685},[679,31426,31357],{"class":689},[679,31428,1339],{"class":693},[679,31430,31431,31433,31435,31437,31440],{"class":681,"line":993},[679,31432,6872],{"class":693},[679,31434,31366],{"class":685},[679,31436,745],{"class":693},[679,31438,31439],{"class":689},"\"production\"",[679,31441,1339],{"class":693},[679,31443,31444,31446,31449],{"class":681,"line":2129},[679,31445,31378],{"class":693},[679,31447,31448],{"class":880},"production",[679,31450,17545],{"class":693},[679,31452,31453],{"class":681,"line":2140},[679,31454,28496],{"class":693},[679,31456,31457,31459,31461,31463,31465,31467],{"class":681,"line":2145},[679,31458,9444],{"class":685},[679,31460,2054],{"class":685},[679,31462,31396],{"class":880},[679,31464,31399],{"class":693},[679,31466,31402],{"class":931},[679,31468,1208],{"class":693},[679,31470,31471],{"class":681,"line":2154},[679,31472,985],{"class":693},[679,31474,31475],{"class":681,"line":2159},[679,31476,996],{"class":693},[4542,31478,31480],{"id":31479},"yaml-configuration","YAML Configuration",[651,31482,31483],{},"If you haven't heard of YAML before Wikipedia's definition of it is as follows",[1004,31485,31486],{},[651,31487,31488,4193,31491,31522,31523,31526,31527,31533,31539,31540,31546],{},[2939,31489,31490],{},"YAML",[679,31492,31494],{"style":31493},"nowrap",[679,31495,31497],{"style":31496},"IPA nopopups noexcerpt",[812,31498,4408,31502,31506,31510,31514,31518,4408],{"href":31499,"rel":31500,"title":31501},"https://en.wikipedia.org/wiki/Help:IPA_for_English",[816],"Help:IPA for English",[679,31503,31505],{"style":31504},"/ˈ/ primary stress follows","ˈ",[679,31507,31509],{"style":31508},"/j/ 'y' in 'yes'","j",[679,31511,31513],{"style":31512},"/æ/ short 'a' in 'bad'","æ",[679,31515,31517],{"style":31516},"'m' in 'my'","m",[679,31519,31521],{"style":31520},"/əl/ 'le' in 'bottle'","əl",", rhymes with ",[7300,31524,31525],{},"mammal",") is a ",[812,31528,31532],{"href":31529,"rel":31530,"title":31531},"https://en.wikipedia.org/wiki/Human-readable",[816],"Human-readable","human-readable",[812,31534,31538],{"href":31535,"rel":31536,"title":31537},"https://en.wikipedia.org/wiki/Data_serialization_language",[816],"Data serialization language","data serialization language",". It is commonly used for ",[812,31541,31545],{"href":31542,"rel":31543,"title":31544},"https://en.wikipedia.org/wiki/Configuration_file",[816],"Configuration file","configuration files",", but could be used in many applications where data is being stored (e.g. debugging output) or transmitted (e.g. document headers).",[651,31548,31549],{},"We can use it in place of or right along side of the application.properties. It really just comes down to a preference but if you have a ton of configuration it is usually easier to read YAML configuration in my opinion. If we take a look back our student had a YAML configuration file that looked like this. ",[669,31551,31552],{"className":5851,"code":31257,"language":5853,"meta":674,"style":674},[676,31553,31554,31558,31562,31570,31576,31580,31588],{"__ignoreMap":674},[679,31555,31556],{"class":681,"line":682},[679,31557,31264],{"class":880},[679,31559,31560],{"class":681,"line":790},[679,31561,31269],{"class":880},[679,31563,31564,31566,31568],{"class":681,"line":892},[679,31565,31274],{"class":880},[679,31567,31277],{"class":689},[679,31569,31280],{"class":689},[679,31571,31572,31574],{"class":681,"line":901},[679,31573,31285],{"class":880},[679,31575,31288],{"class":689},[679,31577,31578],{"class":681,"line":909},[679,31579,31293],{"class":880},[679,31581,31582,31584,31586],{"class":681,"line":918},[679,31583,31274],{"class":880},[679,31585,31300],{"class":689},[679,31587,31280],{"class":689},[679,31589,31590,31592],{"class":681,"line":935},[679,31591,31285],{"class":880},[679,31593,31309],{"class":689},[5909,31595,31597],{"id":31596},"profile-specific-configuration","Profile Specific Configuration",[651,31599,31600],{},"The first thing I will say about this is that I usually break out different environment configurations into separate configuration files. You can do this with both .properties or .yml configuration using a -environment name on the file. If we had a development, test and production environment we would have 3 files in src/main/resources that looked like this. ",[651,31602,31603],{},[660,31604],{"alt":31597,"src":31605},"./2017-06-27_12-38-38.png",[5909,31607,31609],{"id":31608},"one-config-to-rule-them-all","One Config to rule them all",[651,31611,31612],{},"That is what I would do but if you want to keep them all in one file you most certainly can do so. If we had them all in a single file we can use the 3 dashes and treat them as separate configurations. In this configuration, I am setting the active profile to the development profile and set up two different profiles. ",[669,31614,31616],{"className":5851,"code":31615,"language":5853,"meta":674,"style":674},"spring:\n profiles:.active: development\n---\nspring:\n profiles: development\ndatasource:\n name: Development Setup\n url: http://dev.bar.com\n---\nspring:\n profiles: production\ndatasource:\n name: Production Setup\n url: http://prod.bar.com\n\n",[676,31617,31618,31623,31631,31636,31640,31647,31652,31662,31669,31673,31677,31684,31688,31696],{"__ignoreMap":674},[679,31619,31620],{"class":681,"line":682},[679,31621,31622],{"class":880},"spring:\n",[679,31624,31625,31628],{"class":681,"line":790},[679,31626,31627],{"class":880}," profiles:.active:",[679,31629,31630],{"class":689}," development\n",[679,31632,31633],{"class":681,"line":892},[679,31634,31635],{"class":880},"---\n",[679,31637,31638],{"class":681,"line":901},[679,31639,31622],{"class":880},[679,31641,31642,31645],{"class":681,"line":909},[679,31643,31644],{"class":880}," profiles:",[679,31646,31630],{"class":689},[679,31648,31649],{"class":681,"line":918},[679,31650,31651],{"class":880},"datasource:\n",[679,31653,31654,31657,31659],{"class":681,"line":935},[679,31655,31656],{"class":880}," name:",[679,31658,31277],{"class":689},[679,31660,31661],{"class":689}," Setup\n",[679,31663,31664,31667],{"class":681,"line":944},[679,31665,31666],{"class":880}," url:",[679,31668,31288],{"class":689},[679,31670,31671],{"class":681,"line":959},[679,31672,31635],{"class":880},[679,31674,31675],{"class":681,"line":964},[679,31676,31622],{"class":880},[679,31678,31679,31681],{"class":681,"line":977},[679,31680,31644],{"class":880},[679,31682,31683],{"class":689}," production\n",[679,31685,31686],{"class":681,"line":982},[679,31687,31651],{"class":880},[679,31689,31690,31692,31694],{"class":681,"line":988},[679,31691,31656],{"class":880},[679,31693,31300],{"class":689},[679,31695,31661],{"class":689},[679,31697,31698,31700],{"class":681,"line":993},[679,31699,31666],{"class":880},[679,31701,31309],{"class":689},[651,31703,31704,31705,31710],{},"Back in our DataSource Configuration class, we can make a small change. When the only thing that changes about the bean is the properties it's instantiated with like we have here there is no need to create different beans based on the environment we are in. We can simply create one single bean and we will use the correct properties based on the active profile. To get our properties into our class we will use the ",[812,31706,31709],{"href":31707,"rel":31708},"https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Value.html",[816],"@Value annotation"," and the correct values based on our profile will get injected. ",[669,31712,31714],{"className":4107,"code":31713,"language":4109,"meta":674,"style":674},"package com.therealdanvega.config;\n\nimport com.therealdanvega.domain.DataSource;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class DataSourceConfig {\n\n @Value(\"${datasource.name}\")\n private String name;\n\n @Value(\"${datasource.url}\")\n private String url;\n\n @Bean\n DataSource dataSource() {\n return new DataSource(name,url);\n }\n\n}\n\n",[676,31715,31716,31723,31727,31734,31741,31747,31753,31757,31763,31774,31778,31792,31798,31802,31815,31822,31826,31832,31841,31852,31856,31860],{"__ignoreMap":674},[679,31717,31718,31720],{"class":681,"line":682},[679,31719,2543],{"class":685},[679,31721,31722],{"class":693}," com.therealdanvega.config;\n",[679,31724,31725],{"class":681,"line":790},[679,31726,889],{"emptyLinePlaceholder":797},[679,31728,31729,31731],{"class":681,"line":892},[679,31730,1999],{"class":685},[679,31732,31733],{"class":693}," com.therealdanvega.domain.DataSource;\n",[679,31735,31736,31738],{"class":681,"line":901},[679,31737,1999],{"class":685},[679,31739,31740],{"class":693}," org.springframework.beans.factory.annotation.Value;\n",[679,31742,31743,31745],{"class":681,"line":909},[679,31744,1999],{"class":685},[679,31746,6362],{"class":693},[679,31748,31749,31751],{"class":681,"line":918},[679,31750,1999],{"class":685},[679,31752,6201],{"class":693},[679,31754,31755],{"class":681,"line":935},[679,31756,889],{"emptyLinePlaceholder":797},[679,31758,31759,31761],{"class":681,"line":944},[679,31760,4116],{"class":693},[679,31762,6212],{"class":685},[679,31764,31765,31767,31769,31772],{"class":681,"line":959},[679,31766,6073],{"class":685},[679,31768,4512],{"class":685},[679,31770,31771],{"class":880}," DataSourceConfig",[679,31773,884],{"class":693},[679,31775,31776],{"class":681,"line":964},[679,31777,889],{"emptyLinePlaceholder":797},[679,31779,31780,31782,31785,31787,31790],{"class":681,"line":977},[679,31781,6872],{"class":693},[679,31783,31784],{"class":685},"Value",[679,31786,745],{"class":693},[679,31788,31789],{"class":689},"\"${datasource.name}\"",[679,31791,1339],{"class":693},[679,31793,31794,31796],{"class":681,"line":982},[679,31795,9232],{"class":685},[679,31797,16319],{"class":693},[679,31799,31800],{"class":681,"line":988},[679,31801,889],{"emptyLinePlaceholder":797},[679,31803,31804,31806,31808,31810,31813],{"class":681,"line":993},[679,31805,6872],{"class":693},[679,31807,31784],{"class":685},[679,31809,745],{"class":693},[679,31811,31812],{"class":689},"\"${datasource.url}\"",[679,31814,1339],{"class":693},[679,31816,31817,31819],{"class":681,"line":2129},[679,31818,9232],{"class":685},[679,31820,31821],{"class":693}," String url;\n",[679,31823,31824],{"class":681,"line":2140},[679,31825,889],{"emptyLinePlaceholder":797},[679,31827,31828,31830],{"class":681,"line":2145},[679,31829,6872],{"class":693},[679,31831,16929],{"class":685},[679,31833,31834,31836,31839],{"class":681,"line":2154},[679,31835,31378],{"class":693},[679,31837,31838],{"class":880},"dataSource",[679,31840,2667],{"class":693},[679,31842,31843,31845,31847,31849],{"class":681,"line":2159},[679,31844,9444],{"class":685},[679,31846,2054],{"class":685},[679,31848,31396],{"class":880},[679,31850,31851],{"class":693},"(name,url);\n",[679,31853,31854],{"class":681,"line":2164},[679,31855,985],{"class":693},[679,31857,31858],{"class":681,"line":3134},[679,31859,889],{"emptyLinePlaceholder":797},[679,31861,31862],{"class":681,"line":3139},[679,31863,996],{"class":693},[651,31865,31866],{},"This is the approach I would take and it works out pretty well. ",[4542,31868,31870],{"id":31869},"spring-boot-configuration-using-yaml-properties-screencast","Spring Boot Configuration using YAML Properties Screencast",[651,31872,31873],{},[812,31874,31875],{"href":31875,"rel":31876},"https://youtu.be/Utwu-17Ct9Y",[816],[4542,31878,9042],{"id":9041},[651,31880,31881],{},"Thanks for the question and I hope this helped others clear up some questions on using YAML configuration. This can be a little confusing when you have some many ways to inject properties but after some practice it all makes sense. ",[651,31883,13453,31884,31886],{},[2939,31885,11650],{}," What problems are you facing with YAML Configuration? _",[786,31888,31889],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":31891},[31892,31896,31897],{"id":31479,"depth":790,"text":31480,"children":31893},[31894,31895],{"id":31596,"depth":892,"text":31597},{"id":31608,"depth":892,"text":31609},{"id":31869,"depth":790,"text":31870},{"id":9041,"depth":790,"text":9042},{"slug":31899,"published":797,"date":31900,"tags":31901,"cover":22467},"spring-boot-configuration-using-yaml","2017-06-26T09:00:06-04:00",[7055],{"title":630,"description":630},"blog/2017/06/26/spring-boot-configuration-using-yaml","_-rKIcplosmOiPSbbseQdHh1e-3m6JhFiB1-sP1ywe0",{"id":31906,"title":627,"body":31907,"description":627,"extension":793,"meta":32543,"navigation":797,"path":628,"seo":32548,"stem":32549,"__hash__":32550},"content/blog/2017/06/28/deploying-war-application-server-spring-boot.md",{"type":648,"value":31908,"toc":32531},[31909,31916,31921,31924,31928,31947,31951,31954,31960,31964,31967,32200,32203,32207,32210,32299,32302,32405,32408,32414,32418,32421,32448,32452,32477,32481,32484,32497,32503,32506,32510,32516,32518,32521,32528],[651,31910,31911,31912,31915],{},"In this tutorial, we are taking a look at a student's question from my ",[812,31913,21140],{"href":30549,"rel":31914},[816],". We might not realize it but when we start a new project we are selecting how we want to package this application when we ready to go live. I recently received the following question from a student: ",[1004,31917,31918],{},[651,31919,31920],{},"In the real world, an application will be deployed to an application server like JBoss. How can the application be wrapped into a war file and deploy to the application server?",[651,31922,31923],{},"Thank you for the question and now let's dive into the answer. ",[4542,31925,31927],{"id":31926},"war-vs-jar","WAR vs JAR",[651,31929,31930,31931,31934,31935,31938,31939,31942,31943,31946],{},"The first thing we need to discuss is what is the difference between a WAR and a JAR. ",[7300,31932,31933],{},"WAR"," stands for Web Application Archive and is deployed on a Servlet Container like Tomcat or Jetty. ",[7300,31936,31937],{},"JAR"," stands for ",[2939,31940,31941],{},"J","ava ",[2939,31944,31945],{},"AR","chive and this will contain and embedded servlet container like Tomcat. These are two very different ways of deploying an application but I can assure that in the \"real world\" deploying a JAR file is very common. ",[4542,31948,31950],{"id":31949},"selecting-your-package-type","Selecting your Package Type",[651,31952,31953],{},"When you create a new project using the Spring Initializr you have the option to select how you want to package your application. ",[651,31955,31956],{},[660,31957],{"alt":31958,"src":31959},"Spring Boot Package Type","./2017-06-28_08-26-58-1024x645.png",[4542,31961,31963],{"id":31962},"converting-a-spring-boot-jar-application-to-a-war","Converting a Spring Boot JAR Application to a WAR",[651,31965,31966],{},"We don't always get the opportunity to create a new project from scratch, so what happens when you're working on an existing project? If you have a Maven based project that is already using JAR as its packaging type you might have a POM that looks like this. ",[669,31968,31970],{"className":9101,"code":31969,"language":9103,"meta":674,"style":674},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003Cproject xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n \u003CmodelVersion>4.0.0\u003C/modelVersion>\n\n \u003CgroupId>com.therealdanvega\u003C/groupId>\n \u003CartifactId>warnotjar\u003C/artifactId>\n \u003Cversion>0.0.1-SNAPSHOT\u003C/version>\n \u003Cpackaging>jar\u003C/packaging>\n\n \u003Cname>warnotjar\u003C/name>\n \u003Cdescription>Demo project for Spring Boot\u003C/description>\n\n \u003Cparent>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-parent\u003C/artifactId>\n \u003Cversion>1.5.4.RELEASE\u003C/version>\n \u003CrelativePath/> \u003C!-- lookup parent from repository -->\n \u003C/parent>\n\n \u003Cproperties>\n \u003Cproject.build.sourceEncoding>UTF-8\u003C/project.build.sourceEncoding>\n \u003Cproject.reporting.outputEncoding>UTF-8\u003C/project.reporting.outputEncoding>\n \u003Cjava.version>1.8\u003C/java.version>\n \u003C/properties>\n\n \u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n \u003C/dependency>\n\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-test\u003C/artifactId>\n \u003Cscope>test\u003C/scope>\n \u003C/dependency>\n \u003C/dependencies>\n\n \u003Cbuild>\n \u003Cplugins>\n \u003Cplugin>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-maven-plugin\u003C/artifactId>\n \u003C/plugin>\n \u003C/plugins>\n \u003C/build>\n\u003C/project>\n",[676,31971,31972,31977,31982,31987,31992,31996,32001,32006,32011,32016,32020,32025,32030,32034,32039,32043,32048,32053,32058,32063,32067,32072,32077,32082,32087,32092,32096,32101,32106,32110,32115,32120,32124,32128,32132,32137,32142,32146,32151,32155,32160,32165,32170,32175,32180,32185,32190,32195],{"__ignoreMap":674},[679,31973,31974],{"class":681,"line":682},[679,31975,31976],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",[679,31978,31979],{"class":681,"line":790},[679,31980,31981],{},"\u003Cproject xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n",[679,31983,31984],{"class":681,"line":892},[679,31985,31986],{}," xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n",[679,31988,31989],{"class":681,"line":901},[679,31990,31991],{}," \u003CmodelVersion>4.0.0\u003C/modelVersion>\n",[679,31993,31994],{"class":681,"line":909},[679,31995,889],{"emptyLinePlaceholder":797},[679,31997,31998],{"class":681,"line":918},[679,31999,32000],{}," \u003CgroupId>com.therealdanvega\u003C/groupId>\n",[679,32002,32003],{"class":681,"line":935},[679,32004,32005],{}," \u003CartifactId>warnotjar\u003C/artifactId>\n",[679,32007,32008],{"class":681,"line":944},[679,32009,32010],{}," \u003Cversion>0.0.1-SNAPSHOT\u003C/version>\n",[679,32012,32013],{"class":681,"line":959},[679,32014,32015],{}," \u003Cpackaging>jar\u003C/packaging>\n",[679,32017,32018],{"class":681,"line":964},[679,32019,889],{"emptyLinePlaceholder":797},[679,32021,32022],{"class":681,"line":977},[679,32023,32024],{}," \u003Cname>warnotjar\u003C/name>\n",[679,32026,32027],{"class":681,"line":982},[679,32028,32029],{}," \u003Cdescription>Demo project for Spring Boot\u003C/description>\n",[679,32031,32032],{"class":681,"line":988},[679,32033,889],{"emptyLinePlaceholder":797},[679,32035,32036],{"class":681,"line":993},[679,32037,32038],{}," \u003Cparent>\n",[679,32040,32041],{"class":681,"line":2129},[679,32042,19209],{},[679,32044,32045],{"class":681,"line":2140},[679,32046,32047],{}," \u003CartifactId>spring-boot-starter-parent\u003C/artifactId>\n",[679,32049,32050],{"class":681,"line":2145},[679,32051,32052],{}," \u003Cversion>1.5.4.RELEASE\u003C/version>\n",[679,32054,32055],{"class":681,"line":2154},[679,32056,32057],{}," \u003CrelativePath/> \u003C!-- lookup parent from repository -->\n",[679,32059,32060],{"class":681,"line":2159},[679,32061,32062],{}," \u003C/parent>\n",[679,32064,32065],{"class":681,"line":2164},[679,32066,889],{"emptyLinePlaceholder":797},[679,32068,32069],{"class":681,"line":3134},[679,32070,32071],{}," \u003Cproperties>\n",[679,32073,32074],{"class":681,"line":3139},[679,32075,32076],{}," \u003Cproject.build.sourceEncoding>UTF-8\u003C/project.build.sourceEncoding>\n",[679,32078,32079],{"class":681,"line":3144},[679,32080,32081],{}," \u003Cproject.reporting.outputEncoding>UTF-8\u003C/project.reporting.outputEncoding>\n",[679,32083,32084],{"class":681,"line":3149},[679,32085,32086],{}," \u003Cjava.version>1.8\u003C/java.version>\n",[679,32088,32089],{"class":681,"line":3169},[679,32090,32091],{}," \u003C/properties>\n",[679,32093,32094],{"class":681,"line":3185},[679,32095,889],{"emptyLinePlaceholder":797},[679,32097,32098],{"class":681,"line":3194},[679,32099,32100],{}," \u003Cdependencies>\n",[679,32102,32103],{"class":681,"line":3199},[679,32104,32105],{}," \u003Cdependency>\n",[679,32107,32108],{"class":681,"line":3212},[679,32109,22280],{},[679,32111,32112],{"class":681,"line":3217},[679,32113,32114],{}," \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n",[679,32116,32117],{"class":681,"line":3222},[679,32118,32119],{}," \u003C/dependency>\n",[679,32121,32122],{"class":681,"line":3227},[679,32123,889],{"emptyLinePlaceholder":797},[679,32125,32126],{"class":681,"line":3232},[679,32127,32105],{},[679,32129,32130],{"class":681,"line":3499},[679,32131,22280],{},[679,32133,32134],{"class":681,"line":3509},[679,32135,32136],{}," \u003CartifactId>spring-boot-starter-test\u003C/artifactId>\n",[679,32138,32139],{"class":681,"line":3516},[679,32140,32141],{}," \u003Cscope>test\u003C/scope>\n",[679,32143,32144],{"class":681,"line":3531},[679,32145,32119],{},[679,32147,32148],{"class":681,"line":3536},[679,32149,32150],{}," \u003C/dependencies>\n",[679,32152,32153],{"class":681,"line":3541},[679,32154,889],{"emptyLinePlaceholder":797},[679,32156,32157],{"class":681,"line":3546},[679,32158,32159],{}," \u003Cbuild>\n",[679,32161,32162],{"class":681,"line":3551},[679,32163,32164],{}," \u003Cplugins>\n",[679,32166,32167],{"class":681,"line":3557},[679,32168,32169],{}," \u003Cplugin>\n",[679,32171,32172],{"class":681,"line":3567},[679,32173,32174],{}," \u003CgroupId>org.springframework.boot\u003C/groupId>\n",[679,32176,32177],{"class":681,"line":3574},[679,32178,32179],{}," \u003CartifactId>spring-boot-maven-plugin\u003C/artifactId>\n",[679,32181,32182],{"class":681,"line":3589},[679,32183,32184],{}," \u003C/plugin>\n",[679,32186,32187],{"class":681,"line":3594},[679,32188,32189],{}," \u003C/plugins>\n",[679,32191,32192],{"class":681,"line":3602},[679,32193,32194],{}," \u003C/build>\n",[679,32196,32197],{"class":681,"line":3608},[679,32198,32199],{},"\u003C/project>\n",[651,32201,32202],{},"We can use 3 very simple steps to convert our application to use WAR as the package type. ",[5909,32204,32206],{"id":32205},"step-1-adding-the-servlet-initializer-class","Step 1: Adding the Servlet Initializer Class",[651,32208,32209],{},"In an application where we are using JAR as the package type, we only need the main application class that looks like this. ",[669,32211,32213],{"className":4107,"code":32212,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class WarnotjarApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(WarnotjarApplication.class, args);\n }\n}\n",[676,32214,32215,32221,32225,32231,32237,32241,32247,32258,32262,32282,32291,32295],{"__ignoreMap":674},[679,32216,32217,32219],{"class":681,"line":682},[679,32218,2543],{"class":685},[679,32220,6039],{"class":693},[679,32222,32223],{"class":681,"line":790},[679,32224,889],{"emptyLinePlaceholder":797},[679,32226,32227,32229],{"class":681,"line":892},[679,32228,1999],{"class":685},[679,32230,6050],{"class":693},[679,32232,32233,32235],{"class":681,"line":901},[679,32234,1999],{"class":685},[679,32236,6057],{"class":693},[679,32238,32239],{"class":681,"line":909},[679,32240,889],{"emptyLinePlaceholder":797},[679,32242,32243,32245],{"class":681,"line":918},[679,32244,4116],{"class":693},[679,32246,6068],{"class":685},[679,32248,32249,32251,32253,32256],{"class":681,"line":935},[679,32250,6073],{"class":685},[679,32252,4512],{"class":685},[679,32254,32255],{"class":880}," WarnotjarApplication",[679,32257,884],{"class":693},[679,32259,32260],{"class":681,"line":944},[679,32261,889],{"emptyLinePlaceholder":797},[679,32263,32264,32266,32268,32270,32272,32274,32276,32278,32280],{"class":681,"line":959},[679,32265,6089],{"class":685},[679,32267,6092],{"class":685},[679,32269,6095],{"class":685},[679,32271,6098],{"class":880},[679,32273,745],{"class":693},[679,32275,4758],{"class":685},[679,32277,16901],{"class":693},[679,32279,6108],{"class":2099},[679,32281,4390],{"class":693},[679,32283,32284,32286,32288],{"class":681,"line":964},[679,32285,6115],{"class":693},[679,32287,6118],{"class":880},[679,32289,32290],{"class":693},"(WarnotjarApplication.class, args);\n",[679,32292,32293],{"class":681,"line":977},[679,32294,985],{"class":693},[679,32296,32297],{"class":681,"line":982},[679,32298,996],{"class":693},[651,32300,32301],{},"In our WAR deployment, we are going to add another class that will help configure our web application. We are going to add a new class called Servlet Initializer that looks like this. You could also extend your main application class if you like and override configure in that class but I like separating this out. ",[669,32303,32305],{"className":4107,"code":32304,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.boot.builder.SpringApplicationBuilder;\nimport org.springframework.boot.web.support.SpringBootServletInitializer;\n\npublic class ServletInitializer extends SpringBootServletInitializer {\n\n @Override\n protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {\n return application.sources(WarnotjarApplication.class);\n }\n\n}\n",[676,32306,32307,32313,32317,32324,32331,32335,32351,32355,32361,32380,32393,32397,32401],{"__ignoreMap":674},[679,32308,32309,32311],{"class":681,"line":682},[679,32310,2543],{"class":685},[679,32312,6039],{"class":693},[679,32314,32315],{"class":681,"line":790},[679,32316,889],{"emptyLinePlaceholder":797},[679,32318,32319,32321],{"class":681,"line":892},[679,32320,1999],{"class":685},[679,32322,32323],{"class":693}," org.springframework.boot.builder.SpringApplicationBuilder;\n",[679,32325,32326,32328],{"class":681,"line":901},[679,32327,1999],{"class":685},[679,32329,32330],{"class":693}," org.springframework.boot.web.support.SpringBootServletInitializer;\n",[679,32332,32333],{"class":681,"line":909},[679,32334,889],{"emptyLinePlaceholder":797},[679,32336,32337,32339,32341,32344,32346,32349],{"class":681,"line":918},[679,32338,6073],{"class":685},[679,32340,4512],{"class":685},[679,32342,32343],{"class":880}," ServletInitializer",[679,32345,2767],{"class":685},[679,32347,32348],{"class":880}," SpringBootServletInitializer",[679,32350,884],{"class":693},[679,32352,32353],{"class":681,"line":935},[679,32354,889],{"emptyLinePlaceholder":797},[679,32356,32357,32359],{"class":681,"line":944},[679,32358,6872],{"class":693},[679,32360,10723],{"class":685},[679,32362,32363,32366,32369,32372,32375,32378],{"class":681,"line":959},[679,32364,32365],{"class":685}," protected",[679,32367,32368],{"class":693}," SpringApplicationBuilder ",[679,32370,32371],{"class":880},"configure",[679,32373,32374],{"class":693},"(SpringApplicationBuilder ",[679,32376,32377],{"class":2099},"application",[679,32379,4390],{"class":693},[679,32381,32382,32384,32387,32390],{"class":681,"line":964},[679,32383,9444],{"class":685},[679,32385,32386],{"class":693}," application.",[679,32388,32389],{"class":880},"sources",[679,32391,32392],{"class":693},"(WarnotjarApplication.class);\n",[679,32394,32395],{"class":681,"line":977},[679,32396,985],{"class":693},[679,32398,32399],{"class":681,"line":982},[679,32400,889],{"emptyLinePlaceholder":797},[679,32402,32403],{"class":681,"line":988},[679,32404,996],{"class":693},[651,32406,32407],{},"In my example, I am using my main application class (WarnotjarApplication.class) as an argument to the sources method. You should end up with the main application class and a class called ServletInitializer. ",[651,32409,32410],{},[660,32411],{"alt":32412,"src":32413},"WAR not JAR","./2017-06-28_08-53-25.png",[5909,32415,32417],{"id":32416},"step-2-update-the-tomcat-dependency","Step 2: Update the Tomcat Dependency",[651,32419,32420],{},"In our Spring Boot Starter Web dependency, there is a Tomcat dependency declared. We can't go in and make a change there but we can make the change in our POM. We need to change that dependency to say that this will be provided for us. ",[669,32422,32424],{"className":9101,"code":32423,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-tomcat\u003C/artifactId>\n \u003Cscope>provided\u003C/scope>\n\u003C/dependency>\n",[676,32425,32426,32430,32434,32439,32444],{"__ignoreMap":674},[679,32427,32428],{"class":681,"line":682},[679,32429,9110],{},[679,32431,32432],{"class":681,"line":790},[679,32433,9115],{},[679,32435,32436],{"class":681,"line":892},[679,32437,32438],{}," \u003CartifactId>spring-boot-starter-tomcat\u003C/artifactId>\n",[679,32440,32441],{"class":681,"line":901},[679,32442,32443],{}," \u003Cscope>provided\u003C/scope>\n",[679,32445,32446],{"class":681,"line":909},[679,32447,9125],{},[5909,32449,32451],{"id":32450},"step-3-change-the-package-type-to-war","Step 3: Change the package type to war",[669,32453,32455],{"className":9101,"code":32454,"language":9103,"meta":674,"style":674},"\u003CgroupId>com.therealdanvega\u003C/groupId>\n\u003CartifactId>warnotjar\u003C/artifactId>\n\u003Cversion>0.0.1-SNAPSHOT\u003C/version>\n\u003Cpackaging>war\u003C/packaging>\n",[676,32456,32457,32462,32467,32472],{"__ignoreMap":674},[679,32458,32459],{"class":681,"line":682},[679,32460,32461],{},"\u003CgroupId>com.therealdanvega\u003C/groupId>\n",[679,32463,32464],{"class":681,"line":790},[679,32465,32466],{},"\u003CartifactId>warnotjar\u003C/artifactId>\n",[679,32468,32469],{"class":681,"line":892},[679,32470,32471],{},"\u003Cversion>0.0.1-SNAPSHOT\u003C/version>\n",[679,32473,32474],{"class":681,"line":901},[679,32475,32476],{},"\u003Cpackaging>war\u003C/packaging>\n",[4542,32478,32480],{"id":32479},"packaging-your-application","Packaging your application",[651,32482,32483],{},"With those changes in place, you can now package your application. ",[669,32485,32487],{"className":5851,"code":32486,"language":5853,"meta":674,"style":674},"./mvnw package\n",[676,32488,32489],{"__ignoreMap":674},[679,32490,32491,32494],{"class":681,"line":682},[679,32492,32493],{"class":880},"./mvnw",[679,32495,32496],{"class":689}," package\n",[651,32498,32499],{},[660,32500],{"alt":32501,"src":32502},"Maven Package WAR","./2017-06-28_09-08-50.png",[651,32504,32505],{},"If you look inside of the /target folder now you should see the war file. You can now take that file and deploy on your servlet container. ",[4542,32507,32509],{"id":32508},"spring-boot-war-screencast","Spring Boot WAR Screencast",[651,32511,32512],{},[812,32513,32514],{"href":32514,"rel":32515},"https://youtu.be/92ceKwUZoA0",[816],[4542,32517,9042],{"id":9041},[651,32519,32520],{},"Spring Boot makes it easy for us to package our applications to fit our needs. I hope this tutorial showed how easy it was to convert an existing JAR application over to a WAR. ",[651,32522,32523],{},[7300,32524,32525,32527],{},[2939,32526,11650],{}," What Spring Boot Deployment issues are you facing?",[786,32529,32530],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":32532},[32533,32534,32535,32540,32541,32542],{"id":31926,"depth":790,"text":31927},{"id":31949,"depth":790,"text":31950},{"id":31962,"depth":790,"text":31963,"children":32536},[32537,32538,32539],{"id":32205,"depth":892,"text":32206},{"id":32416,"depth":892,"text":32417},{"id":32450,"depth":892,"text":32451},{"id":32479,"depth":790,"text":32480},{"id":32508,"depth":790,"text":32509},{"id":9041,"depth":790,"text":9042},{"slug":32544,"published":797,"date":32545,"tags":32546,"cover":32547},"deploying-war-application-server-spring-boot","2017-06-28T09:14:15-04:00",[7055],"./pexels-photo-292627-760x506.jpeg",{"title":627,"description":627},"blog/2017/06/28/deploying-war-application-server-spring-boot","NyDlSk2eVWWofLoDXIfTXFaqA8yhCVtC84T0GP4oNYk",{"id":32552,"title":624,"body":32553,"description":624,"extension":793,"meta":32790,"navigation":797,"path":625,"seo":32795,"stem":32796,"__hash__":32797},"content/blog/2017/06/30/9-presentations-can-watch-right-now-learn-java-9.md",{"type":648,"value":32554,"toc":32775},[32555,32558,32562,32565,32569,32572,32578,32582,32590,32596,32600,32603,32620,32623,32629,32633,32636,32642,32646,32649,32655,32659,32662,32668,32672,32675,32681,32685,32688,32694,32698,32701,32707,32710,32719,32758,32764,32766,32769],[651,32556,32557],{},"In this article, I am going to give you 9 presentations that can you help learn all about Java 9.",[4542,32559,32561],{"id":32560},"_9-presentations-on-java-9","9 Presentations on Java 9",[651,32563,32564],{},"The following presentations cover a wide variety of topics to help you understand what is coming in Java 9. ",[5909,32566,32568],{"id":32567},"real-world-java-9-by-trisha-gee","Real World Java 9 by Trisha Gee",[651,32570,32571],{},"The feature we always hear about whenever Java 9 is in the news is Jigsaw, modularity for Java. But modularity just doesn't scratch the same developer itch that Java 8's lambdas and streams did, and as developers, we're left with a vague sensation that version 9 might just not be that interesting. In fact, Java 9 actually has a lot of great additions and changes which will make Java just that bit nicer to work with. These features can't be lumped under a nice umbrella term like Java 8's lambdas and streams, but the Java 9 changes are scattered throughout the APIs and language features that we regularly use.",[651,32573,32574],{},[812,32575,32576],{"href":32576,"rel":32577},"https://www.youtube.com/watch?v=watS54iWH9U&feature=youtu.be&list=PLEx5khR4g7PLcjLdaugk3GndelpTGbYDS",[816],[5909,32579,32581],{"id":32580},"exploring-java-9-by-venkat-subramaniam","Exploring Java 9 by Venkat Subramaniam",[651,32583,32584,32585,32589],{},"How we write code was greatly influenced by Java 8. How we package the code and interact with it will be impacted by Java 9. In this presentations, we will learn about the major features of Java 9. We will start by discussing the current concerns and how the module system tries to alleviate those pains. We will learn about modules, how to define dependencies, and also how to work with existing jar files. Finally, we will explore Java 9 REPL, the reasons to use it, and various features that can benefit the programmers. Dr. Venkat Subramaniam is an award-winning author, founder of Agile Developer, Inc., creator of agilelearner.com, and an instructional professor at the University of Houston. He has trained and mentored thousands of software developers in the US, Canada, Europe, and Asia, and is a regularly-invited speaker at several international conferences. Venkat helps his clients effectively apply and succeed with sustainable agile practices on their software projects. Venkat is a (co)author of multiple technical books, including the 2007 Jolt Productivity award-winning book Practices of an Agile Developer. You can find a list of his books at agiledeveloper.com. You can reach him by email at ",[812,32586,32588],{"href":32587},"mailto:venkats@agiledeveloper.com","venkats@agiledeveloper.com"," or on Twitter at @venkat_s",[651,32591,32592],{},[812,32593,32594],{"href":32594,"rel":32595},"https://www.youtube.com/watch?v=8XmYT89fBKg&t=1099s",[816],[5909,32597,32599],{"id":32598},"_55-new-features-in-jdk-9-by-simon-ritter","55 New Features in JDK 9 by Simon Ritter",[651,32601,32602],{},"Following on from the popular “55 New Features in Java SE 8” we bring you the eagerly-awaited sequel, “55 New Features in JDK 9”. Obviously, the big new feature in JDK 9 is modularity and project Jigsaw, but there’s lots more to tempt developers. We’ll divide things into five categories:",[5316,32604,32605,32608,32611,32614,32617],{},[5332,32606,32607],{},"Features",[5332,32609,32610],{},"Standards",[5332,32612,32613],{},"Inside the JVM",[5332,32615,32616],{},"Specialised",[5332,32618,32619],{},"Housekeeping",[651,32621,32622],{},"Join us on a whirlwind tour of what’s in (and what’s out) in JDK 9 so you’re ready to get started with the latest version of the most popular programming platform on the planet.",[651,32624,32625],{},[812,32626,32627],{"href":32627,"rel":32628},"https://www.youtube.com/watch?v=CMMzG8I23lY",[816],[5909,32630,32632],{"id":32631},"what-you-need-to-know-about-java-9-including-migration-by-simon-maple","What You Need to Know About Java 9, Including Migration by Simon Maple",[651,32634,32635],{},"Java 9 is due for release in just a few months. The latest version brings new, interesting features including modules, JShell, G1GC, and many improvements to the existing programming model. This session examines what these features mean to developer and production environments and delves into what you need to know when migrating to the latest version. What do you need to do to make your applications modular? How do you break down your existing application to make them run in the module-path? How does the new default garbage collector affect how your application will run in production? Come to this session to learn what's new in Java 9 and discover how to best adopt and migrate to the new version.",[651,32637,32638],{},[812,32639,32640],{"href":32640,"rel":32641},"https://www.youtube.com/watch?v=s0WHIXaSbr4",[816],[5909,32643,32645],{"id":32644},"migrating-to-java-9-modules-by-paul-bakker","Migrating to Java 9 Modules by Paul Bakker",[651,32647,32648],{},"Java 9 comes to your doorstep with major changes to all of us, whether you ordered it or not. The module system in Java 9 is a great advancement for the Java language, and we would like to migrate existing code to make use of the module system. Migrating an existing code base from the classpath to any kind of module system can be a daunting task, however. Not only do we have to take care of migrating our own code to modules, we also have to take third party libraries into consideration. Java 9 comes with a number of features to ease migration. This includes automatic modules, the unnamed module and a number of command line arguments. While these features provide great value, they do require a deep understanding of the module system to use them to their full potential. In this talk, we will look at examples of migrating real code, based on a Spring/Hibernate application. We will face common problems we run into during migration, which gives us practical tips to apply, but also a good understanding of the module framework itself and the various migration features it supports. This talk is an excellent preparation to start migrating your own code. Paul Bakker is a senior software engineer with Netflix in the Edge Developer Experience team. Besides loving to write code he has a passion for sharing knowledge. He is the co-author of \"Java 9 Modularity\" that will be published by O'Reilly in 2017, and the co-author of \"Modular Cloud Apps with OSGi\" which was published by O'Reilly in 2013. Paul is also frequently speaking at conferences about Modularity, container technology, and many other topics.",[651,32650,32651],{},[812,32652,32653],{"href":32653,"rel":32654},"https://www.youtube.com/watch?v=TEoexFsDP6A",[816],[5909,32656,32658],{"id":32657},"modular-development-with-jdk-9-by-alex-buckley","Modular Development with JDK 9 by Alex Buckley",[651,32660,32661],{},"A modular development style benefits every Java developer, whether your application is one JAR or one hundred JARs. This session will introduce the Java 9 module system that's been used to structure the JDK as dozens of reusable modules that strongly protect their internals. Then, the session will explain how you can create modules to enforce the structure inherent in your application. The session will prepare you for some of the pitfalls of modular development, notably the technical debt present in popular libraries that rely on JDK internals. Finally, the session will look at how tools are preparing for modules. Alex Buckley is the Specification Lead for the Java programming language and the Java Virtual Machine at Oracle. He holds a Ph.D. in Computing from Imperial College London.",[651,32663,32664],{},[812,32665,32666],{"href":32666,"rel":32667},"https://www.youtube.com/watch?v=rfOjch4p0Po",[816],[5909,32669,32671],{"id":32670},"jdk-9-language-tooling-and-library-features-by-joe-darcy","JDK 9 Language, Tooling, and Library Features by Joe Darcy",[651,32673,32674],{},"Modularity support from Project Jigsaw is the largest change coming in Java SE 9, but many other improvements are coming to the Java programming language, related tooling, and libraries in JDK 9. On the language front, some polish is added to the Project Coin features and the number of uninformative warnings has been reduced. The javac tool can now accurately cross-compile to older platform versions and has improved attribution engineering to better support type inference. Javadoc is getting a search box, a new modernized doclet API, and HTML5 support. The libraries offer better process control, improved collections, and more-compact strings. Learn more at this session. Joe Darcy is a long-time JDK engineer who has worked on projects ranging over floating-point arithmetic, introducing annotation processing to javac, serving as the inaugural release manager for OpenJDK 6, leading the Project Coin language additions, and migrating JDK bugs to a JIRA system.",[651,32676,32677],{},[812,32678,32679],{"href":32679,"rel":32680},"https://www.youtube.com/watch?v=KQiYlWFvc68",[816],[5909,32682,32684],{"id":32683},"enhanced-deprecation-in-java-9-by-stuart-marks","Enhanced Deprecation in Java 9 by Stuart Marks",[651,32686,32687],{},"Java has grown immensely since its introduction, more than 20 years ago. However, there has not been a corresponding effort to remove obsolete features from the system. Some features have been deprecated, but very few have actually been removed. The platform has grown ever larger and more complex, resulting in an accumulation of complexity and technical debt. These impose a continual tax on development. This session explores “depreciation,” the trailing edge of the feature lifecycle. This includes work in Java 9 to update and clarify the @Deprecated annotation, plus additional tools being provided to enable developers to assess the impact of depreciation on their codebases and to help them deal with code migration more effectively. ",[651,32689,32690],{},[812,32691,32692],{"href":32692,"rel":32693},"https://www.youtube.com/watch?v=T_O9merCgKw",[816],[5909,32695,32697],{"id":32696},"interactive-development-and-fast-feedback-with-java-9-repl-by-venkat-subramaniam","Interactive Development and Fast Feedback with Java 9 REPL by Venkat Subramaniam",[651,32699,32700],{},"Programming is an act of continuous discoveries. Auto-Completion in IDEs are great, but they're more of a speculation than experimentation. Read-Evaluate-Print-Loop or REPL gives an instant feedback and the ability to quickly try out your ideas. Fast feedbacks are the rage today in development. Come to this all live coding, no slides session to learn how to leverage the Java 9 REPL to accelerate your Java Development.",[651,32702,32703],{},[812,32704,32705],{"href":32705,"rel":32706},"https://www.youtube.com/watch?v=DHTVcq_fK2U",[816],[4542,32708,32709],{"id":25304},"Course",[651,32711,32712,32713,32718],{},"It looks like the release of Java 9 has been pushed back again until September 21st, 2017. While this is unfortunate it was a little expected and gives me some more time to finish my course. If you're interested in signing up for the waitlist for this course ",[812,32714,32717],{"href":32715,"rel":32716},"https://www.danvega.dev/java-9",[816],"please click here",". In this course, we are going to dive into what’s new in Java 9. We are going to walk through how to get Java 9 as well as work on upgrading existing Java 8 applications to Java 9. After that, we will look at some tools and resources to get us started. Then we will dive into the meat of the course where we will have mini workshops that dive into the new features of JDK 9. ",[5316,32720,32721,32723,32726,32729,32732,32735,32738,32755],{},[5332,32722,25857],{},[5332,32724,32725],{},"Prepare for JDK 9",[5332,32727,32728],{},"Installation & Usage",[5332,32730,32731],{},"Upgrading existing applications to JDK 9",[5332,32733,32734],{},"Tools & Resources",[5332,32736,32737],{},"JDK 9 Language, Tooling, and Library Features",[5332,32739,32740,32741],{},"Feature Workshops\n",[5316,32742,32743,32746,32749,32752],{},[5332,32744,32745],{},"Java 9 Modules (Jigsaw)",[5332,32747,32748],{},"JShell ",[5332,32750,32751],{},"Http 2",[5332,32753,32754],{},"Language Enhancements ",[5332,32756,32757],{},"The Future of Java",[651,32759,32760],{},[812,32761,32763],{"href":32715,"rel":32762},[816],"Java 9 Course Waitlist",[4542,32765,9042],{"id":9041},[651,32767,32768],{},"There is already some amazing content out there on Java 9 and it is always improving. I hope these presentations help you along your journey in learning Java 9. ",[651,32770,32771],{},[7300,32772,32773,17823],{},[2939,32774,11650],{},{"title":674,"searchDepth":790,"depth":790,"links":32776},[32777,32788,32789],{"id":32560,"depth":790,"text":32561,"children":32778},[32779,32780,32781,32782,32783,32784,32785,32786,32787],{"id":32567,"depth":892,"text":32568},{"id":32580,"depth":892,"text":32581},{"id":32598,"depth":892,"text":32599},{"id":32631,"depth":892,"text":32632},{"id":32644,"depth":892,"text":32645},{"id":32657,"depth":892,"text":32658},{"id":32670,"depth":892,"text":32671},{"id":32683,"depth":892,"text":32684},{"id":32696,"depth":892,"text":32697},{"id":25304,"depth":790,"text":32709},{"id":9041,"depth":790,"text":9042},{"slug":32791,"published":797,"date":32792,"tags":32793,"cover":32794},"9-presentations-can-watch-right-now-learn-java-9","2017-06-30T08:00:48-04:00",[4109],"./coding.jpg",{"title":624,"description":624},"blog/2017/06/30/9-presentations-can-watch-right-now-learn-java-9","CCBBxcfGJ-ukwuLgixOJ2TZu9LEojQ8zXW2sp-D9Edw",{"id":32799,"title":621,"body":32800,"description":621,"extension":793,"meta":33208,"navigation":797,"path":622,"seo":33213,"stem":33214,"__hash__":33215},"content/blog/2017/07/03/multiple-request-mappings-spring-boot.md",{"type":648,"value":32801,"toc":33203},[32802,32808,32813,32817,32825,32919,32927,33032,33035,33067,33070,33168,33180,33184,33190,33192,33195,33200],[651,32803,31911,32804,32807],{},[812,32805,21140],{"href":30549,"rel":32806},[816],". The question was related to building out Spring Controllers and how to define multiple request mappings for a single method.",[1004,32809,32810],{},[651,32811,32812],{},"\"How can I create multiple request mappings for a single method?\"",[4542,32814,32816],{"id":32815},"multiple-request-mappings","Multiple Request Mappings",[651,32818,32819,32820,32824],{},"If you want to follow along with this tutorial you can create a simple Spring Boot project that includes the Web dependency. We know that we can use the ",[812,32821,22845],{"href":32822,"rel":32823},"https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html",[816]," annotation to map web requests onto specific handler classes and/or handler methods. If we wanted to map our home method to the root \"/\" we can do so using the following code. ",[669,32826,32828],{"className":4107,"code":32827,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class HomeController {\n\n @RequestMapping(\"/\")\n public String home() {\n return \"Hello, World!\";\n }\n\n}\n\n",[676,32829,32830,32836,32840,32846,32852,32856,32862,32872,32876,32888,32898,32907,32911,32915],{"__ignoreMap":674},[679,32831,32832,32834],{"class":681,"line":682},[679,32833,2543],{"class":685},[679,32835,6039],{"class":693},[679,32837,32838],{"class":681,"line":790},[679,32839,889],{"emptyLinePlaceholder":797},[679,32841,32842,32844],{"class":681,"line":892},[679,32843,1999],{"class":685},[679,32845,12545],{"class":693},[679,32847,32848,32850],{"class":681,"line":901},[679,32849,1999],{"class":685},[679,32851,12552],{"class":693},[679,32853,32854],{"class":681,"line":909},[679,32855,889],{"emptyLinePlaceholder":797},[679,32857,32858,32860],{"class":681,"line":918},[679,32859,4116],{"class":693},[679,32861,9212],{"class":685},[679,32863,32864,32866,32868,32870],{"class":681,"line":935},[679,32865,6073],{"class":685},[679,32867,4512],{"class":685},[679,32869,18716],{"class":880},[679,32871,884],{"class":693},[679,32873,32874],{"class":681,"line":944},[679,32875,889],{"emptyLinePlaceholder":797},[679,32877,32878,32880,32882,32884,32886],{"class":681,"line":959},[679,32879,6872],{"class":693},[679,32881,9275],{"class":685},[679,32883,745],{"class":693},[679,32885,10032],{"class":689},[679,32887,1339],{"class":693},[679,32889,32890,32892,32894,32896],{"class":681,"line":964},[679,32891,6089],{"class":685},[679,32893,9289],{"class":693},[679,32895,12642],{"class":880},[679,32897,2667],{"class":693},[679,32899,32900,32902,32905],{"class":681,"line":977},[679,32901,9444],{"class":685},[679,32903,32904],{"class":689}," \"Hello, World!\"",[679,32906,1186],{"class":693},[679,32908,32909],{"class":681,"line":982},[679,32910,985],{"class":693},[679,32912,32913],{"class":681,"line":988},[679,32914,889],{"emptyLinePlaceholder":797},[679,32916,32917],{"class":681,"line":993},[679,32918,996],{"class":693},[651,32920,32921,32922,32926],{},"If you fire up this application and visit ",[812,32923,32924],{"href":32924,"rel":32925},"http://localhost:8080/",[816]," you will see the string \"Hello, World!\" returned to the browser. What happens if you wanted to define 2 or more web requests to be handled by the same method? It turns out you can only use one annotation per method, so something like this will not work. ",[669,32928,32930],{"className":4107,"code":32929,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class HomeController {\n\n @RequestMapping(\"/\")\n @RequestMapping(\"/home\")\n public String home() {\n return \"Hello, World!\";\n }\n\n}\n",[676,32931,32932,32938,32942,32948,32954,32958,32964,32974,32978,32990,33002,33012,33020,33024,33028],{"__ignoreMap":674},[679,32933,32934,32936],{"class":681,"line":682},[679,32935,2543],{"class":685},[679,32937,6039],{"class":693},[679,32939,32940],{"class":681,"line":790},[679,32941,889],{"emptyLinePlaceholder":797},[679,32943,32944,32946],{"class":681,"line":892},[679,32945,1999],{"class":685},[679,32947,12545],{"class":693},[679,32949,32950,32952],{"class":681,"line":901},[679,32951,1999],{"class":685},[679,32953,12552],{"class":693},[679,32955,32956],{"class":681,"line":909},[679,32957,889],{"emptyLinePlaceholder":797},[679,32959,32960,32962],{"class":681,"line":918},[679,32961,4116],{"class":693},[679,32963,9212],{"class":685},[679,32965,32966,32968,32970,32972],{"class":681,"line":935},[679,32967,6073],{"class":685},[679,32969,4512],{"class":685},[679,32971,18716],{"class":880},[679,32973,884],{"class":693},[679,32975,32976],{"class":681,"line":944},[679,32977,889],{"emptyLinePlaceholder":797},[679,32979,32980,32982,32984,32986,32988],{"class":681,"line":959},[679,32981,6872],{"class":693},[679,32983,9275],{"class":685},[679,32985,745],{"class":693},[679,32987,10032],{"class":689},[679,32989,1339],{"class":693},[679,32991,32992,32994,32996,32998,33000],{"class":681,"line":964},[679,32993,6872],{"class":693},[679,32995,9275],{"class":685},[679,32997,745],{"class":693},[679,32999,29811],{"class":689},[679,33001,1339],{"class":693},[679,33003,33004,33006,33008,33010],{"class":681,"line":977},[679,33005,6089],{"class":685},[679,33007,9289],{"class":693},[679,33009,12642],{"class":880},[679,33011,2667],{"class":693},[679,33013,33014,33016,33018],{"class":681,"line":982},[679,33015,9444],{"class":685},[679,33017,32904],{"class":689},[679,33019,1186],{"class":693},[679,33021,33022],{"class":681,"line":988},[679,33023,985],{"class":693},[679,33025,33026],{"class":681,"line":993},[679,33027,889],{"emptyLinePlaceholder":797},[679,33029,33030],{"class":681,"line":2129},[679,33031,996],{"class":693},[651,33033,33034],{},"If we look at the Request Mapping class value will take an array of strings and defaults to an empty array.",[669,33036,33038],{"className":4107,"code":33037,"language":4109,"meta":674,"style":674},"@AliasFor(\"value\")\nString\\[\\] path() default {};\n",[676,33039,33040,33053],{"__ignoreMap":674},[679,33041,33042,33044,33046,33048,33051],{"class":681,"line":682},[679,33043,4116],{"class":693},[679,33045,6875],{"class":685},[679,33047,745],{"class":693},[679,33049,33050],{"class":689},"\"value\"",[679,33052,1339],{"class":693},[679,33054,33055,33058,33061,33063,33065],{"class":681,"line":790},[679,33056,33057],{"class":693},"String\\[\\] ",[679,33059,33060],{"class":880},"path",[679,33062,6700],{"class":693},[679,33064,6703],{"class":685},[679,33066,6706],{"class":693},[651,33068,33069],{},"So we can pass in an array of strings that represent web requests. ",[669,33071,33073],{"className":4107,"code":33072,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class HomeController {\n\n @RequestMapping( {\"/\", \"/home\"} )\n public String home() {\n return \"Hello, World!\";\n }\n\n}\n",[676,33074,33075,33081,33085,33091,33097,33101,33107,33117,33121,33138,33148,33156,33160,33164],{"__ignoreMap":674},[679,33076,33077,33079],{"class":681,"line":682},[679,33078,2543],{"class":685},[679,33080,6039],{"class":693},[679,33082,33083],{"class":681,"line":790},[679,33084,889],{"emptyLinePlaceholder":797},[679,33086,33087,33089],{"class":681,"line":892},[679,33088,1999],{"class":685},[679,33090,12545],{"class":693},[679,33092,33093,33095],{"class":681,"line":901},[679,33094,1999],{"class":685},[679,33096,12552],{"class":693},[679,33098,33099],{"class":681,"line":909},[679,33100,889],{"emptyLinePlaceholder":797},[679,33102,33103,33105],{"class":681,"line":918},[679,33104,4116],{"class":693},[679,33106,9212],{"class":685},[679,33108,33109,33111,33113,33115],{"class":681,"line":935},[679,33110,6073],{"class":685},[679,33112,4512],{"class":685},[679,33114,18716],{"class":880},[679,33116,884],{"class":693},[679,33118,33119],{"class":681,"line":944},[679,33120,889],{"emptyLinePlaceholder":797},[679,33122,33123,33125,33127,33130,33132,33134,33136],{"class":681,"line":959},[679,33124,6872],{"class":693},[679,33126,9275],{"class":685},[679,33128,33129],{"class":693},"( {",[679,33131,10032],{"class":689},[679,33133,2797],{"class":693},[679,33135,29811],{"class":689},[679,33137,11572],{"class":693},[679,33139,33140,33142,33144,33146],{"class":681,"line":964},[679,33141,6089],{"class":685},[679,33143,9289],{"class":693},[679,33145,12642],{"class":880},[679,33147,2667],{"class":693},[679,33149,33150,33152,33154],{"class":681,"line":977},[679,33151,9444],{"class":685},[679,33153,32904],{"class":689},[679,33155,1186],{"class":693},[679,33157,33158],{"class":681,"line":982},[679,33159,985],{"class":693},[679,33161,33162],{"class":681,"line":988},[679,33163,889],{"emptyLinePlaceholder":797},[679,33165,33166],{"class":681,"line":993},[679,33167,996],{"class":693},[651,33169,33170,33171,33174,33175,33179],{},"Now if visit ",[812,33172,32924],{"href":32924,"rel":33173},[816]," or ",[812,33176,33177],{"href":33177,"rel":33178},"http://localhost:8080/home",[816]," we should see the string \"Hello, World!\" printed to the browser window. ",[4542,33181,33183],{"id":33182},"multiple-request-mappings-screencast","Multiple Request Mappings Screencast",[651,33185,33186],{},[812,33187,33188],{"href":33188,"rel":33189},"https://www.youtube.com/watch?v=qmALVM38oec&t=1s",[816],[4542,33191,9042],{"id":9041},[651,33193,33194],{},"There are some things that might not be apparent but as we saw in this tutorial we can quickly drill into the code and find a solution. This is one of the many reasons open source is so great.",[651,33196,13453,33197,33199],{},[2939,33198,11650],{}," Do you face any problems setting up request mappings? _",[786,33201,33202],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":33204},[33205,33206,33207],{"id":32815,"depth":790,"text":32816},{"id":33182,"depth":790,"text":33183},{"id":9041,"depth":790,"text":9042},{"slug":33209,"published":797,"date":33210,"tags":33211,"cover":33212},"multiple-request-mappings-spring-boot","2017-07-03T08:30:50-04:00",[7055],"./pexels-photo-450035-760x506.jpeg",{"title":621,"description":621},"blog/2017/07/03/multiple-request-mappings-spring-boot","O6L-TFKIqBMYZ_5v9Y1dPtZIOo1kBDxltsdtwTx2y10",{"id":33217,"title":618,"body":33218,"description":618,"extension":793,"meta":34547,"navigation":797,"path":619,"seo":34553,"stem":34554,"__hash__":34555},"content/blog/2017/07/05/read-json-data-spring-boot-write-database.md",{"type":648,"value":33219,"toc":34539},[33220,33226,33231,33234,33236,33239,33243,33247,33262,33483,33486,33637,33737,33809,33905,33909,33912,34043,34046,34200,34204,34216,34222,34229,34232,34255,34258,34510,34518,34524,34526,34529,34536],[651,33221,31911,33222,33225],{},[812,33223,21140],{"href":33224},"/courses",". If you have a question you would like me to answer please feel free to reach out and ask me. The question I am answering today is:",[1004,33227,33228],{},[651,33229,33230],{},"How can I read in a JSON file in Spring Boot and save the records to a database?",[651,33232,33233],{},"If you have ever had to read and write JSON data you know that this can be tricky. First, you have to read in the JSON data and mapping that data to your domain model isn't always easy. Once you have that in place you need to take that data and save it off to a database. In this tutorial, we will look at a quick and easy way to do this in Spring Boot.",[4542,33235,18978],{"id":26471},[651,33237,33238],{},"The first thing you need to do is to create a new Spring Boot application using the Spring Initalizr. This will create a new Spring Boot application and you will want to select the following dependencies:",[651,33240,33241],{},[660,33242],{"alt":16228,"src":16229},[5909,33244,33246],{"id":33245},"json-data","JSON Data",[651,33248,33249,33250,33255,33256,33261],{},"Next you need some sample JSON data and one of my favorite services for doing this is ",[812,33251,33254],{"href":33252,"rel":33253},"https://jsonplaceholder.typicode.com/",[816],"JSON Placeholder",". We are going to grab a ",[812,33257,33260],{"href":33258,"rel":33259},"https://jsonplaceholder.typicode.com/users",[816],"list of users"," and save that to a file called users.json and place it inside of /src/main/resources/json/. Each JSON record for a user will look something like this.",[669,33263,33265],{"className":28439,"code":33264,"language":28441,"meta":674,"style":674}," {\n \"id\": 1,\n \"name\": \"Leanne Graham\",\n \"username\": \"Bret\",\n \"email\": \"Sincere@april.biz\",\n \"address\": {\n \"street\": \"Kulas Light\",\n \"suite\": \"Apt. 556\",\n \"city\": \"Gwenborough\",\n \"zipcode\": \"92998-3874\",\n \"geo\": {\n \"lat\": \"-37.3159\",\n \"lng\": \"81.1496\"\n }\n },\n \"phone\": \"1-770-736-8031 x56442\",\n \"website\": \"hildegard.org\",\n \"company\": {\n \"name\": \"Romaguera-Crona\",\n \"catchPhrase\": \"Multi-layered client-server neural-net\",\n \"bs\": \"harness real-time e-markets\"\n }\n },\n",[676,33266,33267,33272,33283,33294,33306,33318,33325,33337,33349,33361,33373,33380,33392,33402,33406,33410,33422,33434,33441,33453,33465,33475,33479],{"__ignoreMap":674},[679,33268,33269],{"class":681,"line":682},[679,33270,33271],{"class":693}," {\n",[679,33273,33274,33277,33279,33281],{"class":681,"line":790},[679,33275,33276],{"class":931}," \"id\"",[679,33278,4282],{"class":693},[679,33280,1557],{"class":931},[679,33282,12083],{"class":693},[679,33284,33285,33287,33289,33292],{"class":681,"line":892},[679,33286,28473],{"class":931},[679,33288,4282],{"class":693},[679,33290,33291],{"class":689},"\"Leanne Graham\"",[679,33293,12083],{"class":693},[679,33295,33296,33299,33301,33304],{"class":681,"line":901},[679,33297,33298],{"class":931}," \"username\"",[679,33300,4282],{"class":693},[679,33302,33303],{"class":689},"\"Bret\"",[679,33305,12083],{"class":693},[679,33307,33308,33311,33313,33316],{"class":681,"line":909},[679,33309,33310],{"class":931}," \"email\"",[679,33312,4282],{"class":693},[679,33314,33315],{"class":689},"\"Sincere@april.biz\"",[679,33317,12083],{"class":693},[679,33319,33320,33323],{"class":681,"line":918},[679,33321,33322],{"class":931}," \"address\"",[679,33324,28468],{"class":693},[679,33326,33327,33330,33332,33335],{"class":681,"line":935},[679,33328,33329],{"class":931}," \"street\"",[679,33331,4282],{"class":693},[679,33333,33334],{"class":689},"\"Kulas Light\"",[679,33336,12083],{"class":693},[679,33338,33339,33342,33344,33347],{"class":681,"line":944},[679,33340,33341],{"class":931}," \"suite\"",[679,33343,4282],{"class":693},[679,33345,33346],{"class":689},"\"Apt. 556\"",[679,33348,12083],{"class":693},[679,33350,33351,33354,33356,33359],{"class":681,"line":959},[679,33352,33353],{"class":931}," \"city\"",[679,33355,4282],{"class":693},[679,33357,33358],{"class":689},"\"Gwenborough\"",[679,33360,12083],{"class":693},[679,33362,33363,33366,33368,33371],{"class":681,"line":964},[679,33364,33365],{"class":931}," \"zipcode\"",[679,33367,4282],{"class":693},[679,33369,33370],{"class":689},"\"92998-3874\"",[679,33372,12083],{"class":693},[679,33374,33375,33378],{"class":681,"line":977},[679,33376,33377],{"class":931}," \"geo\"",[679,33379,28468],{"class":693},[679,33381,33382,33385,33387,33390],{"class":681,"line":982},[679,33383,33384],{"class":931}," \"lat\"",[679,33386,4282],{"class":693},[679,33388,33389],{"class":689},"\"-37.3159\"",[679,33391,12083],{"class":693},[679,33393,33394,33397,33399],{"class":681,"line":988},[679,33395,33396],{"class":931}," \"lng\"",[679,33398,4282],{"class":693},[679,33400,33401],{"class":689},"\"81.1496\"\n",[679,33403,33404],{"class":681,"line":993},[679,33405,11804],{"class":693},[679,33407,33408],{"class":681,"line":2129},[679,33409,28763],{"class":693},[679,33411,33412,33415,33417,33420],{"class":681,"line":2140},[679,33413,33414],{"class":931}," \"phone\"",[679,33416,4282],{"class":693},[679,33418,33419],{"class":689},"\"1-770-736-8031 x56442\"",[679,33421,12083],{"class":693},[679,33423,33424,33427,33429,33432],{"class":681,"line":2145},[679,33425,33426],{"class":931}," \"website\"",[679,33428,4282],{"class":693},[679,33430,33431],{"class":689},"\"hildegard.org\"",[679,33433,12083],{"class":693},[679,33435,33436,33439],{"class":681,"line":2154},[679,33437,33438],{"class":931}," \"company\"",[679,33440,28468],{"class":693},[679,33442,33443,33446,33448,33451],{"class":681,"line":2159},[679,33444,33445],{"class":931}," \"name\"",[679,33447,4282],{"class":693},[679,33449,33450],{"class":689},"\"Romaguera-Crona\"",[679,33452,12083],{"class":693},[679,33454,33455,33458,33460,33463],{"class":681,"line":2164},[679,33456,33457],{"class":931}," \"catchPhrase\"",[679,33459,4282],{"class":693},[679,33461,33462],{"class":689},"\"Multi-layered client-server neural-net\"",[679,33464,12083],{"class":693},[679,33466,33467,33470,33472],{"class":681,"line":3134},[679,33468,33469],{"class":931}," \"bs\"",[679,33471,4282],{"class":693},[679,33473,33474],{"class":689},"\"harness real-time e-markets\"\n",[679,33476,33477],{"class":681,"line":3139},[679,33478,985],{"class":693},[679,33480,33481],{"class":681,"line":3144},[679,33482,28483],{"class":693},[651,33484,33485],{},"Now that you have a list of users saved you need to model a domain after a user. You could create relationships between each of your domain models but to keep this simple I would just store all of this data in a single user table. To accomplish this you are going to use an embedded address and company domain.",[669,33487,33489],{"className":4107,"code":33488,"language":4109,"meta":674,"style":674},"@Data\n@AllArgsConstructor\n@Entity\npublic class User {\n\n @Id\n @GeneratedValue( strategy = GenerationType.AUTO )\n private Long id;\n private String name;\n private String username;\n private String email;\n private String phone;\n private String website;\n\n @Embedded\n private Address address;\n @Embedded\n private Company company;\n\n public User() {}\n\n}\n",[676,33490,33491,33497,33504,33510,33520,33524,33531,33548,33554,33560,33566,33572,33579,33586,33590,33597,33604,33610,33617,33621,33629,33633],{"__ignoreMap":674},[679,33492,33493,33495],{"class":681,"line":682},[679,33494,4116],{"class":693},[679,33496,15305],{"class":685},[679,33498,33499,33501],{"class":681,"line":790},[679,33500,4116],{"class":693},[679,33502,33503],{"class":685},"AllArgsConstructor\n",[679,33505,33506,33508],{"class":681,"line":892},[679,33507,4116],{"class":693},[679,33509,11234],{"class":685},[679,33511,33512,33514,33516,33518],{"class":681,"line":901},[679,33513,6073],{"class":685},[679,33515,4512],{"class":685},[679,33517,881],{"class":880},[679,33519,884],{"class":693},[679,33521,33522],{"class":681,"line":909},[679,33523,889],{"emptyLinePlaceholder":797},[679,33525,33526,33528],{"class":681,"line":918},[679,33527,6872],{"class":693},[679,33529,33530],{"class":685},"Id\n",[679,33532,33533,33535,33538,33540,33543,33545],{"class":681,"line":935},[679,33534,6872],{"class":693},[679,33536,33537],{"class":685},"GeneratedValue",[679,33539,1234],{"class":693},[679,33541,33542],{"class":931},"strategy",[679,33544,6883],{"class":685},[679,33546,33547],{"class":693}," GenerationType.AUTO )\n",[679,33549,33550,33552],{"class":681,"line":944},[679,33551,9232],{"class":685},[679,33553,11268],{"class":693},[679,33555,33556,33558],{"class":681,"line":959},[679,33557,9232],{"class":685},[679,33559,16319],{"class":693},[679,33561,33562,33564],{"class":681,"line":964},[679,33563,9232],{"class":685},[679,33565,11275],{"class":693},[679,33567,33568,33570],{"class":681,"line":977},[679,33569,9232],{"class":685},[679,33571,13626],{"class":693},[679,33573,33574,33576],{"class":681,"line":982},[679,33575,9232],{"class":685},[679,33577,33578],{"class":693}," String phone;\n",[679,33580,33581,33583],{"class":681,"line":988},[679,33582,9232],{"class":685},[679,33584,33585],{"class":693}," String website;\n",[679,33587,33588],{"class":681,"line":993},[679,33589,889],{"emptyLinePlaceholder":797},[679,33591,33592,33594],{"class":681,"line":2129},[679,33593,6872],{"class":693},[679,33595,33596],{"class":685},"Embedded\n",[679,33598,33599,33601],{"class":681,"line":2140},[679,33600,9232],{"class":685},[679,33602,33603],{"class":693}," Address address;\n",[679,33605,33606,33608],{"class":681,"line":2145},[679,33607,6872],{"class":693},[679,33609,33596],{"class":685},[679,33611,33612,33614],{"class":681,"line":2154},[679,33613,9232],{"class":685},[679,33615,33616],{"class":693}," Company company;\n",[679,33618,33619],{"class":681,"line":2159},[679,33620,889],{"emptyLinePlaceholder":797},[679,33622,33623,33625,33627],{"class":681,"line":2164},[679,33624,6089],{"class":685},[679,33626,881],{"class":880},[679,33628,11295],{"class":693},[679,33630,33631],{"class":681,"line":3134},[679,33632,889],{"emptyLinePlaceholder":797},[679,33634,33635],{"class":681,"line":3139},[679,33636,996],{"class":693},[669,33638,33640],{"className":4107,"code":33639,"language":4109,"meta":674,"style":674},"@Data\n@AllArgsConstructor\n@Embeddable\npublic class Address {\n\n private String street;\n private String suite;\n private String city;\n private String zipcode;\n\n @Embedded\n private Geo geo;\n\n public Address() {}\n}\n",[676,33641,33642,33648,33654,33661,33672,33676,33683,33690,33697,33704,33708,33714,33721,33725,33733],{"__ignoreMap":674},[679,33643,33644,33646],{"class":681,"line":682},[679,33645,4116],{"class":693},[679,33647,15305],{"class":685},[679,33649,33650,33652],{"class":681,"line":790},[679,33651,4116],{"class":693},[679,33653,33503],{"class":685},[679,33655,33656,33658],{"class":681,"line":892},[679,33657,4116],{"class":693},[679,33659,33660],{"class":685},"Embeddable\n",[679,33662,33663,33665,33667,33670],{"class":681,"line":901},[679,33664,6073],{"class":685},[679,33666,4512],{"class":685},[679,33668,33669],{"class":880}," Address",[679,33671,884],{"class":693},[679,33673,33674],{"class":681,"line":909},[679,33675,889],{"emptyLinePlaceholder":797},[679,33677,33678,33680],{"class":681,"line":918},[679,33679,9232],{"class":685},[679,33681,33682],{"class":693}," String street;\n",[679,33684,33685,33687],{"class":681,"line":935},[679,33686,9232],{"class":685},[679,33688,33689],{"class":693}," String suite;\n",[679,33691,33692,33694],{"class":681,"line":944},[679,33693,9232],{"class":685},[679,33695,33696],{"class":693}," String city;\n",[679,33698,33699,33701],{"class":681,"line":959},[679,33700,9232],{"class":685},[679,33702,33703],{"class":693}," String zipcode;\n",[679,33705,33706],{"class":681,"line":964},[679,33707,889],{"emptyLinePlaceholder":797},[679,33709,33710,33712],{"class":681,"line":977},[679,33711,6872],{"class":693},[679,33713,33596],{"class":685},[679,33715,33716,33718],{"class":681,"line":982},[679,33717,9232],{"class":685},[679,33719,33720],{"class":693}," Geo geo;\n",[679,33722,33723],{"class":681,"line":988},[679,33724,889],{"emptyLinePlaceholder":797},[679,33726,33727,33729,33731],{"class":681,"line":993},[679,33728,6089],{"class":685},[679,33730,33669],{"class":880},[679,33732,11295],{"class":693},[679,33734,33735],{"class":681,"line":2129},[679,33736,996],{"class":693},[669,33738,33740],{"className":4107,"code":33739,"language":4109,"meta":674,"style":674},"@Data\n@AllArgsConstructor\n@Embeddable\npublic class Geo {\n\n private String lat;\n private String lng;\n\n public Geo() {}\n\n}\n",[676,33741,33742,33748,33754,33760,33771,33775,33782,33789,33793,33801,33805],{"__ignoreMap":674},[679,33743,33744,33746],{"class":681,"line":682},[679,33745,4116],{"class":693},[679,33747,15305],{"class":685},[679,33749,33750,33752],{"class":681,"line":790},[679,33751,4116],{"class":693},[679,33753,33503],{"class":685},[679,33755,33756,33758],{"class":681,"line":892},[679,33757,4116],{"class":693},[679,33759,33660],{"class":685},[679,33761,33762,33764,33766,33769],{"class":681,"line":901},[679,33763,6073],{"class":685},[679,33765,4512],{"class":685},[679,33767,33768],{"class":880}," Geo",[679,33770,884],{"class":693},[679,33772,33773],{"class":681,"line":909},[679,33774,889],{"emptyLinePlaceholder":797},[679,33776,33777,33779],{"class":681,"line":918},[679,33778,9232],{"class":685},[679,33780,33781],{"class":693}," String lat;\n",[679,33783,33784,33786],{"class":681,"line":935},[679,33785,9232],{"class":685},[679,33787,33788],{"class":693}," String lng;\n",[679,33790,33791],{"class":681,"line":944},[679,33792,889],{"emptyLinePlaceholder":797},[679,33794,33795,33797,33799],{"class":681,"line":959},[679,33796,6089],{"class":685},[679,33798,33768],{"class":880},[679,33800,11295],{"class":693},[679,33802,33803],{"class":681,"line":964},[679,33804,889],{"emptyLinePlaceholder":797},[679,33806,33807],{"class":681,"line":977},[679,33808,996],{"class":693},[669,33810,33812],{"className":4107,"code":33811,"language":4109,"meta":674,"style":674},"@Data\n@AllArgsConstructor\n@Embeddable\npublic class Company {\n\n @Column( name = \"company_name\")\n private String name;\n private String catchPhrase;\n private String bs;\n\n public Company() {}\n\n}\n",[676,33813,33814,33820,33826,33832,33843,33847,33865,33871,33878,33885,33889,33897,33901],{"__ignoreMap":674},[679,33815,33816,33818],{"class":681,"line":682},[679,33817,4116],{"class":693},[679,33819,15305],{"class":685},[679,33821,33822,33824],{"class":681,"line":790},[679,33823,4116],{"class":693},[679,33825,33503],{"class":685},[679,33827,33828,33830],{"class":681,"line":892},[679,33829,4116],{"class":693},[679,33831,33660],{"class":685},[679,33833,33834,33836,33838,33841],{"class":681,"line":901},[679,33835,6073],{"class":685},[679,33837,4512],{"class":685},[679,33839,33840],{"class":880}," Company",[679,33842,884],{"class":693},[679,33844,33845],{"class":681,"line":909},[679,33846,889],{"emptyLinePlaceholder":797},[679,33848,33849,33851,33854,33856,33858,33860,33863],{"class":681,"line":918},[679,33850,6872],{"class":693},[679,33852,33853],{"class":685},"Column",[679,33855,1234],{"class":693},[679,33857,16334],{"class":931},[679,33859,6883],{"class":685},[679,33861,33862],{"class":689}," \"company_name\"",[679,33864,1339],{"class":693},[679,33866,33867,33869],{"class":681,"line":935},[679,33868,9232],{"class":685},[679,33870,16319],{"class":693},[679,33872,33873,33875],{"class":681,"line":944},[679,33874,9232],{"class":685},[679,33876,33877],{"class":693}," String catchPhrase;\n",[679,33879,33880,33882],{"class":681,"line":959},[679,33881,9232],{"class":685},[679,33883,33884],{"class":693}," String bs;\n",[679,33886,33887],{"class":681,"line":964},[679,33888,889],{"emptyLinePlaceholder":797},[679,33890,33891,33893,33895],{"class":681,"line":977},[679,33892,6089],{"class":685},[679,33894,33840],{"class":880},[679,33896,11295],{"class":693},[679,33898,33899],{"class":681,"line":982},[679,33900,889],{"emptyLinePlaceholder":797},[679,33902,33903],{"class":681,"line":988},[679,33904,996],{"class":693},[5909,33906,33908],{"id":33907},"spring-boot-rest-application","Spring Boot REST Application",[651,33910,33911],{},"Now that you have our domain model in place you are going to build out a REST controller that uses a service & Repository to list and save data.",[669,33913,33915],{"className":4107,"code":33914,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/users\")\npublic class UserController {\n\n private final UserService userService;\n\n public UserController(UserService userService) {\n this.userService = userService;\n }\n\n @GetMapping(\"/list\")\n public Iterable\u003CUser> list() {\n return userService.list();\n }\n}\n",[676,33916,33917,33923,33936,33946,33950,33959,33963,33977,33989,33993,33997,34009,34024,34035,34039],{"__ignoreMap":674},[679,33918,33919,33921],{"class":681,"line":682},[679,33920,4116],{"class":693},[679,33922,9212],{"class":685},[679,33924,33925,33927,33929,33931,33934],{"class":681,"line":790},[679,33926,4116],{"class":693},[679,33928,9275],{"class":685},[679,33930,745],{"class":693},[679,33932,33933],{"class":689},"\"/users\"",[679,33935,1339],{"class":693},[679,33937,33938,33940,33942,33944],{"class":681,"line":892},[679,33939,6073],{"class":685},[679,33941,4512],{"class":685},[679,33943,2013],{"class":880},[679,33945,884],{"class":693},[679,33947,33948],{"class":681,"line":901},[679,33949,889],{"emptyLinePlaceholder":797},[679,33951,33952,33954,33956],{"class":681,"line":909},[679,33953,9232],{"class":685},[679,33955,12768],{"class":685},[679,33957,33958],{"class":693}," UserService userService;\n",[679,33960,33961],{"class":681,"line":918},[679,33962,889],{"emptyLinePlaceholder":797},[679,33964,33965,33967,33969,33972,33975],{"class":681,"line":935},[679,33966,6089],{"class":685},[679,33968,2013],{"class":880},[679,33970,33971],{"class":693},"(UserService ",[679,33973,33974],{"class":2099},"userService",[679,33976,4390],{"class":693},[679,33978,33979,33981,33984,33986],{"class":681,"line":944},[679,33980,7862],{"class":931},[679,33982,33983],{"class":693},".userService ",[679,33985,686],{"class":685},[679,33987,33988],{"class":693}," userService;\n",[679,33990,33991],{"class":681,"line":959},[679,33992,985],{"class":693},[679,33994,33995],{"class":681,"line":964},[679,33996,889],{"emptyLinePlaceholder":797},[679,33998,33999,34001,34003,34005,34007],{"class":681,"line":977},[679,34000,6872],{"class":693},[679,34002,20852],{"class":685},[679,34004,745],{"class":693},[679,34006,12889],{"class":689},[679,34008,1339],{"class":693},[679,34010,34011,34013,34015,34018,34020,34022],{"class":681,"line":982},[679,34012,6089],{"class":685},[679,34014,20875],{"class":693},[679,34016,34017],{"class":685},"User",[679,34019,20881],{"class":693},[679,34021,7623],{"class":880},[679,34023,2667],{"class":693},[679,34025,34026,34028,34031,34033],{"class":681,"line":988},[679,34027,9444],{"class":685},[679,34029,34030],{"class":693}," userService.",[679,34032,7623],{"class":880},[679,34034,9317],{"class":693},[679,34036,34037],{"class":681,"line":993},[679,34038,985],{"class":693},[679,34040,34041],{"class":681,"line":2129},[679,34042,996],{"class":693},[651,34044,34045],{},"The important thing about our service here is that it takes a list of users and calls our repository to save them all at once.",[669,34047,34049],{"className":4107,"code":34048,"language":4109,"meta":674,"style":674},"@Service\npublic class UserService {\n\n private final UserRepository userRepository;\n\n public UserService(UserRepository userRepository) {\n this.userRepository = userRepository;\n }\n\n public Iterable\u003CUser> list() {\n return userRepository.findAll();\n }\n\n public Iterable\u003CUser> save(List\u003CUser> users) {\n return userRepository.save(users);\n }\n\n}\n",[676,34050,34051,34057,34068,34072,34081,34085,34099,34111,34115,34119,34133,34145,34149,34153,34177,34188,34192,34196],{"__ignoreMap":674},[679,34052,34053,34055],{"class":681,"line":682},[679,34054,4116],{"class":693},[679,34056,9486],{"class":685},[679,34058,34059,34061,34063,34066],{"class":681,"line":790},[679,34060,6073],{"class":685},[679,34062,4512],{"class":685},[679,34064,34065],{"class":880}," UserService",[679,34067,884],{"class":693},[679,34069,34070],{"class":681,"line":892},[679,34071,889],{"emptyLinePlaceholder":797},[679,34073,34074,34076,34078],{"class":681,"line":901},[679,34075,9232],{"class":685},[679,34077,12768],{"class":685},[679,34079,34080],{"class":693}," UserRepository userRepository;\n",[679,34082,34083],{"class":681,"line":909},[679,34084,889],{"emptyLinePlaceholder":797},[679,34086,34087,34089,34091,34094,34097],{"class":681,"line":918},[679,34088,6089],{"class":685},[679,34090,34065],{"class":880},[679,34092,34093],{"class":693},"(UserRepository ",[679,34095,34096],{"class":2099},"userRepository",[679,34098,4390],{"class":693},[679,34100,34101,34103,34106,34108],{"class":681,"line":935},[679,34102,7862],{"class":931},[679,34104,34105],{"class":693},".userRepository ",[679,34107,686],{"class":685},[679,34109,34110],{"class":693}," userRepository;\n",[679,34112,34113],{"class":681,"line":944},[679,34114,985],{"class":693},[679,34116,34117],{"class":681,"line":959},[679,34118,889],{"emptyLinePlaceholder":797},[679,34120,34121,34123,34125,34127,34129,34131],{"class":681,"line":964},[679,34122,6089],{"class":685},[679,34124,20875],{"class":693},[679,34126,34017],{"class":685},[679,34128,20881],{"class":693},[679,34130,7623],{"class":880},[679,34132,2667],{"class":693},[679,34134,34135,34137,34140,34143],{"class":681,"line":977},[679,34136,9444],{"class":685},[679,34138,34139],{"class":693}," userRepository.",[679,34141,34142],{"class":880},"findAll",[679,34144,9317],{"class":693},[679,34146,34147],{"class":681,"line":982},[679,34148,985],{"class":693},[679,34150,34151],{"class":681,"line":988},[679,34152,889],{"emptyLinePlaceholder":797},[679,34154,34155,34157,34159,34161,34163,34165,34168,34170,34172,34175],{"class":681,"line":993},[679,34156,6089],{"class":685},[679,34158,20875],{"class":693},[679,34160,34017],{"class":685},[679,34162,20881],{"class":693},[679,34164,7629],{"class":880},[679,34166,34167],{"class":693},"(List\u003C",[679,34169,34017],{"class":685},[679,34171,20881],{"class":693},[679,34173,34174],{"class":2099},"users",[679,34176,4390],{"class":693},[679,34178,34179,34181,34183,34185],{"class":681,"line":2129},[679,34180,9444],{"class":685},[679,34182,34139],{"class":693},[679,34184,7629],{"class":880},[679,34186,34187],{"class":693},"(users);\n",[679,34189,34190],{"class":681,"line":2140},[679,34191,985],{"class":693},[679,34193,34194],{"class":681,"line":2145},[679,34195,889],{"emptyLinePlaceholder":797},[679,34197,34198],{"class":681,"line":2154},[679,34199,996],{"class":693},[5909,34201,34203],{"id":34202},"read-write-json-data-to-database","Read & Write JSON Data to Database",[651,34205,34206,34207,34212,34213,34215],{},"Before you can write to a database you need to make sure that you have a database configured. In this tutorial you are using an in-memory H2 database. The in-memory part just means that each time we restart the application the database will be wiped clean. If you're new H2 you can check out ",[812,34208,34211],{"href":34209,"rel":34210},"https://youtu.be/tSJW5NKPhcM",[816],"this tutorial",". For now you will need to open up ",[676,34214,16242],{}," and add the following lines:",[669,34217,34220],{"className":34218,"code":34219,"language":11464},[16247],"spring.h2.console.enabled=true\nspring.datasource.generate-unique-name=false\nspring.datasource.name=users\n",[676,34221,34219],{"__ignoreMap":674},[651,34223,34224,34225,664],{},"With our application in place, there is only one step left to do. To read the JSON and write it to a database you are going to use a command-line runner. If you're not familiar with a command-line runner it is an interface that you can implement to executed some code before the application starts up. If you want to read more on this you can check out ",[812,34226,34228],{"href":34227},"/blog/2017/04/07/spring-boot-command-line-runner/","my blog post here",[651,34230,34231],{},"When you bring in the Web dependency you also get the jackson-databind dependency. This contains an Object Mapper class which allows us to easily map JSON data to our domain model.",[669,34233,34235],{"className":9101,"code":34234,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>com.fasterxml.jackson.core\u003C/groupId>\n \u003CartifactId>jackson-databind\u003C/artifactId>\n\u003C/dependency>\n",[676,34236,34237,34241,34246,34251],{"__ignoreMap":674},[679,34238,34239],{"class":681,"line":682},[679,34240,9110],{},[679,34242,34243],{"class":681,"line":790},[679,34244,34245],{}," \u003CgroupId>com.fasterxml.jackson.core\u003C/groupId>\n",[679,34247,34248],{"class":681,"line":892},[679,34249,34250],{}," \u003CartifactId>jackson-databind\u003C/artifactId>\n",[679,34252,34253],{"class":681,"line":901},[679,34254,9125],{},[651,34256,34257],{},"Using that Object Mapper and our well-crafted domain model from above we have what we need to accomplish our goal. You can read in users.json file and then map that data to the domain model.",[669,34259,34261],{"className":4107,"code":34260,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class JsondbApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(JsondbApplication.class, args);\n }\n\n @Bean\n CommandLineRunner runner(UserService userService) {\n return args -> {\n // read json and write to db\n ObjectMapper mapper = new ObjectMapper();\n TypeReference\u003CList\u003CUser>> typeReference = new TypeReference\u003CList\u003CUser>>(){};\n InputStream inputStream = TypeReference.class.getResourceAsStream(\"/json/users.json\");\n try {\n List\u003CUser> users = mapper.readValue(inputStream,typeReference);\n userService.save(users);\n System.out.println(\"Users Saved!\");\n } catch (IOException e){\n System.out.println(\"Unable to save users: \" + e.getMessage());\n }\n };\n }\n}\n",[676,34262,34263,34269,34280,34284,34304,34313,34317,34321,34327,34339,34349,34354,34368,34390,34410,34417,34438,34447,34461,34475,34494,34498,34502,34506],{"__ignoreMap":674},[679,34264,34265,34267],{"class":681,"line":682},[679,34266,4116],{"class":693},[679,34268,6068],{"class":685},[679,34270,34271,34273,34275,34278],{"class":681,"line":790},[679,34272,6073],{"class":685},[679,34274,4512],{"class":685},[679,34276,34277],{"class":880}," JsondbApplication",[679,34279,884],{"class":693},[679,34281,34282],{"class":681,"line":892},[679,34283,889],{"emptyLinePlaceholder":797},[679,34285,34286,34288,34290,34292,34294,34296,34298,34300,34302],{"class":681,"line":901},[679,34287,6089],{"class":685},[679,34289,6092],{"class":685},[679,34291,6095],{"class":685},[679,34293,6098],{"class":880},[679,34295,745],{"class":693},[679,34297,4758],{"class":685},[679,34299,16901],{"class":693},[679,34301,6108],{"class":2099},[679,34303,4390],{"class":693},[679,34305,34306,34308,34310],{"class":681,"line":909},[679,34307,6115],{"class":693},[679,34309,6118],{"class":880},[679,34311,34312],{"class":693},"(JsondbApplication.class, args);\n",[679,34314,34315],{"class":681,"line":918},[679,34316,985],{"class":693},[679,34318,34319],{"class":681,"line":935},[679,34320,889],{"emptyLinePlaceholder":797},[679,34322,34323,34325],{"class":681,"line":944},[679,34324,6872],{"class":693},[679,34326,16929],{"class":685},[679,34328,34329,34331,34333,34335,34337],{"class":681,"line":959},[679,34330,20982],{"class":693},[679,34332,16939],{"class":880},[679,34334,33971],{"class":693},[679,34336,33974],{"class":2099},[679,34338,4390],{"class":693},[679,34340,34341,34343,34345,34347],{"class":681,"line":964},[679,34342,9444],{"class":685},[679,34344,16952],{"class":693},[679,34346,16955],{"class":685},[679,34348,884],{"class":693},[679,34350,34351],{"class":681,"line":977},[679,34352,34353],{"class":1400}," // read json and write to db\n",[679,34355,34356,34359,34361,34363,34366],{"class":681,"line":982},[679,34357,34358],{"class":693}," ObjectMapper mapper ",[679,34360,686],{"class":685},[679,34362,2054],{"class":685},[679,34364,34365],{"class":880}," ObjectMapper",[679,34367,9317],{"class":693},[679,34369,34370,34373,34375,34378,34380,34382,34385,34387],{"class":681,"line":988},[679,34371,34372],{"class":693}," TypeReference\u003CList\u003C",[679,34374,34017],{"class":685},[679,34376,34377],{"class":693},">> typeReference ",[679,34379,686],{"class":685},[679,34381,2054],{"class":685},[679,34383,34384],{"class":693}," TypeReference\u003CList\u003C",[679,34386,34017],{"class":685},[679,34388,34389],{"class":693},">>(){};\n",[679,34391,34392,34395,34397,34400,34403,34405,34408],{"class":681,"line":993},[679,34393,34394],{"class":693}," InputStream inputStream ",[679,34396,686],{"class":685},[679,34398,34399],{"class":693}," TypeReference.class.",[679,34401,34402],{"class":880},"getResourceAsStream",[679,34404,745],{"class":693},[679,34406,34407],{"class":689},"\"/json/users.json\"",[679,34409,1208],{"class":693},[679,34411,34412,34415],{"class":681,"line":2129},[679,34413,34414],{"class":685}," try",[679,34416,884],{"class":693},[679,34418,34419,34422,34424,34427,34429,34432,34435],{"class":681,"line":2140},[679,34420,34421],{"class":693}," List\u003C",[679,34423,34017],{"class":685},[679,34425,34426],{"class":693},"> users ",[679,34428,686],{"class":685},[679,34430,34431],{"class":693}," mapper.",[679,34433,34434],{"class":880},"readValue",[679,34436,34437],{"class":693},"(inputStream,typeReference);\n",[679,34439,34440,34443,34445],{"class":681,"line":2145},[679,34441,34442],{"class":693}," userService.",[679,34444,7629],{"class":880},[679,34446,34187],{"class":693},[679,34448,34449,34452,34454,34456,34459],{"class":681,"line":2154},[679,34450,34451],{"class":693}," System.out.",[679,34453,1729],{"class":880},[679,34455,745],{"class":693},[679,34457,34458],{"class":689},"\"Users Saved!\"",[679,34460,1208],{"class":693},[679,34462,34463,34466,34468,34471,34473],{"class":681,"line":2159},[679,34464,34465],{"class":693}," } ",[679,34467,9394],{"class":685},[679,34469,34470],{"class":693}," (IOException ",[679,34472,9400],{"class":2099},[679,34474,9533],{"class":693},[679,34476,34477,34479,34481,34483,34486,34488,34490,34492],{"class":681,"line":2164},[679,34478,34451],{"class":693},[679,34480,1729],{"class":880},[679,34482,745],{"class":693},[679,34484,34485],{"class":689},"\"Unable to save users: \"",[679,34487,3059],{"class":685},[679,34489,9425],{"class":693},[679,34491,9428],{"class":880},[679,34493,9431],{"class":693},[679,34495,34496],{"class":681,"line":3134},[679,34497,25517],{"class":693},[679,34499,34500],{"class":681,"line":3139},[679,34501,17018],{"class":693},[679,34503,34504],{"class":681,"line":3144},[679,34505,985],{"class":693},[679,34507,34508],{"class":681,"line":3149},[679,34509,996],{"class":693},[651,34511,34512,34513,34517],{},"If you run our application and look visit the ",[812,34514,34516],{"href":16733,"rel":34515},[816],"H2 Database console"," you should see the 10 records have been inserted. Congratulations, you just read data from a JSON file and inserted it into a database.",[651,34519,34520],{},[660,34521],{"alt":34522,"src":34523},"Spring Boot H2 Console","./spring-boot-h2-console.png",[4542,34525,9042],{"id":9041},[651,34527,34528],{},"I often get quetions like the one we looked at today. The first thing you need to do is to break down this problem into smaller pieces. The first problem to solve for was creating some sample JSON, modeling our domain model after it and reading it in. Once you have that you can move on to persisting that data into a database.",[651,34530,34531,34532,664],{},"If you want to check out the source code head over to ",[812,34533,17458],{"href":34534,"rel":34535},"https://github.com/cfaddict/spring-boot-jsontodb",[816],[786,34537,34538],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":34540},[34541,34546],{"id":26471,"depth":790,"text":18978,"children":34542},[34543,34544,34545],{"id":33245,"depth":892,"text":33246},{"id":33907,"depth":892,"text":33908},{"id":34202,"depth":892,"text":34203},{"id":9041,"depth":790,"text":9042},{"slug":34548,"published":797,"date":34549,"updatedOn":17481,"tags":34550,"cover":34551,"video":34552},"read-json-data-spring-boot-write-database","2017-07-05T12:00:27-04:00",[7055],"./pexels-photo-374899-760x506.jpeg","https://www.youtube.com/embed/rGdKmF2UzSc",{"title":618,"description":618},"blog/2017/07/05/read-json-data-spring-boot-write-database","fF9Oy-o3DdJ0rC6hdAllj0IpC5G0g81lk8GMiYWN69w",{"id":34557,"title":615,"body":34558,"description":615,"extension":793,"meta":35169,"navigation":797,"path":616,"seo":35174,"stem":35175,"__hash__":35176},"content/blog/2017/07/24/spring-boot-application-failed-start.md",{"type":648,"value":34559,"toc":35164},[34560,34563,34567,34570,35122,35125,35129,35132,35138,35144,35146,35154,35161],[651,34561,34562],{},"Errors are a part of the development lifecycle and there is no way getting around them. I know we all like to think we are top notch coders and that we are error prone but I can assure you that isn't the case. When errors come up it is nice to have as much information at our disposal as possible. We don't have time to search the web for hours of cryptic stack messages to find our solution. In this article, I am going to show you an error you might come across and how Spring Boot 1.4 has made it a little bit easier to decipher error messages. ",[4542,34564,34566],{"id":34565},"analysis-of-startup-failures","Analysis of Startup Failures",[651,34568,34569],{},"Prior to Spring Boot version 1.4, it was a little tough decrypting error messages that were causing our applications not to start. If you already have an application running on port 8080 and try to start up another one you might have seen an error that looked like this. ",[669,34571,34573],{"className":5851,"code":34572,"language":5853,"meta":674,"style":674},"2016-02-16 17:46:14.334 ERROR 24753 --- \\[ main\\] o.s.boot.SpringApplication : Application startup failed\n\njava.lang.RuntimeException: java.net.BindException: Address already in use\n at io.undertow.Undertow.start(Undertow.java:181) ~\\[undertow-core-1.3.14.Final.jar:1.3.14.Final\\]\n at org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainer.start(UndertowEmbeddedServletContainer.java:121) ~\\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.startEmbeddedServletContainer(EmbeddedWebApplicationContext.java:293) ~\\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh(EmbeddedWebApplicationContext.java:141) ~\\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) ~\\[spring-context-4.2.4.RELEASE.jar:4.2.4.RELEASE\\]\n at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~\\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766) \\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361) \\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) \\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191) \\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180) \\[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE\\]\n at sample.undertow.SampleUndertowApplication.main(SampleUndertowApplication.java:26) \\[classes/:na\\]\nCaused by: java.net.BindException: Address already in use\n at sun.nio.ch.Net.bind0(Native Method) ~\\[na:1.8.0\\_60\\]\n at sun.nio.ch.Net.bind(Net.java:433) ~\\[na:1.8.0\\_60\\]\n at sun.nio.ch.Net.bind(Net.java:425) ~\\[na:1.8.0\\_60\\]\n at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:223) ~\\[na:1.8.0\\_60\\]\n at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:74) ~\\[na:1.8.0\\_60\\]\n at org.xnio.nio.NioXnioWorker.createTcpConnectionServer(NioXnioWorker.java:190) ~\\[xnio-nio-3.3.4.Final.jar:3.3.4.Final\\]\n at org.xnio.XnioWorker.createStreamConnectionServer(XnioWorker.java:243) ~\\[xnio-api-3.3.4.Final.jar:3.3.4.Final\\]\n at io.undertow.Undertow.start(Undertow.java:137) ~\\[undertow-core-1.3.14.Final.jar:1.3.14.Final\\]\n ... 11 common frames omitted\n",[676,34574,34575,34612,34616,34634,34657,34680,34702,34724,34747,34769,34789,34809,34829,34848,34867,34888,34904,34935,34961,34986,35012,35038,35061,35084,35105],{"__ignoreMap":674},[679,34576,34577,34580,34583,34586,34589,34591,34593,34596,34598,34601,34604,34606,34609],{"class":681,"line":682},[679,34578,34579],{"class":880},"2016-02-16",[679,34581,34582],{"class":689}," 17:46:14.334",[679,34584,34585],{"class":689}," ERROR",[679,34587,34588],{"class":931}," 24753",[679,34590,18568],{"class":931},[679,34592,1411],{"class":931},[679,34594,34595],{"class":689}," main",[679,34597,4477],{"class":931},[679,34599,34600],{"class":689}," o.s.boot.SpringApplication",[679,34602,34603],{"class":689}," :",[679,34605,16878],{"class":689},[679,34607,34608],{"class":689}," startup",[679,34610,34611],{"class":689}," failed\n",[679,34613,34614],{"class":681,"line":790},[679,34615,889],{"emptyLinePlaceholder":797},[679,34617,34618,34621,34624,34626,34629,34631],{"class":681,"line":892},[679,34619,34620],{"class":880},"java.lang.RuntimeException:",[679,34622,34623],{"class":689}," java.net.BindException:",[679,34625,33669],{"class":689},[679,34627,34628],{"class":689}," already",[679,34630,6997],{"class":689},[679,34632,34633],{"class":689}," use\n",[679,34635,34636,34638,34641,34643,34646,34648,34650,34652,34655],{"class":681,"line":901},[679,34637,8024],{"class":880},[679,34639,34640],{"class":689}," io.undertow.Undertow.start",[679,34642,745],{"class":693},[679,34644,34645],{"class":880},"Undertow.java:181",[679,34647,2378],{"class":693},[679,34649,8037],{"class":689},[679,34651,4471],{"class":931},[679,34653,34654],{"class":689},"undertow-core-1.3.14.Final.jar:1.3.14.Final",[679,34656,1720],{"class":931},[679,34658,34659,34661,34664,34666,34669,34671,34673,34675,34678],{"class":681,"line":909},[679,34660,8024],{"class":880},[679,34662,34663],{"class":689}," org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainer.start",[679,34665,745],{"class":693},[679,34667,34668],{"class":880},"UndertowEmbeddedServletContainer.java:121",[679,34670,2378],{"class":693},[679,34672,8037],{"class":689},[679,34674,4471],{"class":931},[679,34676,34677],{"class":689},"spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE",[679,34679,1720],{"class":931},[679,34681,34682,34684,34687,34689,34692,34694,34696,34698,34700],{"class":681,"line":918},[679,34683,8024],{"class":880},[679,34685,34686],{"class":689}," org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.startEmbeddedServletContainer",[679,34688,745],{"class":693},[679,34690,34691],{"class":880},"EmbeddedWebApplicationContext.java:293",[679,34693,2378],{"class":693},[679,34695,8037],{"class":689},[679,34697,4471],{"class":931},[679,34699,34677],{"class":689},[679,34701,1720],{"class":931},[679,34703,34704,34706,34709,34711,34714,34716,34718,34720,34722],{"class":681,"line":935},[679,34705,8024],{"class":880},[679,34707,34708],{"class":689}," org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh",[679,34710,745],{"class":693},[679,34712,34713],{"class":880},"EmbeddedWebApplicationContext.java:141",[679,34715,2378],{"class":693},[679,34717,8037],{"class":689},[679,34719,4471],{"class":931},[679,34721,34677],{"class":689},[679,34723,1720],{"class":931},[679,34725,34726,34728,34731,34733,34736,34738,34740,34742,34745],{"class":681,"line":944},[679,34727,8024],{"class":880},[679,34729,34730],{"class":689}," org.springframework.context.support.AbstractApplicationContext.refresh",[679,34732,745],{"class":693},[679,34734,34735],{"class":880},"AbstractApplicationContext.java:541",[679,34737,2378],{"class":693},[679,34739,8037],{"class":689},[679,34741,4471],{"class":931},[679,34743,34744],{"class":689},"spring-context-4.2.4.RELEASE.jar:4.2.4.RELEASE",[679,34746,1720],{"class":931},[679,34748,34749,34751,34754,34756,34759,34761,34763,34765,34767],{"class":681,"line":959},[679,34750,8024],{"class":880},[679,34752,34753],{"class":689}," org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh",[679,34755,745],{"class":693},[679,34757,34758],{"class":880},"EmbeddedWebApplicationContext.java:118",[679,34760,2378],{"class":693},[679,34762,8037],{"class":689},[679,34764,4471],{"class":931},[679,34766,34677],{"class":689},[679,34768,1720],{"class":931},[679,34770,34771,34773,34776,34778,34781,34783,34785,34787],{"class":681,"line":964},[679,34772,8024],{"class":880},[679,34774,34775],{"class":689}," org.springframework.boot.SpringApplication.refresh",[679,34777,745],{"class":693},[679,34779,34780],{"class":880},"SpringApplication.java:766",[679,34782,2378],{"class":693},[679,34784,4471],{"class":931},[679,34786,34677],{"class":689},[679,34788,1720],{"class":931},[679,34790,34791,34793,34796,34798,34801,34803,34805,34807],{"class":681,"line":977},[679,34792,8024],{"class":880},[679,34794,34795],{"class":689}," org.springframework.boot.SpringApplication.createAndRefreshContext",[679,34797,745],{"class":693},[679,34799,34800],{"class":880},"SpringApplication.java:361",[679,34802,2378],{"class":693},[679,34804,4471],{"class":931},[679,34806,34677],{"class":689},[679,34808,1720],{"class":931},[679,34810,34811,34813,34816,34818,34821,34823,34825,34827],{"class":681,"line":982},[679,34812,8024],{"class":880},[679,34814,34815],{"class":689}," org.springframework.boot.SpringApplication.run",[679,34817,745],{"class":693},[679,34819,34820],{"class":880},"SpringApplication.java:307",[679,34822,2378],{"class":693},[679,34824,4471],{"class":931},[679,34826,34677],{"class":689},[679,34828,1720],{"class":931},[679,34830,34831,34833,34835,34837,34840,34842,34844,34846],{"class":681,"line":988},[679,34832,8024],{"class":880},[679,34834,34815],{"class":689},[679,34836,745],{"class":693},[679,34838,34839],{"class":880},"SpringApplication.java:1191",[679,34841,2378],{"class":693},[679,34843,4471],{"class":931},[679,34845,34677],{"class":689},[679,34847,1720],{"class":931},[679,34849,34850,34852,34854,34856,34859,34861,34863,34865],{"class":681,"line":993},[679,34851,8024],{"class":880},[679,34853,34815],{"class":689},[679,34855,745],{"class":693},[679,34857,34858],{"class":880},"SpringApplication.java:1180",[679,34860,2378],{"class":693},[679,34862,4471],{"class":931},[679,34864,34677],{"class":689},[679,34866,1720],{"class":931},[679,34868,34869,34871,34874,34876,34879,34881,34883,34886],{"class":681,"line":2129},[679,34870,8024],{"class":880},[679,34872,34873],{"class":689}," sample.undertow.SampleUndertowApplication.main",[679,34875,745],{"class":693},[679,34877,34878],{"class":880},"SampleUndertowApplication.java:26",[679,34880,2378],{"class":693},[679,34882,4471],{"class":931},[679,34884,34885],{"class":689},"classes/:na",[679,34887,1720],{"class":931},[679,34889,34890,34892,34894,34896,34898,34900,34902],{"class":681,"line":2140},[679,34891,19721],{"class":880},[679,34893,19724],{"class":689},[679,34895,34623],{"class":689},[679,34897,33669],{"class":689},[679,34899,34628],{"class":689},[679,34901,6997],{"class":689},[679,34903,34633],{"class":689},[679,34905,34906,34908,34911,34913,34916,34919,34921,34923,34925,34928,34930,34933],{"class":681,"line":2145},[679,34907,8024],{"class":880},[679,34909,34910],{"class":689}," sun.nio.ch.Net.bind0",[679,34912,745],{"class":693},[679,34914,34915],{"class":880},"Native",[679,34917,34918],{"class":689}," Method",[679,34920,2378],{"class":693},[679,34922,8037],{"class":689},[679,34924,4471],{"class":931},[679,34926,34927],{"class":689},"na:1.8.0",[679,34929,3422],{"class":931},[679,34931,34932],{"class":689},"60",[679,34934,1720],{"class":931},[679,34936,34937,34939,34942,34944,34947,34949,34951,34953,34955,34957,34959],{"class":681,"line":2154},[679,34938,8024],{"class":880},[679,34940,34941],{"class":689}," sun.nio.ch.Net.bind",[679,34943,745],{"class":693},[679,34945,34946],{"class":880},"Net.java:433",[679,34948,2378],{"class":693},[679,34950,8037],{"class":689},[679,34952,4471],{"class":931},[679,34954,34927],{"class":689},[679,34956,3422],{"class":931},[679,34958,34932],{"class":689},[679,34960,1720],{"class":931},[679,34962,34963,34965,34967,34969,34972,34974,34976,34978,34980,34982,34984],{"class":681,"line":2159},[679,34964,8024],{"class":880},[679,34966,34941],{"class":689},[679,34968,745],{"class":693},[679,34970,34971],{"class":880},"Net.java:425",[679,34973,2378],{"class":693},[679,34975,8037],{"class":689},[679,34977,4471],{"class":931},[679,34979,34927],{"class":689},[679,34981,3422],{"class":931},[679,34983,34932],{"class":689},[679,34985,1720],{"class":931},[679,34987,34988,34990,34993,34995,34998,35000,35002,35004,35006,35008,35010],{"class":681,"line":2164},[679,34989,8024],{"class":880},[679,34991,34992],{"class":689}," sun.nio.ch.ServerSocketChannelImpl.bind",[679,34994,745],{"class":693},[679,34996,34997],{"class":880},"ServerSocketChannelImpl.java:223",[679,34999,2378],{"class":693},[679,35001,8037],{"class":689},[679,35003,4471],{"class":931},[679,35005,34927],{"class":689},[679,35007,3422],{"class":931},[679,35009,34932],{"class":689},[679,35011,1720],{"class":931},[679,35013,35014,35016,35019,35021,35024,35026,35028,35030,35032,35034,35036],{"class":681,"line":3134},[679,35015,8024],{"class":880},[679,35017,35018],{"class":689}," sun.nio.ch.ServerSocketAdaptor.bind",[679,35020,745],{"class":693},[679,35022,35023],{"class":880},"ServerSocketAdaptor.java:74",[679,35025,2378],{"class":693},[679,35027,8037],{"class":689},[679,35029,4471],{"class":931},[679,35031,34927],{"class":689},[679,35033,3422],{"class":931},[679,35035,34932],{"class":689},[679,35037,1720],{"class":931},[679,35039,35040,35042,35045,35047,35050,35052,35054,35056,35059],{"class":681,"line":3139},[679,35041,8024],{"class":880},[679,35043,35044],{"class":689}," org.xnio.nio.NioXnioWorker.createTcpConnectionServer",[679,35046,745],{"class":693},[679,35048,35049],{"class":880},"NioXnioWorker.java:190",[679,35051,2378],{"class":693},[679,35053,8037],{"class":689},[679,35055,4471],{"class":931},[679,35057,35058],{"class":689},"xnio-nio-3.3.4.Final.jar:3.3.4.Final",[679,35060,1720],{"class":931},[679,35062,35063,35065,35068,35070,35073,35075,35077,35079,35082],{"class":681,"line":3144},[679,35064,8024],{"class":880},[679,35066,35067],{"class":689}," org.xnio.XnioWorker.createStreamConnectionServer",[679,35069,745],{"class":693},[679,35071,35072],{"class":880},"XnioWorker.java:243",[679,35074,2378],{"class":693},[679,35076,8037],{"class":689},[679,35078,4471],{"class":931},[679,35080,35081],{"class":689},"xnio-api-3.3.4.Final.jar:3.3.4.Final",[679,35083,1720],{"class":931},[679,35085,35086,35088,35090,35092,35095,35097,35099,35101,35103],{"class":681,"line":3149},[679,35087,8024],{"class":880},[679,35089,34640],{"class":689},[679,35091,745],{"class":693},[679,35093,35094],{"class":880},"Undertow.java:137",[679,35096,2378],{"class":693},[679,35098,8037],{"class":689},[679,35100,4471],{"class":931},[679,35102,34654],{"class":689},[679,35104,1720],{"class":931},[679,35106,35107,35110,35113,35116,35119],{"class":681,"line":3169},[679,35108,35109],{"class":931}," ...",[679,35111,35112],{"class":931}," 11",[679,35114,35115],{"class":689}," common",[679,35117,35118],{"class":689}," frames",[679,35120,35121],{"class":689}," omitted\n",[651,35123,35124],{},"Now if you that error message enough times you can quickly recognize it but if you're seeing it for the first time it can be a little confusing. ",[4542,35126,35128],{"id":35127},"spring-boot-14-improvement","Spring Boot 1.4 Improvement",[651,35130,35131],{},"Spring Boot will now perform analysis of common start-up failures and provide useful diagnostic information rather than simply logging an exception and its stack trace. This is what the same error message looks like in 1.4+. ",[651,35133,35134],{},[660,35135],{"alt":35136,"src":35137},"Application Failed to Start","./2017-07-24_09-48-54-1024x345.png",[651,35139,35140,35141,22733],{},"With this error message, you can clearly see why our application is not starting. If you still want to see the stack trace of the underlying cause, enable debug logging for ",[676,35142,35143],{},"org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter",[4542,35145,9042],{"id":9041},[651,35147,35148,35149,15266],{},"The Spring Boot team is always listening to feedback to improve the developer experience. If you have issues like this one ",[812,35150,35153],{"href":35151,"rel":35152},"https://github.com/spring-projects/spring-boot",[816],"please consider submitting an issue request",[651,35155,35156],{},[7300,35157,35158,35160],{},[2939,35159,11650],{}," What problems are you facing debugging errors in Spring Boot applications?",[786,35162,35163],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":35165},[35166,35167,35168],{"id":34565,"depth":790,"text":34566},{"id":35127,"depth":790,"text":35128},{"id":9041,"depth":790,"text":9042},{"slug":35170,"published":797,"date":35171,"tags":35172,"cover":35173},"spring-boot-application-failed-start","2017-07-24T09:55:33-04:00",[4109,7055],"./pexels-photo-205316-760x506.png",{"title":615,"description":615},"blog/2017/07/24/spring-boot-application-failed-start","YE6fWoPdXUp6rC6SBGSABp3ztOL7E3_kqP36IxmEjqE",{"id":35178,"title":612,"body":35179,"description":612,"extension":793,"meta":35632,"navigation":797,"path":613,"seo":35637,"stem":35638,"__hash__":35639},"content/blog/2017/07/26/use-hikaricp-next-spring-boot-project.md",{"type":648,"value":35180,"toc":35624},[35181,35190,35193,35200,35214,35227,35244,35258,35262,35265,35298,35301,35315,35318,35392,35396,35399,35566,35569,35589,35593,35606,35610,35616,35618,35621],[651,35182,35183,35184,35189],{},"Performance is something we are all trying to improve on when it comes to our applications. It turns out that there is a very reliable, high performance JDBC connection pool out there that we can start using in our Spring Boot applications today. In this article, we are going to take a look at HikariCP, the CP standing for a connection pool. ",[812,35185,35188],{"href":35186,"rel":35187},"https://brettwooldridge.github.io/HikariCP/",[816],"HikariCP"," is a “zero-overhead” production-quality connection pool.",[4542,35191,35188],{"id":35192},"hikaricp",[651,35194,35195,35196,35199],{},"As I mentioned earlier, ",[812,35197,35188],{"href":35186,"rel":35198},[816]," is a reliable, high-performance JDBC connection pool. What is a connection pool you ask?",[1004,35201,35202],{},[651,35203,35204,35205,35208,35209],{},"In software engineering, a ",[2939,35206,35207],{},"connection pool"," is a cache of database connections maintained so that the connections can be reused when future requests to the database are required. Connection pools are used to enhance the performance of executing commands on a database. - ",[812,35210,35213],{"href":35211,"rel":35212},"https://en.wikipedia.org/wiki/Connection_pool",[816],"Wikipedia",[651,35215,35216,35217,35222,35223,35226],{},"If you weren't already aware you are using the Tomcat pooling Datasource by default. Here is some great information ",[812,35218,35221],{"href":35219,"rel":35220},"http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connect-to-production-database",[816],"from the documentation"," that explains how this is selected. Production database connections can also be auto-configured using a pooling ",[676,35224,35225],{},"DataSource"," . Here’s the algorithm for choosing a specific implementation:",[5316,35228,35229,35235,35238,35241],{},[5332,35230,35231,35232,35234],{},"We prefer the Tomcat pooling ",[676,35233,35225],{}," for its performance and concurrency, so if that is available we always choose it.",[5332,35236,35237],{},"Otherwise, if HikariCP is available we will use it.",[5332,35239,35240],{},"If neither the Tomcat pooling datasource nor HikariCP are available and if Commons DBCP is available we will use it, but we don’t recommend it in production and its support is deprecated.",[5332,35242,35243],{},"Lastly, if Commons DBCP2 is available we will use it.",[651,35245,35246,35247,35250,35251,35254,35255,22733],{},"If you use the ",[676,35248,35249],{},"spring-boot-starter-jdbc"," or ",[676,35252,35253],{},"spring-boot-starter-data-jpa"," ‘starters’ you will automatically get a dependency to ",[676,35256,35257],{},"tomcat-jdbc",[4542,35259,35261],{"id":35260},"using-hikaricp-in-spring-boot","Using HikariCP in Spring Boot",[651,35263,35264],{},"To use HikariCP you can simply add the following dependency to a new or existing project. ",[669,35266,35268],{"className":9101,"code":35267,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>com.zaxxer\u003C/groupId>\n \u003CartifactId>HikariCP\u003C/artifactId>\n \u003Cversion>2.6.1\u003C/version>\n \u003Cscope>compile\u003C/scope>\n\u003C/dependency>\n",[676,35269,35270,35274,35279,35284,35289,35294],{"__ignoreMap":674},[679,35271,35272],{"class":681,"line":682},[679,35273,9110],{},[679,35275,35276],{"class":681,"line":790},[679,35277,35278],{}," \u003CgroupId>com.zaxxer\u003C/groupId>\n",[679,35280,35281],{"class":681,"line":892},[679,35282,35283],{}," \u003CartifactId>HikariCP\u003C/artifactId>\n",[679,35285,35286],{"class":681,"line":901},[679,35287,35288],{}," \u003Cversion>2.6.1\u003C/version>\n",[679,35290,35291],{"class":681,"line":909},[679,35292,35293],{}," \u003Cscope>compile\u003C/scope>\n",[679,35295,35296],{"class":681,"line":918},[679,35297,9125],{},[651,35299,35300],{},"You used to have to configure your own Datasource but thanks to the magic of AutoConfiguration you don't have to anymore. Next, we are going to open up application.properties and add the following configuration. ",[669,35302,35304],{"className":4107,"code":35303,"language":4109,"meta":674,"style":674},"spring.datasource.type=com.zaxxer.hikari.HikariDataSource\n",[676,35305,35306],{"__ignoreMap":674},[679,35307,35308,35310,35312],{"class":681,"line":682},[679,35309,23964],{"class":693},[679,35311,686],{"class":685},[679,35313,35314],{"class":693},"com.zaxxer.hikari.HikariDataSource\n",[651,35316,35317],{},"Now if you run the application you should see something like this in the console letting us know our change was accepted. ",[669,35319,35321],{"className":5851,"code":35320,"language":5853,"meta":674,"style":674},"2017-07-26 07:57:26.345 INFO 1015 --- \\[ main\\] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...\n2017-07-26 07:57:26.505 INFO 1015 --- \\[ main\\] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.\n",[676,35322,35323,35359],{"__ignoreMap":674},[679,35324,35325,35328,35331,35333,35336,35338,35340,35342,35344,35347,35350,35353,35356],{"class":681,"line":682},[679,35326,35327],{"class":880},"2017-07-26",[679,35329,35330],{"class":689}," 07:57:26.345",[679,35332,18562],{"class":689},[679,35334,35335],{"class":931}," 1015",[679,35337,18568],{"class":931},[679,35339,1411],{"class":931},[679,35341,34595],{"class":689},[679,35343,4477],{"class":931},[679,35345,35346],{"class":689}," com.zaxxer.hikari.HikariDataSource",[679,35348,35349],{"class":689}," :",[679,35351,35352],{"class":689}," HikariPool-1",[679,35354,35355],{"class":689}," -",[679,35357,35358],{"class":689}," Starting...\n",[679,35360,35361,35363,35366,35368,35370,35372,35374,35376,35378,35380,35382,35384,35386,35389],{"class":681,"line":790},[679,35362,35327],{"class":880},[679,35364,35365],{"class":689}," 07:57:26.505",[679,35367,18562],{"class":689},[679,35369,35335],{"class":931},[679,35371,18568],{"class":931},[679,35373,1411],{"class":931},[679,35375,34595],{"class":689},[679,35377,4477],{"class":931},[679,35379,35346],{"class":689},[679,35381,35349],{"class":689},[679,35383,35352],{"class":689},[679,35385,35355],{"class":689},[679,35387,35388],{"class":689}," Start",[679,35390,35391],{"class":689}," completed.\n",[4542,35393,35395],{"id":35394},"spring-boot-autoconfiguration","Spring Boot AutoConfiguration",[651,35397,35398],{},"We have talked about AutoConfiguration before but I think this is a good chance to look at another great example of it in use. Remember how I said earlier that in the old days you might have had to configure your own Datasource? So why don't we have to do this now? If you do a quick search in IntelliJ (double tap shift key) and look for DataSourceConfiguration.class in (org.springframework.boot.autoconfigure.jdbc) you will find the following block of code.",[669,35400,35402],{"className":4107,"code":35401,"language":4109,"meta":674,"style":674},"@ConditionalOnClass({HikariDataSource.class})\n@ConditionalOnProperty(\n name = {\"spring.datasource.type\"},\n havingValue = \"com.zaxxer.hikari.HikariDataSource\",\n matchIfMissing = true\n)\nstatic class Hikari extends DataSourceConfiguration {\n Hikari() {\n }\n\n @Bean\n @ConfigurationProperties(\n prefix = \"spring.datasource.hikari\"\n )\n public HikariDataSource dataSource(DataSourceProperties properties) {\n return (HikariDataSource)this.createDataSource(properties, HikariDataSource.class);\n }\n}\n",[676,35403,35404,35414,35423,35437,35449,35458,35462,35479,35486,35490,35494,35500,35509,35519,35524,35541,35558,35562],{"__ignoreMap":674},[679,35405,35406,35408,35411],{"class":681,"line":682},[679,35407,4116],{"class":693},[679,35409,35410],{"class":685},"ConditionalOnClass",[679,35412,35413],{"class":693},"({HikariDataSource.class})\n",[679,35415,35416,35418,35421],{"class":681,"line":790},[679,35417,4116],{"class":693},[679,35419,35420],{"class":685},"ConditionalOnProperty",[679,35422,21337],{"class":693},[679,35424,35425,35428,35430,35432,35435],{"class":681,"line":892},[679,35426,35427],{"class":931}," name",[679,35429,6883],{"class":685},[679,35431,11566],{"class":693},[679,35433,35434],{"class":689},"\"spring.datasource.type\"",[679,35436,6481],{"class":693},[679,35438,35439,35442,35444,35447],{"class":681,"line":901},[679,35440,35441],{"class":931}," havingValue",[679,35443,6883],{"class":685},[679,35445,35446],{"class":689}," \"com.zaxxer.hikari.HikariDataSource\"",[679,35448,12083],{"class":693},[679,35450,35451,35454,35456],{"class":681,"line":909},[679,35452,35453],{"class":931}," matchIfMissing",[679,35455,6883],{"class":685},[679,35457,2582],{"class":931},[679,35459,35460],{"class":681,"line":918},[679,35461,1339],{"class":693},[679,35463,35464,35467,35469,35472,35474,35477],{"class":681,"line":935},[679,35465,35466],{"class":685},"static",[679,35468,4512],{"class":685},[679,35470,35471],{"class":880}," Hikari",[679,35473,2767],{"class":685},[679,35475,35476],{"class":880}," DataSourceConfiguration",[679,35478,884],{"class":693},[679,35480,35481,35484],{"class":681,"line":944},[679,35482,35483],{"class":880}," Hikari",[679,35485,2667],{"class":693},[679,35487,35488],{"class":681,"line":959},[679,35489,985],{"class":693},[679,35491,35492],{"class":681,"line":964},[679,35493,889],{"emptyLinePlaceholder":797},[679,35495,35496,35498],{"class":681,"line":977},[679,35497,6872],{"class":693},[679,35499,16929],{"class":685},[679,35501,35502,35504,35507],{"class":681,"line":982},[679,35503,6872],{"class":693},[679,35505,35506],{"class":685},"ConfigurationProperties",[679,35508,21337],{"class":693},[679,35510,35511,35514,35516],{"class":681,"line":988},[679,35512,35513],{"class":931}," prefix",[679,35515,6883],{"class":685},[679,35517,35518],{"class":689}," \"spring.datasource.hikari\"\n",[679,35520,35521],{"class":681,"line":993},[679,35522,35523],{"class":693}," )\n",[679,35525,35526,35528,35531,35533,35536,35539],{"class":681,"line":2129},[679,35527,6089],{"class":685},[679,35529,35530],{"class":693}," HikariDataSource ",[679,35532,31838],{"class":880},[679,35534,35535],{"class":693},"(DataSourceProperties ",[679,35537,35538],{"class":2099},"properties",[679,35540,4390],{"class":693},[679,35542,35543,35545,35548,35550,35552,35555],{"class":681,"line":2140},[679,35544,9444],{"class":685},[679,35546,35547],{"class":693}," (HikariDataSource)",[679,35549,4732],{"class":931},[679,35551,664],{"class":693},[679,35553,35554],{"class":880},"createDataSource",[679,35556,35557],{"class":693},"(properties, HikariDataSource.class);\n",[679,35559,35560],{"class":681,"line":2145},[679,35561,985],{"class":693},[679,35563,35564],{"class":681,"line":2154},[679,35565,996],{"class":693},[651,35567,35568],{},"The @ConditionalOnClass is saying we are only going to create this class if this class exists on the classpath. Earlier we defined HikariCP as a dependency and this now becomes a true condition. The @ConditionalOnProperty is now looking for that property and looking for a specific value. When we defined that property in application.properties, that statement was now true. You can see that it creates a Datasource and uses the prefix \"spring.datasource.hikari\" for its configuration properties. This means all of the configuration Hikari exposes can be used using that prefix. The HikariCP documentation tells us that we can change the connection timeout property so in our application.properties we can simply use ",[669,35570,35572],{"className":4107,"code":35571,"language":4109,"meta":674,"style":674},"spring.datasource.hikari.connection-timeout=60000\n",[676,35573,35574],{"__ignoreMap":674},[679,35575,35576,35579,35581,35584,35586],{"class":681,"line":682},[679,35577,35578],{"class":693},"spring.datasource.hikari.connection",[679,35580,6453],{"class":685},[679,35582,35583],{"class":693},"timeout",[679,35585,686],{"class":685},[679,35587,35588],{"class":931},"60000\n",[4542,35590,35592],{"id":35591},"spring-boot-20","Spring Boot 2.0",[651,35594,35595,35596,35601,35602,664],{},"Spring Boot 2.0 is coming soon and when it ships we will no longer need to worry about this. This is because they are going to move to ",[812,35597,35600],{"href":35598,"rel":35599},"https://github.com/spring-projects/spring-boot/issues/6013",[816],"HikariCP by default",". This is just another awesome example of the Spring Boot team providing us with sensible defaults. Want to learn more about ",[812,35603,35605],{"href":30506,"rel":35604},[816],"Spring Boot 2.0, click here",[4542,35607,35609],{"id":35608},"hikaricp-spring-boot-screencast","HikariCP Spring Boot Screencast",[651,35611,35612,11108],{},[812,35613,35614],{"href":35614,"rel":35615},"https://youtu.be/Q8Dx8EzIveM",[816],[4542,35617,9042],{"id":9041},[651,35619,35620],{},"In conclusion AutoConfiguration is really awesome and helps save us some valuable time. In this case, we simply declared a dependency and a property and Spring Boot wired up a Hikari Datasource for us.",[786,35622,35623],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":35625},[35626,35627,35628,35629,35630,35631],{"id":35192,"depth":790,"text":35188},{"id":35260,"depth":790,"text":35261},{"id":35394,"depth":790,"text":35395},{"id":35591,"depth":790,"text":35592},{"id":35608,"depth":790,"text":35609},{"id":9041,"depth":790,"text":9042},{"slug":35633,"published":797,"date":35634,"tags":35635,"cover":35636},"use-hikaricp-next-spring-boot-project","2017-07-26T08:16:50-04:00",[4109,7055],"./pexels-photo-169976-760x506.jpeg",{"title":612,"description":612},"blog/2017/07/26/use-hikaricp-next-spring-boot-project","alyUihf_3hcjOxeyklO4-C9x_btIaUswlxF91IWUn1I",{"id":35641,"title":609,"body":35642,"description":609,"extension":793,"meta":36310,"navigation":797,"path":610,"seo":36314,"stem":36315,"__hash__":36316},"content/blog/2017/07/31/spring-data-aggregate-functions-repository.md",{"type":648,"value":35643,"toc":36304},[35644,35649,35654,35657,35661,35669,35675,35678,35684,35687,35816,35823,36046,36049,36053,36061,36125,36134,36271,36275,36281,36283,36296,36301],[651,35645,31911,35646,664],{},[812,35647,21140],{"href":30549,"rel":35648},[816],[1004,35650,35651],{},[651,35652,35653],{},"Hi Dan, How can I use the @Query to specify an aggregation function in a select statement like... \"select post_date, sum(value) from post group by post_date\" retrieve it to a DTO and show on screen?",[651,35655,35656],{},"This is actually pretty easy to do using the @Query annotation. In this tutorial, I will show you how to do this using JQL and native SQL. ",[4542,35658,35660],{"id":35659},"creating-the-application","Creating the application",[651,35662,35663,35664,15266],{},"The first thing we need to do is to create a very basic Spring Boot application with the following dependencies. If you want you can grab the source code for this ",[812,35665,35668],{"href":35666,"rel":35667},"https://github.com/cfaddict/queryagg",[816],"demo here",[651,35670,35671],{},[660,35672],{"alt":35673,"src":35674},"Aggregate Functions Dependencies ","./2017-07-26_15-05-12-1024x645.png",[651,35676,35677],{},"I won't walk you through every single step of this application because I don't want to focus on the application itself. We are building a simple application that exposes some endpoints that call a service and a repository. ",[651,35679,35680],{},[660,35681],{"alt":35682,"src":35683},"Aggregate Functions Project Structure","./2017-07-31_08-38-06.png",[651,35685,35686],{},"This is what our domain looks like. ",[669,35688,35690],{"className":4107,"code":35689,"language":4109,"meta":674,"style":674},"@Entity\n@Data\n@NoArgsConstructor\npublic class User {\n\n @Id @GeneratedValue\n private long id;\n private String firstName;\n private int age;\n\n public User(String first, int age) {\n this.firstName = first;\n this.age = age;\n }\n\n}\n",[676,35691,35692,35698,35704,35710,35720,35724,35734,35743,35750,35759,35763,35782,35793,35804,35808,35812],{"__ignoreMap":674},[679,35693,35694,35696],{"class":681,"line":682},[679,35695,4116],{"class":693},[679,35697,11234],{"class":685},[679,35699,35700,35702],{"class":681,"line":790},[679,35701,4116],{"class":693},[679,35703,15305],{"class":685},[679,35705,35706,35708],{"class":681,"line":892},[679,35707,4116],{"class":693},[679,35709,16281],{"class":685},[679,35711,35712,35714,35716,35718],{"class":681,"line":901},[679,35713,6073],{"class":685},[679,35715,4512],{"class":685},[679,35717,881],{"class":880},[679,35719,884],{"class":693},[679,35721,35722],{"class":681,"line":909},[679,35723,889],{"emptyLinePlaceholder":797},[679,35725,35726,35728,35730,35732],{"class":681,"line":918},[679,35727,6872],{"class":693},[679,35729,11256],{"class":685},[679,35731,6475],{"class":693},[679,35733,11261],{"class":685},[679,35735,35736,35738,35741],{"class":681,"line":935},[679,35737,9232],{"class":685},[679,35739,35740],{"class":685}," long",[679,35742,11318],{"class":693},[679,35744,35745,35747],{"class":681,"line":944},[679,35746,9232],{"class":685},[679,35748,35749],{"class":693}," String firstName;\n",[679,35751,35752,35754,35756],{"class":681,"line":959},[679,35753,9232],{"class":685},[679,35755,14925],{"class":685},[679,35757,35758],{"class":693}," age;\n",[679,35760,35761],{"class":681,"line":964},[679,35762,889],{"emptyLinePlaceholder":797},[679,35764,35765,35767,35769,35771,35773,35775,35777,35780],{"class":681,"line":977},[679,35766,6089],{"class":685},[679,35768,881],{"class":880},[679,35770,11400],{"class":693},[679,35772,13805],{"class":2099},[679,35774,2797],{"class":693},[679,35776,1078],{"class":685},[679,35778,35779],{"class":2099}," age",[679,35781,4390],{"class":693},[679,35783,35784,35786,35789,35791],{"class":681,"line":982},[679,35785,7862],{"class":931},[679,35787,35788],{"class":693},".firstName ",[679,35790,686],{"class":685},[679,35792,13783],{"class":693},[679,35794,35795,35797,35800,35802],{"class":681,"line":988},[679,35796,7862],{"class":931},[679,35798,35799],{"class":693},".age ",[679,35801,686],{"class":685},[679,35803,35758],{"class":693},[679,35805,35806],{"class":681,"line":993},[679,35807,985],{"class":693},[679,35809,35810],{"class":681,"line":2129},[679,35811,889],{"emptyLinePlaceholder":797},[679,35813,35814],{"class":681,"line":2140},[679,35815,996],{"class":693},[651,35817,35818,35819,15266],{},"And we will load some initial test data using a ",[812,35820,16213],{"href":35821,"rel":35822},"https://www.danvega.dev/blog/2017/04/07/spring-boot-command-line-runner",[816],[669,35824,35826],{"className":4107,"code":35825,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n CommandLineRunner runner(UserRepository userRepository){\n return args -> {\n userRepository.save( new User(\"Sam\", 20) );\n userRepository.save( new User(\"Joe\", 25) );\n userRepository.save( new User(\"Mark\", 48) );\n userRepository.save( new User(\"Emily\", 26) );\n userRepository.save( new User(\"Nick\", 59) );\n };\n }\n\n}\n",[676,35827,35828,35834,35844,35848,35868,35876,35880,35884,35890,35902,35912,35935,35958,35982,36006,36030,36034,36038,36042],{"__ignoreMap":674},[679,35829,35830,35832],{"class":681,"line":682},[679,35831,4116],{"class":693},[679,35833,6068],{"class":685},[679,35835,35836,35838,35840,35842],{"class":681,"line":790},[679,35837,6073],{"class":685},[679,35839,4512],{"class":685},[679,35841,16878],{"class":880},[679,35843,884],{"class":693},[679,35845,35846],{"class":681,"line":892},[679,35847,889],{"emptyLinePlaceholder":797},[679,35849,35850,35852,35854,35856,35858,35860,35862,35864,35866],{"class":681,"line":901},[679,35851,6089],{"class":685},[679,35853,6092],{"class":685},[679,35855,6095],{"class":685},[679,35857,6098],{"class":880},[679,35859,745],{"class":693},[679,35861,4758],{"class":685},[679,35863,16901],{"class":693},[679,35865,6108],{"class":2099},[679,35867,4390],{"class":693},[679,35869,35870,35872,35874],{"class":681,"line":909},[679,35871,6115],{"class":693},[679,35873,6118],{"class":880},[679,35875,16914],{"class":693},[679,35877,35878],{"class":681,"line":918},[679,35879,985],{"class":693},[679,35881,35882],{"class":681,"line":935},[679,35883,889],{"emptyLinePlaceholder":797},[679,35885,35886,35888],{"class":681,"line":944},[679,35887,6872],{"class":693},[679,35889,16929],{"class":685},[679,35891,35892,35894,35896,35898,35900],{"class":681,"line":959},[679,35893,20982],{"class":693},[679,35895,16939],{"class":880},[679,35897,34093],{"class":693},[679,35899,34096],{"class":2099},[679,35901,9533],{"class":693},[679,35903,35904,35906,35908,35910],{"class":681,"line":964},[679,35905,9444],{"class":685},[679,35907,16952],{"class":693},[679,35909,16955],{"class":685},[679,35911,884],{"class":693},[679,35913,35914,35917,35919,35921,35923,35925,35927,35929,35931,35933],{"class":681,"line":977},[679,35915,35916],{"class":693}," userRepository.",[679,35918,7629],{"class":880},[679,35920,1234],{"class":693},[679,35922,8930],{"class":685},[679,35924,881],{"class":880},[679,35926,745],{"class":693},[679,35928,1419],{"class":689},[679,35930,2797],{"class":693},[679,35932,11794],{"class":931},[679,35934,19695],{"class":693},[679,35936,35937,35939,35941,35943,35945,35947,35949,35951,35953,35956],{"class":681,"line":982},[679,35938,35916],{"class":693},[679,35940,7629],{"class":880},[679,35942,1234],{"class":693},[679,35944,8930],{"class":685},[679,35946,881],{"class":880},[679,35948,745],{"class":693},[679,35950,1439],{"class":689},[679,35952,2797],{"class":693},[679,35954,35955],{"class":931},"25",[679,35957,19695],{"class":693},[679,35959,35960,35962,35964,35966,35968,35970,35972,35975,35977,35980],{"class":681,"line":988},[679,35961,35916],{"class":693},[679,35963,7629],{"class":880},[679,35965,1234],{"class":693},[679,35967,8930],{"class":685},[679,35969,881],{"class":880},[679,35971,745],{"class":693},[679,35973,35974],{"class":689},"\"Mark\"",[679,35976,2797],{"class":693},[679,35978,35979],{"class":931},"48",[679,35981,19695],{"class":693},[679,35983,35984,35986,35988,35990,35992,35994,35996,35999,36001,36004],{"class":681,"line":993},[679,35985,35916],{"class":693},[679,35987,7629],{"class":880},[679,35989,1234],{"class":693},[679,35991,8930],{"class":685},[679,35993,881],{"class":880},[679,35995,745],{"class":693},[679,35997,35998],{"class":689},"\"Emily\"",[679,36000,2797],{"class":693},[679,36002,36003],{"class":931},"26",[679,36005,19695],{"class":693},[679,36007,36008,36010,36012,36014,36016,36018,36020,36023,36025,36028],{"class":681,"line":2129},[679,36009,35916],{"class":693},[679,36011,7629],{"class":880},[679,36013,1234],{"class":693},[679,36015,8930],{"class":685},[679,36017,881],{"class":880},[679,36019,745],{"class":693},[679,36021,36022],{"class":689},"\"Nick\"",[679,36024,2797],{"class":693},[679,36026,36027],{"class":931},"59",[679,36029,19695],{"class":693},[679,36031,36032],{"class":681,"line":2140},[679,36033,17018],{"class":693},[679,36035,36036],{"class":681,"line":2145},[679,36037,985],{"class":693},[679,36039,36040],{"class":681,"line":2154},[679,36041,889],{"emptyLinePlaceholder":797},[679,36043,36044],{"class":681,"line":2159},[679,36045,996],{"class":693},[651,36047,36048],{},"With those in place, we can now begin to add some new methods to our repositories. ",[4542,36050,36052],{"id":36051},"spring-data-repositories","Spring Data Repositories",[651,36054,36055,36056,15266],{},"In the first example, I want to find out the average age of all the users in our system. We can do so by using the Aggregate function AVG in both JPQL and SQL. We can write these queries using the ",[812,36057,36060],{"href":36058,"rel":36059},"https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query",[816],"@Query annotation",[669,36062,36064],{"className":4107,"code":36063,"language":4109,"meta":674,"style":674},"public interface UserRepository extends CrudRepository\u003CUser, Long> {\n\n @Query(\"SELECT AVG(u.age) from User u\")\n int getAverageAge();\n\n}\n",[676,36065,36066,36089,36093,36107,36117,36121],{"__ignoreMap":674},[679,36067,36068,36070,36072,36075,36077,36079,36081,36083,36085,36087],{"class":681,"line":682},[679,36069,6073],{"class":685},[679,36071,6994],{"class":685},[679,36073,36074],{"class":880}," UserRepository",[679,36076,2767],{"class":685},[679,36078,16385],{"class":880},[679,36080,4505],{"class":693},[679,36082,34017],{"class":685},[679,36084,2797],{"class":693},[679,36086,1094],{"class":685},[679,36088,16397],{"class":693},[679,36090,36091],{"class":681,"line":790},[679,36092,889],{"emptyLinePlaceholder":797},[679,36094,36095,36097,36100,36102,36105],{"class":681,"line":892},[679,36096,6872],{"class":693},[679,36098,36099],{"class":685},"Query",[679,36101,745],{"class":693},[679,36103,36104],{"class":689},"\"SELECT AVG(u.age) from User u\"",[679,36106,1339],{"class":693},[679,36108,36109,36112,36115],{"class":681,"line":901},[679,36110,36111],{"class":685}," int",[679,36113,36114],{"class":880}," getAverageAge",[679,36116,9317],{"class":693},[679,36118,36119],{"class":681,"line":909},[679,36120,889],{"emptyLinePlaceholder":797},[679,36122,36123],{"class":681,"line":918},[679,36124,996],{"class":693},[651,36126,36127,36128,36133],{},"In this example, we are using ",[812,36129,36132],{"href":36130,"rel":36131},"http://docs.oracle.com/html/E13946_04/ejb3_langref.html",[816],"JPQL"," to write the SQL that will fetch us the data we need. JPQL stands for Java Persistence Query Language and if you have ever worked with Hibernate before you have probably seen this in action. It is important to understand that this is the default and if you want to write native SQL you can, but you need to add a flag, more on that in a bit. Now let's say that we wanted to add a new method that would give us the max age of all the users but also allow us to exclude a single user. This is strictly a demo and you probably wouldn't use this in a real application but it does show off a couple of things. First, it shows us how to include parameters in our SQL statement and it also shows us that we can write native SQL by using the native SQL flag. ",[669,36135,36137],{"className":4107,"code":36136,"language":4109,"meta":674,"style":674},"package com.therealdanvega.repository;\n\nimport com.therealdanvega.domain.User;\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.data.repository.CrudRepository;\n\npublic interface UserRepository extends CrudRepository\u003CUser, Long> {\n\n @Query(\"SELECT AVG(u.age) from User u\")\n int getAverageAge();\n\n @Query(value = \"SELECT max(age) from User where first_name \u003C> ?1\", nativeQuery = true)\n int getMaxAgeMinus(String name);\n\n}\n",[676,36138,36139,36146,36150,36157,36164,36170,36174,36196,36200,36212,36220,36224,36250,36263,36267],{"__ignoreMap":674},[679,36140,36141,36143],{"class":681,"line":682},[679,36142,2543],{"class":685},[679,36144,36145],{"class":693}," com.therealdanvega.repository;\n",[679,36147,36148],{"class":681,"line":790},[679,36149,889],{"emptyLinePlaceholder":797},[679,36151,36152,36154],{"class":681,"line":892},[679,36153,1999],{"class":685},[679,36155,36156],{"class":693}," com.therealdanvega.domain.User;\n",[679,36158,36159,36161],{"class":681,"line":901},[679,36160,1999],{"class":685},[679,36162,36163],{"class":693}," org.springframework.data.jpa.repository.Query;\n",[679,36165,36166,36168],{"class":681,"line":909},[679,36167,1999],{"class":685},[679,36169,26986],{"class":693},[679,36171,36172],{"class":681,"line":918},[679,36173,889],{"emptyLinePlaceholder":797},[679,36175,36176,36178,36180,36182,36184,36186,36188,36190,36192,36194],{"class":681,"line":935},[679,36177,6073],{"class":685},[679,36179,6994],{"class":685},[679,36181,36074],{"class":880},[679,36183,2767],{"class":685},[679,36185,16385],{"class":880},[679,36187,4505],{"class":693},[679,36189,34017],{"class":685},[679,36191,2797],{"class":693},[679,36193,1094],{"class":685},[679,36195,16397],{"class":693},[679,36197,36198],{"class":681,"line":944},[679,36199,889],{"emptyLinePlaceholder":797},[679,36201,36202,36204,36206,36208,36210],{"class":681,"line":959},[679,36203,6872],{"class":693},[679,36205,36099],{"class":685},[679,36207,745],{"class":693},[679,36209,36104],{"class":689},[679,36211,1339],{"class":693},[679,36213,36214,36216,36218],{"class":681,"line":964},[679,36215,36111],{"class":685},[679,36217,36114],{"class":880},[679,36219,9317],{"class":693},[679,36221,36222],{"class":681,"line":977},[679,36223,889],{"emptyLinePlaceholder":797},[679,36225,36226,36228,36230,36232,36234,36236,36239,36241,36244,36246,36248],{"class":681,"line":982},[679,36227,6872],{"class":693},[679,36229,36099],{"class":685},[679,36231,745],{"class":693},[679,36233,19934],{"class":931},[679,36235,6883],{"class":685},[679,36237,36238],{"class":689}," \"SELECT max(age) from User where first_name \u003C> ?1\"",[679,36240,2797],{"class":693},[679,36242,36243],{"class":931},"nativeQuery",[679,36245,6883],{"class":685},[679,36247,14523],{"class":931},[679,36249,1339],{"class":693},[679,36251,36252,36254,36257,36259,36261],{"class":681,"line":988},[679,36253,36111],{"class":685},[679,36255,36256],{"class":880}," getMaxAgeMinus",[679,36258,11400],{"class":693},[679,36260,16334],{"class":2099},[679,36262,1208],{"class":693},[679,36264,36265],{"class":681,"line":993},[679,36266,889],{"emptyLinePlaceholder":797},[679,36268,36269],{"class":681,"line":2129},[679,36270,996],{"class":693},[4542,36272,36274],{"id":36273},"screencast","Screencast ",[651,36276,36277],{},[812,36278,36279],{"href":36279,"rel":36280},"https://www.youtube.com/watch?v=zLRavueFJy0",[816],[4542,36282,9042],{"id":9041},[651,36284,36285,36286,36291,36292,15266],{},"I think the one that confuses people is not understanding that the default query syntax for @Query annotation is ",[812,36287,36290],{"href":36288,"rel":36289},"http://docs.oracle.com/html/E13946_04/ejb3_langref.html#ejb3_langref_agg_examples",[816],"JPQL and that it does support aggregate functions",". If you are interested in the full source code for this ",[812,36293,36295],{"href":35666,"rel":36294},[816],"demo you can grab it here",[651,36297,13453,36298,36300],{},[2939,36299,11650],{}," Are you facing any issues with your Spring Data Repositories? _",[786,36302,36303],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":36305},[36306,36307,36308,36309],{"id":35659,"depth":790,"text":35660},{"id":36051,"depth":790,"text":36052},{"id":36273,"depth":790,"text":36274},{"id":9041,"depth":790,"text":9042},{"slug":36311,"published":797,"date":36312,"tags":36313,"cover":29942},"spring-data-aggregate-functions-repository","2017-07-31T08:48:35-04:00",[7055],{"title":609,"description":609},"blog/2017/07/31/spring-data-aggregate-functions-repository","5N6ooKgrW-Otb9avH96Nd--Qxzt-5apIwzucdSWwZ6Q",{"id":36318,"title":606,"body":36319,"description":606,"extension":793,"meta":36401,"navigation":797,"path":607,"seo":36406,"stem":36407,"__hash__":36408},"content/blog/2017/08/02/kill-java-process-macos-sierra-using-activity-monitor.md",{"type":648,"value":36320,"toc":36394},[36321,36330,36334,36337,36349,36355,36358,36363,36367,36370,36376,36380,36383,36385,36391],[651,36322,36323,36324,36329],{},"Last week I ",[812,36325,36328],{"href":36326,"rel":36327},"https://www.danvega.dev/blog/2017/07/24/spring-boot-application-failed-start",[816],"wrote an article"," on the improvements in Spring Boot 1.4 of startup errors. Specifically, we saw that we got a really informative error message when we try and run an application and port 8080 is already in use. I received a question from a reader that went like this \"I get the port 8080 is already in use error from time to time and I am not sure how to fix it, what can I do?\" This is actually pretty easy to fix and happens to all of us. In this short article, I will show you how to do it on macOS and link to another article I wrote on how to do this on Windows. ",[4542,36331,36333],{"id":36332},"port-8080-is-already-in-use","Port 8080 is already in use",[651,36335,36336],{},"This problem usually comes up because another process wasn't properly terminated. If you want to simulate this issue you can fire up one app in a terminal and then try launching another in your favorite IDE (IntelliJ right?). I opened up a terminal and ran my other project using the Maven plugin.",[669,36338,36340],{"className":5851,"code":36339,"language":5853,"meta":674,"style":674},"./mvnw spring-boot:run\n",[676,36341,36342],{"__ignoreMap":674},[679,36343,36344,36346],{"class":681,"line":682},[679,36345,32493],{"class":880},[679,36347,36348],{"class":689}," spring-boot:run\n",[651,36350,36351],{},[660,36352],{"alt":36353,"src":36354},"Spring Boot Maven Plugin","./2017-08-02_08-11-41-1024x532.png",[651,36356,36357],{},"Now if we head into IntelliJ and try to run our application we are going to see an error that looks something like this. ",[651,36359,36360],{},[660,36361],{"alt":36333,"src":36362},"./2017-08-02_08-17-08-1024x450.png",[4542,36364,36366],{"id":36365},"activity-monitor-ftw","Activity Monitor FTW",[651,36368,36369],{},"So we know that another process is running but it isn't always as simple as forgetting a terminal window is open and running another app. This can happen when an IDE is closed and the process is correctly terminated. macOS gives us a nice tool for monitoring process by name and port called Activity Monitor. If you open up Activity Monitor (I just use Spotlight) you could manually look for the processes or use the search bar in the upper right-hand corner. Now you can kill the existing processes and your unwanted process will be gone.",[651,36371,36372],{},[660,36373],{"alt":36374,"src":36375},"Activity Monitor","./2017-08-02_08-18-11.png",[5909,36377,36379],{"id":36378},"what-about-windows","What about Windows? ",[651,36381,36382],{},"If you're a windows user I wrote an article awhile back on how to kill a process by port number. ",[4542,36384,26009],{"id":36273},[651,36386,36387],{},[812,36388,36389],{"href":36389,"rel":36390},"https://www.youtube.com/watch?v=Qn3vhcDQmpI",[816],[786,36392,36393],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":36395},[36396,36397,36400],{"id":36332,"depth":790,"text":36333},{"id":36365,"depth":790,"text":36366,"children":36398},[36399],{"id":36378,"depth":892,"text":36379},{"id":36273,"depth":790,"text":26009},{"slug":36402,"published":797,"date":36403,"tags":36404,"cover":36405},"kill-java-process-macos-sierra-using-activity-monitor","2017-08-02T08:13:38-04:00",[4109,7055],"./java.jpg",{"title":606,"description":606},"blog/2017/08/02/kill-java-process-macos-sierra-using-activity-monitor","EvYsDGuCZZYbPgQ7CZx6D0I46WbzvM4O1sqrE59Q3eo",{"id":36410,"title":603,"body":36411,"description":603,"extension":793,"meta":36492,"navigation":797,"path":604,"seo":36497,"stem":36498,"__hash__":36499},"content/blog/2017/08/04/3-youtube-channels-java-developers.md",{"type":648,"value":36412,"toc":36484},[36413,36416,36420,36423,36426,36432,36438,36442,36445,36451,36457,36461,36464,36470,36476,36478],[651,36414,36415],{},"I think if I was given the choice to only keep a single website around I would have to go with YouTube. I love it for both entertainment and education and I love creating content on my channel. I am going to share with you 3 YouTube channels I think you should subscribe to. The reason I chose these 3 is that they have all put out some really great content lately. ",[4542,36417,36419],{"id":36418},"youtube-channels-for-java-developers","YouTube Channels for Java Developers",[5909,36421,36422],{"id":4109},"Java",[651,36424,36425],{},"I don't think I could start this list off without including Java. This is a great channel run by Oracle and in the past few days, the released nearly 20 videos. ",[651,36427,36428],{},[812,36429,36430],{"href":36430,"rel":36431},"https://www.youtube.com/user/java",[816],[651,36433,36434],{},[660,36435],{"alt":36436,"src":36437},"Java YouTube Channel","./2017-08-04_13-14-20-1024x169.png",[5909,36439,36441],{"id":36440},"spring-io","Spring I/O",[651,36443,36444],{},"The Spring/IO channel is a collection of presentations from Spring I/O conferences. They recently added a bunch of presentations from their conference in Barcelona. ",[651,36446,36447,11634],{},[812,36448,36449],{"href":36449,"rel":36450},"https://www.youtube.com/channel/UCLMPXsvSrhNPN3i9h-u8PYg",[816],[651,36452,36453],{},[660,36454],{"alt":36455,"src":36456},"Spring I/O YouTube Channel","./2017-08-04_13-17-14-1024x170.png",[5909,36458,36460],{"id":36459},"devoxx","Devoxx",[651,36462,36463],{},"The Devoxx YouTube channel is a collection of presentations from their conferences. They recently added a bunch of presentations from their conference in Poland. ",[651,36465,36466],{},[812,36467,36468],{"href":36468,"rel":36469},"https://www.youtube.com/channel/UCCBVCTuk6uJrN3iFV_3vurg",[816],[651,36471,36472],{},[660,36473],{"alt":36474,"src":36475},"Devoxx YouTube Channel","./2017-08-04_13-17-37-1024x170.png",[4542,36477,9042],{"id":9041},[651,36479,36480,36481,36483],{},"I hope you liked these channels and you should think about subscribing to them. Thank you to 3 channels we discussed for putting out some amazing content! _",[2939,36482,11650],{}," If you could only keep one website around what would it be? _",{"title":674,"searchDepth":790,"depth":790,"links":36485},[36486,36491],{"id":36418,"depth":790,"text":36419,"children":36487},[36488,36489,36490],{"id":4109,"depth":892,"text":36422},{"id":36440,"depth":892,"text":36441},{"id":36459,"depth":892,"text":36460},{"id":9041,"depth":790,"text":9042},{"slug":36493,"published":797,"date":36494,"tags":36495,"cover":36496},"3-youtube-channels-java-developers","2017-08-04T13:14:00-04:00",[4109,7055],"./pexels-photo-760x506.jpg",{"title":603,"description":603},"blog/2017/08/04/3-youtube-channels-java-developers","QDecRVQudZfZCyea8IwmnPTvusNHIInfWQ5My6QIAQg",{"id":36501,"title":600,"body":36502,"description":600,"extension":793,"meta":36798,"navigation":797,"path":601,"seo":36803,"stem":36804,"__hash__":36805},"content/blog/2017/08/16/multiple-dependencies-spring-cli.md",{"type":648,"value":36503,"toc":36792},[36504,36510,36518,36521,36525,36538,36542,36545,36561,36568,36573,36576,36613,36617,36620,36626,36633,36699,36702,36716,36722,36729,36776,36779,36781,36784,36789],[651,36505,31911,36506,36509],{},[812,36507,21140],{"href":30549,"rel":36508},[816],". This question has to do with the Spring CLI which is a great tool that allows you to quickly prototype with Spring. ",[1004,36511,36512,36515],{},[651,36513,36514],{},"Hi Dan,",[651,36516,36517],{},"I have created the spring boot application through spring CLI in the same way you showed in the video but my question is, How can we add multiple dependencies in our application through spring CLI? You have shown it for only one dependency i.e \"web\" using -d option but what if we want to add more dependencies through CLI?",[651,36519,36520],{},"In this article, I am going to talk through what the Spring CLI, how to install it and use it and of course answer the question above. ",[4542,36522,36524],{"id":36523},"spring-cli","Spring CLI",[651,36526,36527,36528,36531,36532,36537],{},"If you weren't already aware there is a really cool tool called the Spring CLI (Command Line Interface). The Spring Boot CLI is a command line tool that can be used if you want to quickly prototype with Spring. It allows you to run ",[812,36529,18628],{"href":13496,"rel":36530},[816]," scripts, which means that you have a familiar Java-like syntax, without so much boilerplate code. Did I mention I also ",[812,36533,36536],{"href":36534,"rel":36535},"https://www.danvega.dev/groovy",[816],"teach a course on Groovy"," and absolutely love the language? You don’t need to use the CLI to work with Spring Boot but it’s definitely the quickest way to get a Spring application off the ground.",[4542,36539,36541],{"id":36540},"installing-the-spring-cli","Installing the Spring CLI",[651,36543,36544],{},"There are a couple of ways to install the Spring CLI and we will go through them here. First, you can install them manually by downloading the Spring CLI distribution from the Spring software repository:",[5316,36546,36547,36554],{},[5332,36548,36549],{},[812,36550,36553],{"href":36551,"rel":36552},"http://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.5.6.RELEASE/spring-boot-cli-1.5.6.RELEASE-bin.zip",[816],"spring-boot-cli-1.5.6.RELEASE-bin.zip",[5332,36555,36556],{},[812,36557,36560],{"href":36558,"rel":36559},"http://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.5.6.RELEASE/spring-boot-cli-1.5.6.RELEASE-bin.tar.gz",[816],"spring-boot-cli-1.5.6.RELEASE-bin.tar.gz",[651,36562,36563,36564,15266],{},"Another way to install the Spring CLI is by using SDKMan. If you have been following me at all you know that I am a huge fan of ",[812,36565,17720],{"href":36566,"rel":36567},"http://sdkman.io/",[816],[651,36569,36570],{},[660,36571],{"alt":17720,"src":36572},"./2017-08-15_20-11-24.png",[651,36574,36575],{},"When you have SDKMan installed you can install the Spring CLI by running the following command. ",[669,36577,36579],{"className":5851,"code":36578,"language":5853,"meta":674,"style":674},"$ sdk install springboot\n$ spring --version\nSpring Boot v1.5.6.RELEASE\n",[676,36580,36581,36593,36603],{"__ignoreMap":674},[679,36582,36583,36585,36588,36590],{"class":681,"line":682},[679,36584,30246],{"class":880},[679,36586,36587],{"class":689}," sdk",[679,36589,24571],{"class":689},[679,36591,36592],{"class":689}," springboot\n",[679,36594,36595,36597,36600],{"class":681,"line":790},[679,36596,30246],{"class":880},[679,36598,36599],{"class":689}," spring",[679,36601,36602],{"class":931}," --version\n",[679,36604,36605,36607,36610],{"class":681,"line":892},[679,36606,23816],{"class":880},[679,36608,36609],{"class":689}," Boot",[679,36611,36612],{"class":689}," v1.5.6.RELEASE\n",[4542,36614,36616],{"id":36615},"using-the-spring-cli","Using the Spring CLI",[651,36618,36619],{},"Now that we have the Spring CLI installed its time to use it. If you aren't sure what options are available you can run the help command to get more information. ",[651,36621,36622],{},[660,36623],{"alt":36624,"src":36625},"Spring CLI Help","./2017-08-16_08-08-04.png",[651,36627,36628,36629,36632],{},"In this first example, we will create and run a simple example. First, create a file called ",[7300,36630,36631],{},"app.groovy"," that looks like this. ",[669,36634,36636],{"className":4107,"code":36635,"language":4109,"meta":674,"style":674},"@RestController\nclass AppController {\n\n @RequestMapping(\"/\")\n public String home() {\n return \"Hello, World!\";\n }\n\n}\n",[676,36637,36638,36644,36653,36657,36669,36679,36687,36691,36695],{"__ignoreMap":674},[679,36639,36640,36642],{"class":681,"line":682},[679,36641,4116],{"class":693},[679,36643,9212],{"class":685},[679,36645,36646,36648,36651],{"class":681,"line":790},[679,36647,877],{"class":685},[679,36649,36650],{"class":880}," AppController",[679,36652,884],{"class":693},[679,36654,36655],{"class":681,"line":892},[679,36656,889],{"emptyLinePlaceholder":797},[679,36658,36659,36661,36663,36665,36667],{"class":681,"line":901},[679,36660,6872],{"class":693},[679,36662,9275],{"class":685},[679,36664,745],{"class":693},[679,36666,10032],{"class":689},[679,36668,1339],{"class":693},[679,36670,36671,36673,36675,36677],{"class":681,"line":909},[679,36672,6089],{"class":685},[679,36674,9289],{"class":693},[679,36676,12642],{"class":880},[679,36678,2667],{"class":693},[679,36680,36681,36683,36685],{"class":681,"line":918},[679,36682,9444],{"class":685},[679,36684,32904],{"class":689},[679,36686,1186],{"class":693},[679,36688,36689],{"class":681,"line":935},[679,36690,985],{"class":693},[679,36692,36693],{"class":681,"line":944},[679,36694,889],{"emptyLinePlaceholder":797},[679,36696,36697],{"class":681,"line":959},[679,36698,996],{"class":693},[651,36700,36701],{},"Now you can run this application from the command line using the following command. ",[669,36703,36705],{"className":5851,"code":36704,"language":5853,"meta":674,"style":674},"spring run app.groovy\n",[676,36706,36707],{"__ignoreMap":674},[679,36708,36709,36711,36713],{"class":681,"line":682},[679,36710,7055],{"class":880},[679,36712,16486],{"class":689},[679,36714,36715],{"class":689}," app.groovy\n",[651,36717,36718],{},[660,36719],{"alt":36720,"src":36721},"Spring CLI run app","./2017-08-16_08-13-27-1024x547.png",[651,36723,36724,36725,36728],{},"You can also use the Spring CLI to initialize a new project using ",[812,36726,30440],{"href":20748,"rel":36727},[816],". This is similar to creating a new project in IntelliJ using the fancy wizard. So to answer our initial question which was how can we initialize a new project from the Spring CLI and add multiple dependencies: ",[669,36730,36732],{"className":5851,"code":36731,"language":5853,"meta":674,"style":674},"$ spring init --dependencies=web,data-jpa my-project\nUsing service at https://start.spring.io\nProject extracted to '/Users/developer/example/my-project'\n",[676,36733,36734,36749,36763],{"__ignoreMap":674},[679,36735,36736,36738,36740,36743,36746],{"class":681,"line":682},[679,36737,30246],{"class":880},[679,36739,36599],{"class":689},[679,36741,36742],{"class":689}," init",[679,36744,36745],{"class":931}," --dependencies=web,data-jpa",[679,36747,36748],{"class":689}," my-project\n",[679,36750,36751,36754,36757,36760],{"class":681,"line":790},[679,36752,36753],{"class":880},"Using",[679,36755,36756],{"class":689}," service",[679,36758,36759],{"class":689}," at",[679,36761,36762],{"class":689}," https://start.spring.io\n",[679,36764,36765,36768,36771,36773],{"class":681,"line":892},[679,36766,36767],{"class":880},"Project",[679,36769,36770],{"class":689}," extracted",[679,36772,21703],{"class":689},[679,36774,36775],{"class":689}," '/Users/developer/example/my-project'\n",[651,36777,36778],{},"All you need to do is separate them using a comma and you can add as many dependencies as you like. ",[4542,36780,9042],{"id":9041},[651,36782,36783],{},"I think the Spring CLI is a great little tool for getting started. I hope you found this tutorial useful and I hope you check out the Spring CLI if you haven't already looked at it. ",[651,36785,13453,36786,36788],{},[2939,36787,11650],{}," What other tools do you find are helpful for developers new to Spring Boot? _",[786,36790,36791],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}",{"title":674,"searchDepth":790,"depth":790,"links":36793},[36794,36795,36796,36797],{"id":36523,"depth":790,"text":36524},{"id":36540,"depth":790,"text":36541},{"id":36615,"depth":790,"text":36616},{"id":9041,"depth":790,"text":9042},{"slug":36799,"published":797,"date":36800,"tags":36801,"cover":36802},"multiple-dependencies-spring-cli","2017-08-16T08:30:40-04:00",[7055],"./pexels-photo-205421-760x506.jpeg",{"title":600,"description":600},"blog/2017/08/16/multiple-dependencies-spring-cli","yucvGpJ3m1bXZr8k3ZE9t-_htwPs85BYOsXSdOiF44s",{"id":36807,"title":597,"body":36808,"description":597,"extension":793,"meta":36933,"navigation":797,"path":598,"seo":36937,"stem":36938,"__hash__":36939},"content/blog/2017/08/28/using-jhipster-development-mode.md",{"type":648,"value":36809,"toc":36926},[36810,36818,36822,36825,36830,36834,36837,36842,36845,36850,36858,36866,36869,36906,36910,36916,36918,36921],[651,36811,36812,36813,36817],{},"I have been working really hard lately to wrap up ",[812,36814,36816],{"href":30833,"rel":36815},[816],"my latest course on JHipster",". In one of the exercises, I have the students create their own application. In this application, one of the requirements is to make UI changes. When working with Angular you have probably come to expect to see those changes automatically reflected in the browser. In this tutorial, we will look at how to work with JHipster in Development. ",[4542,36819,36821],{"id":36820},"jhipster-in-development","JHipster in Development",[651,36823,36824],{},"I was working on this course and I quickly realized that unless you were familiar with this stack it might be a little confusing to know what to do in development. It is important to remember that we are really working with 2 different applications, Angular & Spring Boot, that work together. When you first start working with JHipster your first thought is to simply run the Spring boot app.",[651,36826,36827],{},[660,36828],{"alt":36821,"src":36829},"./2017-08-28_08-49-35-1024x776.png",[5909,36831,36833],{"id":36832},"angular-development","Angular Development",[651,36835,36836],{},"At this point, you might try to go into your webapp (Angular) directory, locate the home component and begin making changes to the application. ",[651,36838,36839],{},[660,36840],{"alt":36821,"src":36841},"./2017-08-28_08-53-38.png",[651,36843,36844],{},"The problem with this is that any changes you make won't be seen in the browser, even if you reload the page. JHipster gives us a way to work efficiently in development mode. If you have worked on Angular applications before you might have spun up a server using \"npm start\" that will watch for any changes and automatically refresh the browser window. JHipster gives us something similar by running the \"yarn start\" command and you can run it right in your IDE. ",[651,36846,36847],{},[660,36848],{"alt":36821,"src":36849},"./2017-08-28_08-57-46-1024x280.png",[651,36851,36852,36857],{},[812,36853,36856],{"href":36854,"rel":36855},"http://www.jhipster.tech/development/#working-with-angular",[816],"From the JHIpster documentation"," running this command provides us some very impressive features. ",[5316,36859,36860,36863],{},[5332,36861,36862],{},"As soon as you modify one of your HTML/CSS/TypeScript files, your browser will refresh itself automatically",[5332,36864,36865],{},"As your testing your application on several different browsers or devices, all your clicks/scrolls/inputs should be automatically synchronized on all screens",[651,36867,36868],{},"This will launch:",[5316,36870,36871,36874,36887,36899],{},[5332,36872,36873],{},"A Webpack task that will automatically compile TypeScript code into JavaScript",[5332,36875,36876,36877,36881,36882,36886],{},"A Webpack “hot module reload” server that will run on ",[812,36878,36879],{"href":36879,"rel":36880},"http://localhost:9060/",[816]," (and has a proxy to ",[812,36883,36884],{"href":36884,"rel":36885},"http://127.0.0.1:8080/api",[816]," to access the Java back-end)",[5332,36888,36889,36890,36894,36895,36898],{},"A BrowserSync task that will run on ",[812,36891,36892],{"href":36892,"rel":36893},"http://localhost:9000/",[816],", which has a proxy to ",[812,36896,36879],{"href":36879,"rel":36897},[816]," (the Webpack “hot module reload” server), and which will synchronize the user’s clicks/scrolls/inputs",[5332,36900,36901,36902],{},"The BrowserSync UI, which will be available on ",[812,36903,36904],{"href":36904,"rel":36905},"http://localhost:3001/",[816],[4542,36907,36909],{"id":36908},"jhipster-in-development-mode-screencast","JHipster in Development Mode Screencast ",[651,36911,36912,11108],{},[812,36913,36914],{"href":36914,"rel":36915},"https://www.youtube.com/watch?v=SVUB3Yhv3sQ&t=114s",[816],[4542,36917,9042],{"id":9041},[651,36919,36920],{},"I hope this short tutorial shed some light on how to work with JHipster in development. ",[651,36922,13453,36923,36925],{},[2939,36924,11650],{}," What problems are you facing in your JHipster projects? _",{"title":674,"searchDepth":790,"depth":790,"links":36927},[36928,36931,36932],{"id":36820,"depth":790,"text":36821,"children":36929},[36930],{"id":36832,"depth":892,"text":36833},{"id":36908,"depth":790,"text":36909},{"id":9041,"depth":790,"text":9042},{"slug":36934,"published":797,"date":36935,"tags":36936,"cover":18035},"using-jhipster-development-mode","2017-08-28T09:30:30-04:00",[17866,7055],{"title":597,"description":597},"blog/2017/08/28/using-jhipster-development-mode","ym8LBb1xIjMxcdS9E50kHnax1guFybKawDeLa7A-f78",{"id":36941,"title":594,"body":36942,"description":594,"extension":793,"meta":37090,"navigation":797,"path":595,"seo":37095,"stem":37096,"__hash__":37097},"content/blog/2017/08/30/groovy-happens-assign-biginteger-integer.md",{"type":648,"value":36943,"toc":37086},[36944,36952,36957,36960,36993,36997,37003,37018,37021,37032,37035,37046,37049,37078,37080,37083],[651,36945,36946,36947,36951],{},"This question came in from a student in my ",[812,36948,36950],{"href":36534,"rel":36949},[816],"Apache Groovy Development Course",". It was actually quite interesting because it tripped me up at first before I understand what was going on behind the scenes. ",[651,36953,36954],{},[660,36955],{"alt":24640,"src":36956},"./pexels-photo-169573-1024x683.jpeg",[651,36958,36959],{},"The student was asking why when they created an integer but assigned to large of a number to it did the results end up like this.",[669,36961,36963],{"className":4107,"code":36962,"language":4109,"meta":674,"style":674},"int i = 2356524235623432414235234234\nprintln i.class // java.lang.Integer\nprintln i // 1413517242\n",[676,36964,36965,36977,36985],{"__ignoreMap":674},[679,36966,36967,36969,36972,36974],{"class":681,"line":682},[679,36968,1078],{"class":685},[679,36970,36971],{"class":693}," i ",[679,36973,686],{"class":685},[679,36975,36976],{"class":931}," 2356524235623432414235234234\n",[679,36978,36979,36982],{"class":681,"line":790},[679,36980,36981],{"class":693},"println i.class ",[679,36983,36984],{"class":1400},"// java.lang.Integer\n",[679,36986,36987,36990],{"class":681,"line":892},[679,36988,36989],{"class":693},"println i ",[679,36991,36992],{"class":1400},"// 1413517242\n",[4542,36994,36996],{"id":36995},"groovy-integer","Groovy Integer",[651,36998,36999,37000,37002],{},"Looking at the code example above can you see what is going on and why? Let's take a minute and break this down. First, we are setting a very large number (BigInteger) to a data type integer. If you look at the Integer class there is a static MAX_VALUE that is set to \"A constant holding the maximum value an ",[676,37001,1078],{}," can have, 231-1.\"",[669,37004,37006],{"className":4107,"code":37005,"language":4109,"meta":674,"style":674},"int i = 2356524235623432414235234234\n",[676,37007,37008],{"__ignoreMap":674},[679,37009,37010,37012,37014,37016],{"class":681,"line":682},[679,37011,1078],{"class":685},[679,37013,36971],{"class":693},[679,37015,686],{"class":685},[679,37017,36976],{"class":931},[651,37019,37020],{},"So we are trying to set a value that is outside of that max value range to an integer. When we print out the class though it's still an integer",[669,37022,37024],{"className":4107,"code":37023,"language":4109,"meta":674,"style":674},"println i.class // java.lang.Integer\n",[676,37025,37026],{"__ignoreMap":674},[679,37027,37028,37030],{"class":681,"line":682},[679,37029,36981],{"class":693},[679,37031,36984],{"class":1400},[651,37033,37034],{},"When we print out the value though it appears it is some random number.",[669,37036,37038],{"className":4107,"code":37037,"language":4109,"meta":674,"style":674},"println i // 1413517242\n",[676,37039,37040],{"__ignoreMap":674},[679,37041,37042,37044],{"class":681,"line":682},[679,37043,36989],{"class":693},[679,37045,36992],{"class":1400},[651,37047,37048],{},"You might expect this to just error out but that isn't the case. What happens underneath the hood is that the number on the right is actually a BigInteger and when you try to assign it to an integer something happens. We end up calling the BigInteger's class method int value which will convert that number to an integer. ",[669,37050,37052],{"className":4107,"code":37051,"language":4109,"meta":674,"style":674},"BigDecimal bd = 2356524235623432414235234234;\nprintln bd.intValue() // 1413517242\n",[676,37053,37054,37066],{"__ignoreMap":674},[679,37055,37056,37059,37061,37064],{"class":681,"line":682},[679,37057,37058],{"class":693},"BigDecimal bd ",[679,37060,686],{"class":685},[679,37062,37063],{"class":931}," 2356524235623432414235234234",[679,37065,1186],{"class":693},[679,37067,37068,37071,37074,37076],{"class":681,"line":790},[679,37069,37070],{"class":693},"println bd.",[679,37072,37073],{"class":880},"intValue",[679,37075,6700],{"class":693},[679,37077,36992],{"class":1400},[4542,37079,9042],{"id":9041},[651,37081,37082],{},"When you understand what is happening under the hood everything seems to make a lot more sense.",[786,37084,37085],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}",{"title":674,"searchDepth":790,"depth":790,"links":37087},[37088,37089],{"id":36995,"depth":790,"text":36996},{"id":9041,"depth":790,"text":9042},{"slug":37091,"published":797,"date":37092,"tags":37093,"cover":37094},"groovy-happens-assign-biginteger-integer","2017-08-30T07:39:40-04:00",[870,4109],"./codes-coding.jpg",{"title":594,"description":594},"blog/2017/08/30/groovy-happens-assign-biginteger-integer","1N_27RInNsTKziJzjPamuYf1pGXrHVncDzgesItZ51s",{"id":37099,"title":591,"body":37100,"description":591,"extension":793,"meta":37436,"navigation":797,"path":592,"seo":37441,"stem":37442,"__hash__":37443},"content/blog/2017/09/06/upgrade-new-angular-command-line-interface-cli-1-3-release.md",{"type":648,"value":37101,"toc":37421},[37102,37105,37109,37122,37128,37131,37135,37152,37191,37195,37198,37212,37218,37223,37227,37230,37234,37245,37252,37256,37259,37277,37281,37284,37309,37312,37327,37331,37334,37368,37371,37389,37392,37396,37399,37403,37406,37413,37415,37418],[651,37103,37104],{},"Angular CLI recently released its next major version, 1.3 (Hopper). There are some pretty great features in this release and I can't wait to talk about a few of them here. In this article, we are going to discuss how to check what version of the CLI you are running, how to upgrade and what new features 1.3 is giving us.",[4542,37106,37108],{"id":37107},"upgrading-angular-cli","Upgrading Angular CLI",[651,37110,37111,37112,37117,37118,37121],{},"The first thing we need to talk about is installing or upgrading the Angular CLI. For those of you who have never actually installed it yet please ",[812,37113,37116],{"href":37114,"rel":37115},"https://www.danvega.dev/blog/2017/06/05/getting-started-angular-cli",[816],"check out this article I wrote",". If you are already running the Angular CLI you can check what version you are using by running the ",[676,37119,37120],{},"ng -v"," command.",[651,37123,37124],{},[660,37125],{"alt":37126,"src":37127},"Angular CLI version check","./2017-09-06_08-00-18.png",[651,37129,37130],{},"As we can see it has been a minute since I ran an update so this is a perfect time to do so.",[5909,37132,37134],{"id":37133},"upgrading-100-beta28-or-earlier","Upgrading 1.0.0-beta.28 or earlier",[651,37136,37137,37138,37141,37142,37145,37146,37148,37149],{},"If you're using Angular CLI ",[676,37139,37140],{},"1.0.0-beta.28"," or less, you need to uninstall ",[676,37143,37144],{},"angular-cli"," package. It should be done due to changing of package's name and scope from ",[676,37147,37144],{}," to ",[676,37150,37151],{},"@angular/cli",[669,37153,37155],{"className":5851,"code":37154,"language":5853,"meta":674,"style":674},"npm uninstall -g angular-cli @angular/cli\nnpm cache clean\nnpm install -g @angular/cli\n",[676,37156,37157,37171,37181],{"__ignoreMap":674},[679,37158,37159,37161,37164,37166,37169],{"class":681,"line":682},[679,37160,24568],{"class":880},[679,37162,37163],{"class":689}," uninstall",[679,37165,24574],{"class":931},[679,37167,37168],{"class":689}," angular-cli",[679,37170,27367],{"class":689},[679,37172,37173,37175,37178],{"class":681,"line":790},[679,37174,24568],{"class":880},[679,37176,37177],{"class":689}," cache",[679,37179,37180],{"class":689}," clean\n",[679,37182,37183,37185,37187,37189],{"class":681,"line":892},[679,37184,24568],{"class":880},[679,37186,24571],{"class":689},[679,37188,24574],{"class":931},[679,37190,27367],{"class":689},[5909,37192,37194],{"id":37193},"update-angular-cli","Update Angular CLI",[651,37196,37197],{},"In my case, I was running a newer version so we can just run a simple command to update.",[669,37199,37200],{"className":5851,"code":27354,"language":5853,"meta":674,"style":674},[676,37201,37202],{"__ignoreMap":674},[679,37203,37204,37206,37208,37210],{"class":681,"line":682},[679,37205,24568],{"class":880},[679,37207,24571],{"class":689},[679,37209,24574],{"class":931},[679,37211,27367],{"class":689},[651,37213,37214,37215,37217],{},"Now if I run ",[676,37216,37120],{}," you can see that I have updated to the latest stable version which is 1.3.2",[651,37219,37220],{},[660,37221],{"alt":21522,"src":37222},"./2017-09-06_08-20-29.png",[4542,37224,37226],{"id":37225},"whats-new-in-angular-cli-13","What's New in Angular CLI 1.3",[651,37228,37229],{},"Angular CLI now officially supports ES2017 and TypeScript 2.4 paving the way for Angular 5 which should be released very soon!",[5909,37231,37233],{"id":37232},"universal-support-server-side-rendering","Universal Support (Server Side Rendering)",[651,37235,37236,37237,37240,37241,37244],{},"Angular Universal Support allows you to run your Angular code on the server side as opposed to in the browser. Angular CLI supports a generation of a Universal build for your application. This is a CommonJS-formatted bundle which can be ",[676,37238,37239],{},"require()"," into a Node application (for example, an Express server) and used with ",[676,37242,37243],{},"@angular/platform-server"," 's APIs to pre-render your application.",[651,37246,37247],{},[812,37248,37251],{"href":37249,"rel":37250},"https://github.com/angular/angular-cli/wiki/stories-universal-rendering",[816],"Follow this tutorial to check it out.",[5909,37253,37255],{"id":37254},"build-optimizer","Build Optimizer",[651,37257,37258],{},"Angular CLI 1.3 upgrades us to Webpack 3 which brings us some new features a bug fixes. We also get a new optimizer that is meant to slim down our applications in production.",[669,37260,37262],{"className":5851,"code":37261,"language":5853,"meta":674,"style":674},"ng build --prod --build-optimizer\n",[676,37263,37264],{"__ignoreMap":674},[679,37265,37266,37268,37271,37274],{"class":681,"line":682},[679,37267,27380],{"class":880},[679,37269,37270],{"class":689}," build",[679,37272,37273],{"class":931}," --prod",[679,37275,37276],{"class":931}," --build-optimizer\n",[5909,37278,37280],{"id":37279},"named-chunks","Named Chunks",[651,37282,37283],{},"As long as we are talking about builds we should mention a new flag --named-chunks which will generate named chunks. Prior to Angular CLI 1.3 all of the lazily loaded chunks were named:",[669,37285,37287],{"className":5851,"code":37286,"language":5853,"meta":674,"style":674},"0.js\n1.js\n2.js\netc...\n",[676,37288,37289,37294,37299,37304],{"__ignoreMap":674},[679,37290,37291],{"class":681,"line":682},[679,37292,37293],{"class":880},"0.js\n",[679,37295,37296],{"class":681,"line":790},[679,37297,37298],{"class":880},"1.js\n",[679,37300,37301],{"class":681,"line":892},[679,37302,37303],{"class":880},"2.js\n",[679,37305,37306],{"class":681,"line":901},[679,37307,37308],{"class":880},"etc...\n",[651,37310,37311],{},"This setting can be enabled disabled with the namedChunks option in angular-cli.json and defaults to true. With that option in place, we will get some nicely named chunks.",[669,37313,37315],{"className":5851,"code":37314,"language":5853,"meta":674,"style":674},"products.module.chunk.js\norders.module.chunk.js\n",[676,37316,37317,37322],{"__ignoreMap":674},[679,37318,37319],{"class":681,"line":682},[679,37320,37321],{"class":880},"products.module.chunk.js\n",[679,37323,37324],{"class":681,"line":790},[679,37325,37326],{"class":880},"orders.module.chunk.js\n",[5909,37328,37330],{"id":37329},"proxy-configuration","Proxy Configuration",[651,37332,37333],{},"It's been possible to define a proxy configuration for awhile now. This allows to match a pattern and forward all of those requests to our server side application. I do this a lot in my Spring Boot applications with something like this.",[669,37335,37337],{"className":25132,"code":37336,"language":25134,"meta":674,"style":674},"{\n \"/api\": {\n \"target\": \"http://localhost:8080\"\n }\n}\n",[676,37338,37339,37343,37350,37360,37364],{"__ignoreMap":674},[679,37340,37341],{"class":681,"line":682},[679,37342,28448],{"class":693},[679,37344,37345,37348],{"class":681,"line":790},[679,37346,37347],{"class":689}," \"/api\"",[679,37349,28468],{"class":693},[679,37351,37352,37355,37357],{"class":681,"line":892},[679,37353,37354],{"class":689}," \"target\"",[679,37356,4282],{"class":693},[679,37358,37359],{"class":689},"\"http://localhost:8080\"\n",[679,37361,37362],{"class":681,"line":901},[679,37363,21405],{"class":693},[679,37365,37366],{"class":681,"line":909},[679,37367,996],{"class":693},[651,37369,37370],{},"The problem was you had to modify the ng serve command so that it took in your proxy config",[669,37372,37374],{"className":5851,"code":37373,"language":5853,"meta":674,"style":674},"ng serve --proxy-config proxy.conf.json\n",[676,37375,37376],{"__ignoreMap":674},[679,37377,37378,37380,37383,37386],{"class":681,"line":682},[679,37379,27380],{"class":880},[679,37381,37382],{"class":689}," serve",[679,37384,37385],{"class":931}," --proxy-config",[679,37387,37388],{"class":689}," proxy.conf.json\n",[651,37390,37391],{},"Now we can simply configure a proxyConfig in our angular-cli.json and the serve command will pick it up automatically.",[5909,37393,37395],{"id":37394},"bug-fixes","Bug Fixes",[651,37397,37398],{},"As always a considerable amount of bug fixes!",[4542,37400,37402],{"id":37401},"angular-cli-13-release-notes","Angular CLI 1.3 Release Notes",[651,37404,37405],{},"This is release 1.3.0 of the Angular CLI. As a minor release, it adds a lot of new features (as well as a considerable amount of bug fixes) from the 1.2 releases.",[651,37407,37408,664],{},[812,37409,37412],{"href":37410,"rel":37411},"https://github.com/angular/angular-cli/releases/tag/v1.3.0",[816],"Read the full release notes here",[4542,37414,9042],{"id":9041},[651,37416,37417],{},"The Angular team is constantly improving the CLI and it's exciting to see some of the new features and bug fixes.",[786,37419,37420],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}",{"title":674,"searchDepth":790,"depth":790,"links":37422},[37423,37427,37434,37435],{"id":37107,"depth":790,"text":37108,"children":37424},[37425,37426],{"id":37133,"depth":892,"text":37134},{"id":37193,"depth":892,"text":37194},{"id":37225,"depth":790,"text":37226,"children":37428},[37429,37430,37431,37432,37433],{"id":37232,"depth":892,"text":37233},{"id":37254,"depth":892,"text":37255},{"id":37279,"depth":892,"text":37280},{"id":37329,"depth":892,"text":37330},{"id":37394,"depth":892,"text":37395},{"id":37401,"depth":790,"text":37402},{"id":9041,"depth":790,"text":9042},{"slug":37437,"published":797,"date":37438,"tags":37439,"cover":37440},"upgrade-new-angular-command-line-interface-cli-1-3-release","2017-09-06T09:07:30-04:00",[17866],"./pexels-photo-247791-1024x562.png",{"title":591,"description":591},"blog/2017/09/06/upgrade-new-angular-command-line-interface-cli-1-3-release","ZhlQZmDUFfTkTuJMC1rE_5y4aGQNH29ilx74pQ8225g",{"id":37445,"title":588,"body":37446,"description":588,"extension":793,"meta":37592,"navigation":797,"path":589,"seo":37597,"stem":37598,"__hash__":37599},"content/blog/2017/09/11/monolithic-vs-microservices.md",{"type":648,"value":37447,"toc":37586},[37448,37456,37460,37463,37468,37471,37477,37480,37484,37487,37499,37506,37511,37514,37530,37533,37537,37546,37549,37571,37574,37576,37579],[651,37449,37450,37451,37455],{},"I am currently working on wrapping up a new section on MicroServices for ",[812,37452,37454],{"href":30833,"rel":37453},[816],"my new course on JHipster",". We could easily create a whole course around MicroServices alone so I am trying to keep this to more of an introduction. I think one of the questions I hear most is \"When should I use a MicroServices architecture in place of a Monolithic application?\". In this article, we will review what both of these are as well as give some recommendations on when to use Monolithic and when to use MicrosServices. ",[4542,37457,37459],{"id":37458},"monolithic-architecture","Monolithic Architecture",[651,37461,37462],{},"A monolithic application is one that most of us are currently working on or have worked on in the past. Wikipedia defines a Monolithic application as:",[1004,37464,37465],{},[651,37466,37467],{},"In software engineering, a monolithic application describes a single-tiered software application in which the user interface and data access code are combined into a single program from a single platform.",[651,37469,37470],{},"I don't know about you but these are the types of applications I have been building for almost 20 years now. Before we get too far I want you to take a deep breath and I am here to say there is nothing wrong with these types of applications. Please don't run out and start refactoring your monolithic applications because you keep hearing about Microservices flying around at every conference you visit. ",[651,37472,37473],{},[660,37474],{"alt":37475,"src":37476},"Monolithic Application","./road-street-sign-way-1-1024x682.jpg",[651,37478,37479],{},"It is often a good idea to start with a Monolithic Architecture because it is way less complex for teams and less moving parts. ",[4542,37481,37483],{"id":37482},"microservices-architecture","Microservices Architecture",[651,37485,37486],{},"Wikipedia defines Microservices as:",[1004,37488,37489],{},[651,37490,37491,13517,37495],{},[679,37492,37494],{"style":37493},"s1","Microservices",[679,37496,37498],{"style":37497},"s2","is a variant of the service-oriented architecture (SOA) architectural style that structures an application as a collection of loosely coupled services. In a microservices architecture, services should be fine-grained and the protocols should be lightweight. The benefit of decomposing an application into different smaller services is that it improves modularity and makes the application easier to understand, develop and test. It also parallelizes development by enabling small autonomous teams to develop, deploy and scale their respective services independently. It also allows the architecture of an individual service to emerge through continuous refactoring. Microservices-based architectures enable continuous delivery and deployment.",[651,37500,37501,37502],{},"What a Microservice boils down to a is something does one thing very well and can live independent of the overall application. In the course, I used the following example. ",[679,37503,37505],{"style":37504},"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;","We run a conference and for years we have been on this monolithic application that manages everything. We have been growing like crazy and it’s time for some big changes.",[651,37507,37508],{},[660,37509],{"alt":37494,"src":37510},"./IMG_5429-1024x768.jpg",[651,37512,37513],{},"When we first started out we built out the functionality to list out speakers, sessions and from that data build an agenda. As we are growing we think about adding the following functionality. ",[5316,37515,37516,37519,37522,37525,37528],{},[5332,37517,37518],{},"Registration (We did this through 3rd party previously) ",[5332,37520,37521],{},"Blog",[5332,37523,37524],{},"Store",[5332,37526,37527],{},"Pre-Conference Training",[5332,37529,37524],{},[651,37531,37532],{},"As you can see those are not only new functions of our application but they seem like components that do one thing well and can function independently in our larger conference application. This is a great way for our organization and our different teams to develop and deploy independently of each other quickly. ",[4542,37534,37536],{"id":37535},"when-to-use-microservices","When to use MicroServices",[651,37538,37539,37540,37545],{},"So now that we know a little bit more about each of these architecture types we need to answer our original question. When should we use Microservices over Monolithic? When I was doing research on this very question I came across ",[812,37541,37544],{"href":37542,"rel":37543},"https://martinfowler.com/bliki/MonolithFirst.html",[816],"an article from Martin Fowler",". If you don't know who Martin is he is probably one of the most brilliant minds in the Software Development world and author of a book that helped me out a lot. ",[651,37547,37548],{},"In this article, Martin says the following: ",[1004,37550,37551,37560,37568],{},[651,37552,37553,37554,37559],{},"As I hear stories about teams using a ",[812,37555,37558],{"href":37556,"rel":37557},"https://martinfowler.com/articles/microservices.html",[816],"microservices architecture",", I've noticed a common pattern.",[27665,37561,37562,37565],{},[5332,37563,37564],{},"Almost all the successful microservice stories have started with a monolith that got too big and was broken up",[5332,37566,37567],{},"Almost all the cases where I've heard of a system that was built as a microservice system from scratch, it has ended up in serious trouble.",[651,37569,37570],{},"This pattern has led many of my colleagues to argue that **you shouldn't start a new project with microservices, even if you're sure your application will be big enough to make it worthwhile. **.",[651,37572,37573],{},"The point of this is that just because Microservices are all the buzz these days it doesn't mean all new applications should start here. In fact as Martin states if you're starting with a Microservice out of the gate you're probably going to run into trouble. ",[4542,37575,9042],{"id":9041},[651,37577,37578],{},"We all suffer from shiny object syndrome from time to time. We hear the term MicroServices and we want to jump right in. I hope this article makes you think about when to use Microservices.",[651,37580,37581],{},[7300,37582,37583,37585],{},[2939,37584,15394],{},": Are you using MicroServices in your development today? If so what advice would you give to others?",{"title":674,"searchDepth":790,"depth":790,"links":37587},[37588,37589,37590,37591],{"id":37458,"depth":790,"text":37459},{"id":37482,"depth":790,"text":37483},{"id":37535,"depth":790,"text":37536},{"id":9041,"depth":790,"text":9042},{"slug":37593,"published":797,"date":37594,"tags":37595,"cover":37596},"monolithic-vs-microservices","2017-09-11T08:30:19-04:00",[22017,7055],"./pexels-photo-325229-760x266.jpeg",{"title":588,"description":588},"blog/2017/09/11/monolithic-vs-microservices","q4_eTdPAz7z4Kf9511V4unT-5Via0IpkjzCz84OOVe4",{"id":37601,"title":585,"body":37602,"description":585,"extension":793,"meta":37762,"navigation":797,"path":586,"seo":37767,"stem":37768,"__hash__":37769},"content/blog/2017/09/22/get-hot-java-9.md",{"type":648,"value":37603,"toc":37747},[37604,37613,37615,37619,37622,37628,37632,37635,37639,37642,37646,37649,37653,37656,37660,37663,37667,37670,37674,37677,37681,37710,37719,37723,37730,37735,37737,37740],[651,37605,37606,37607,37612],{},"After a few delays, ",[812,37608,37611],{"href":37609,"rel":37610},"http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.html",[816],"Java 9 has finally been released",". This is the first major release of Java since March 2014. Java 9 is headlined by Jigsaw which is Java's new modularity system. This is a huge change to how we will write Java programs going forward but there are also a lot of other great features being released in 9. In this article, we will talk briefly about some of the highlights in Java 9.",[4542,37614,21641],{"id":17832},[5909,37616,37618],{"id":37617},"jigsaw-java-modularity","Jigsaw (Java Modularity) ",[651,37620,37621],{},"Remember when I said that there were a few delays in shipping Java 9? Well, this was mainly the feature that caused it. There was a lot of criticism about this new feature and I still don't think everyone is on the same page. Project Jigsaw aims to design and implement a standard module system for the Java SE Platform and to apply that system to the Platform itself, and to the JDK. Its primary goals are to make implementations of the Platform more easily scalable down to small devices, improve security and maintainability, enable improved application performance, and provide developers with better tools for programming in the large.",[651,37623,37624],{},[660,37625],{"alt":37626,"src":37627},"Java 9: Project Jigsaw","./pexels-photo-218443-1-1024x682.jpeg",[5909,37629,37631],{"id":37630},"jshell","JShell",[651,37633,37634],{},"The JShell API and tool will provide a way to interactively evaluate declarations, statements, and expressions of the Java programming language within the JShell state. The JShell state includes an evolving code and execution state. To facilitate rapid investigation and coding, statements and expressions need not occur within a method, and variables and method need not occur within a class. The JShell tool will be a command-line tool with features to ease interaction including a history with editing, tab-completion, automatic addition of needed terminal semicolons, and configurable predefined imports and definitions.",[5909,37636,37638],{"id":37637},"http2","Http/2",[651,37640,37641],{},"Java 9 defines a new HTTP client API that implements HTTP/2 and WebSocket. This will allow us to replace the legacy HttpURLConnection API. This is only going to help us improve performance and faster loading pages. ",[5909,37643,37645],{"id":37644},"stream-api-enhancements","Stream API Enhancements",[651,37647,37648],{},"The streams API that was released in Java 8 was a really great start. Java 9 fills in some of the holes here and gives us the ability to conditionally take or drop items from the stream. Along with a few other updates, these are great enhancements. ",[5909,37650,37652],{"id":37651},"process-api-updates","Process API Updates",[651,37654,37655],{},"Improve the API for controlling and managing operating-system processes.",[5909,37657,37659],{"id":37658},"multi-release-jars","Multi-Release Jars",[651,37661,37662],{},"This is especially huge for any third party library creators. Currently, when a new version of Java is released they have to ship a new version of their libraries. With this update, they will be able to ship one JAR that contains multiple versions based on the JVM version being used. ",[5909,37664,37666],{"id":37665},"conveniencefactory-methods-for-collections","Convenience Factory methods for Collections",[651,37668,37669],{},"I am always a huge fan API changes that allow me to write less code and this is one of them. Java 9 will Provide static factory methods on the collection interfaces that will create compact, unmodifiable collection instances. The API is deliberately kept minimal.",[5909,37671,37673],{"id":37672},"private-interface-methods","Private Interface Methods",[651,37675,37676],{},"You now have the ability to create private methods in your interfaces.",[5909,37678,37680],{"id":37679},"other-new-features","Other New Features",[5316,37682,37683,37689,37696,37703],{},[5332,37684,37685],{},[812,37686,17750],{"href":37687,"rel":37688},"http://openjdk.java.net/jeps/158",[816],[5332,37690,37691],{},[812,37692,37695],{"href":37693,"rel":37694},"http://openjdk.java.net/jeps/236",[816],"Parser API for Nashorn",[5332,37697,37698],{},[812,37699,37702],{"href":37700,"rel":37701},"http://openjdk.java.net/jeps/224",[816],"Javadoc Improvements",[5332,37704,37705],{},[812,37706,37709],{"href":37707,"rel":37708},"http://openjdk.java.net/jeps/248",[816],"Garbage Collector Improvements",[651,37711,37712,37713,37718],{},"These were just some of the highlights of what was released in Java 9. If you're a geek like me (and I'm guessing you are) you can ",[812,37714,37717],{"href":37715,"rel":37716},"http://www.oracle.com/technetwork/java/javase/documentation/9u-relnotes-3704429.html",[816],"dig through the release notes here"," for all the goodies. ",[4542,37720,37722],{"id":37721},"download-java-9","Download Java 9",[651,37724,37725,37726,15266],{},"There are so many new features and I will do my best to highlight them here on the blog. Java 9 is now publicly available and can be ",[812,37727,37729],{"href":37609,"rel":37728},[816],"downloaded from here",[651,37731,37732],{},[660,37733],{"alt":37722,"src":37734},"./luis-llerena-14779-1024x683.jpg",[4542,37736,9042],{"id":9041},[651,37738,37739],{},"Java 9 is a really exciting release and I am excited to see what others think of it. ",[651,37741,37742],{},[7300,37743,37744,37746],{},[2939,37745,11650],{}," What is your favorite new feature in Java 9?",{"title":674,"searchDepth":790,"depth":790,"links":37748},[37749,37760,37761],{"id":17832,"depth":790,"text":21641,"children":37750},[37751,37752,37753,37754,37755,37756,37757,37758,37759],{"id":37617,"depth":892,"text":37618},{"id":37630,"depth":892,"text":37631},{"id":37637,"depth":892,"text":37638},{"id":37644,"depth":892,"text":37645},{"id":37651,"depth":892,"text":37652},{"id":37658,"depth":892,"text":37659},{"id":37665,"depth":892,"text":37666},{"id":37672,"depth":892,"text":37673},{"id":37679,"depth":892,"text":37680},{"id":37721,"depth":790,"text":37722},{"id":9041,"depth":790,"text":9042},{"slug":37763,"published":797,"date":37764,"tags":37765,"cover":37766},"get-hot-java-9","2017-09-22T08:21:00-04:00",[4109],"./2017-09-22_07-39-27-760x504.png",{"title":585,"description":585},"blog/2017/09/22/get-hot-java-9","YWwB5wp1qO1m98hrsHkxtE9rTA2IscZR6JBekEf1E3g",{"id":37771,"title":582,"body":37772,"description":582,"extension":793,"meta":38439,"navigation":797,"path":583,"seo":38444,"stem":38445,"__hash__":38446},"content/blog/2017/10/04/compile-groovy-java-gradle-build.md",{"type":648,"value":37773,"toc":38432},[37774,37777,37780,37784,37787,37793,37799,37802,37808,37811,37816,37820,37823,38015,38018,38167,38170,38176,38180,38183,38334,38337,38341,38344,38418,38421,38423,38429],[651,37775,37776],{},"I had an interesting problem come up at work last week and I would like to share it with you today. A coworker was having an issue with a Gradle build. Whenever he tried to compile the project it threw an error saying it couldn't find the Groovy class he was trying to call from Java.",[651,37778,37779],{},"In this article, we are going to walk through setting up a project that will illustrate this problem. We will then examine what normal behavior is in a Gradle build and how we can make a change to fix this problem that we were facing. ",[4542,37781,37783],{"id":37782},"create-a-new-gradle-project","Create a new Gradle Project",[651,37785,37786],{},"The first thing we are going to do is create a new project using Gradle. This is going to give us a standard Gradle & Java project layout. ",[651,37788,37789],{},[660,37790],{"alt":37791,"src":37792},"New Gradle Project","./2017-10-04_07-37-26-300x197.png",[651,37794,37795],{},[660,37796],{"alt":37797,"src":37798},"Gradle Wrapper","./2017-10-04_07-38-08-300x197.png",[651,37800,37801],{},"The next thing we need to do is add support to our IDE for Groovy.",[651,37803,37804],{},[660,37805],{"alt":37806,"src":37807},"Adding Groovy Support","./2017-10-04_07-43-08-300x263.png",[651,37809,37810],{},"With that in place, we are going to add a new groovy folder to our main directory. ",[651,37812,37813],{},[660,37814],{"alt":674,"src":37815},"./2017-10-04_07-44-38.png",[4542,37817,37819],{"id":37818},"create-a-new-groovy-class","Create a new Groovy class",[651,37821,37822],{},"Now that a lot of our setup is done we get to finally write some code. I created a new package \"com.foo\" under my Groovy src folder and created a simple class called Developer.",[669,37824,37826],{"className":4107,"code":37825,"language":4109,"meta":674,"style":674},"package com.foo\n\nclass Developer {\n\n String firstName\n String lastName\n String email\n String github\n\n List\u003CString> languages = \\[\\]\n\n Developer(String firstName, String lastName, String email, String github, List\u003CString> languages) {\n this.firstName = firstName\n this.lastName = lastName\n this.email = email\n this.github = github\n this.languages = languages\n }\n}\n",[676,37827,37828,37835,37839,37849,37853,37866,37877,37884,37891,37895,37907,37911,37954,37970,37986,37993,38000,38007,38011],{"__ignoreMap":674},[679,37829,37830,37832],{"class":681,"line":682},[679,37831,2543],{"class":685},[679,37833,37834],{"class":693}," com.foo\n",[679,37836,37837],{"class":681,"line":790},[679,37838,889],{"emptyLinePlaceholder":797},[679,37840,37841,37843,37846],{"class":681,"line":892},[679,37842,877],{"class":6561},[679,37844,37845],{"class":6561}," D",[679,37847,37848],{"class":693},"eveloper {\n",[679,37850,37851],{"class":681,"line":901},[679,37852,889],{"emptyLinePlaceholder":797},[679,37854,37855,37857,37860,37863],{"class":681,"line":909},[679,37856,7214],{"class":6561},[679,37858,37859],{"class":693},"tring first",[679,37861,37862],{"class":6561},"N",[679,37864,37865],{"class":693},"ame\n",[679,37867,37868,37870,37873,37875],{"class":681,"line":918},[679,37869,7214],{"class":6561},[679,37871,37872],{"class":693},"tring last",[679,37874,37862],{"class":6561},[679,37876,37865],{"class":693},[679,37878,37879,37881],{"class":681,"line":935},[679,37880,7214],{"class":6561},[679,37882,37883],{"class":693},"tring email\n",[679,37885,37886,37888],{"class":681,"line":944},[679,37887,7214],{"class":6561},[679,37889,37890],{"class":693},"tring github\n",[679,37892,37893],{"class":681,"line":959},[679,37894,889],{"emptyLinePlaceholder":797},[679,37896,37897,37900,37902,37904],{"class":681,"line":964},[679,37898,37899],{"class":6561}," L",[679,37901,7427],{"class":693},[679,37903,7406],{"class":6561},[679,37905,37906],{"class":693},"tring> languages = \\[\\]\n",[679,37908,37909],{"class":681,"line":977},[679,37910,889],{"emptyLinePlaceholder":797},[679,37912,37913,37915,37918,37920,37922,37924,37927,37929,37931,37933,37935,37937,37940,37942,37945,37947,37949,37951],{"class":681,"line":982},[679,37914,7243],{"class":6561},[679,37916,37917],{"class":693},"eveloper(",[679,37919,7406],{"class":6561},[679,37921,37859],{"class":693},[679,37923,37862],{"class":6561},[679,37925,37926],{"class":693},"ame, ",[679,37928,7406],{"class":6561},[679,37930,37872],{"class":693},[679,37932,37862],{"class":6561},[679,37934,37926],{"class":693},[679,37936,7406],{"class":6561},[679,37938,37939],{"class":693},"tring email, ",[679,37941,7406],{"class":6561},[679,37943,37944],{"class":693},"tring github, ",[679,37946,7424],{"class":6561},[679,37948,7427],{"class":693},[679,37950,7406],{"class":6561},[679,37952,37953],{"class":693},"tring> languages) {\n",[679,37955,37956,37958,37961,37963,37966,37968],{"class":681,"line":988},[679,37957,7862],{"class":6561},[679,37959,37960],{"class":693},".first",[679,37962,37862],{"class":6561},[679,37964,37965],{"class":693},"ame = first",[679,37967,37862],{"class":6561},[679,37969,37865],{"class":693},[679,37971,37972,37974,37977,37979,37982,37984],{"class":681,"line":993},[679,37973,7862],{"class":6561},[679,37975,37976],{"class":693},".last",[679,37978,37862],{"class":6561},[679,37980,37981],{"class":693},"ame = last",[679,37983,37862],{"class":6561},[679,37985,37865],{"class":693},[679,37987,37988,37990],{"class":681,"line":2129},[679,37989,7862],{"class":6561},[679,37991,37992],{"class":693},".email = email\n",[679,37994,37995,37997],{"class":681,"line":2140},[679,37996,7862],{"class":6561},[679,37998,37999],{"class":693},".github = github\n",[679,38001,38002,38004],{"class":681,"line":2145},[679,38003,7862],{"class":6561},[679,38005,38006],{"class":693},".languages = languages\n",[679,38008,38009],{"class":681,"line":2154},[679,38010,985],{"class":693},[679,38012,38013],{"class":681,"line":2159},[679,38014,996],{"class":693},[651,38016,38017],{},"Next, we will modify our Gradle build. We are going to add the Groovy dependency and a couple plugins. The groovy plugin gives us the ability to compile the Groovy code. ",[669,38019,38021],{"className":4107,"code":38020,"language":4109,"meta":674,"style":674},"group 'com.therealdanvega'\nversion '1.0-SNAPSHOT'\n\napply plugin: 'java'\napply plugin: 'groovy'\napply plugin: 'idea'\n\nsourceCompatibility = 1.8\n\nrepositories {\n mavenCentral()\n}\n\ndependencies {\n compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.9'\n testCompile group: 'junit', name: 'junit', version: '4.12'\n}\n",[676,38022,38023,38031,38039,38043,38053,38062,38071,38075,38085,38089,38094,38101,38105,38109,38114,38140,38163],{"__ignoreMap":674},[679,38024,38025,38028],{"class":681,"line":682},[679,38026,38027],{"class":693},"group ",[679,38029,38030],{"class":689},"'com.therealdanvega'\n",[679,38032,38033,38036],{"class":681,"line":790},[679,38034,38035],{"class":693},"version ",[679,38037,38038],{"class":689},"'1.0-SNAPSHOT'\n",[679,38040,38041],{"class":681,"line":892},[679,38042,889],{"emptyLinePlaceholder":797},[679,38044,38045,38048,38050],{"class":681,"line":901},[679,38046,38047],{"class":693},"apply plugin",[679,38049,2391],{"class":685},[679,38051,38052],{"class":689}," 'java'\n",[679,38054,38055,38057,38059],{"class":681,"line":909},[679,38056,38047],{"class":693},[679,38058,2391],{"class":685},[679,38060,38061],{"class":689}," 'groovy'\n",[679,38063,38064,38066,38068],{"class":681,"line":918},[679,38065,38047],{"class":693},[679,38067,2391],{"class":685},[679,38069,38070],{"class":689}," 'idea'\n",[679,38072,38073],{"class":681,"line":935},[679,38074,889],{"emptyLinePlaceholder":797},[679,38076,38077,38080,38082],{"class":681,"line":944},[679,38078,38079],{"class":693},"sourceCompatibility ",[679,38081,686],{"class":685},[679,38083,38084],{"class":931}," 1.8\n",[679,38086,38087],{"class":681,"line":959},[679,38088,889],{"emptyLinePlaceholder":797},[679,38090,38091],{"class":681,"line":964},[679,38092,38093],{"class":693},"repositories {\n",[679,38095,38096,38099],{"class":681,"line":977},[679,38097,38098],{"class":880}," mavenCentral",[679,38100,17545],{"class":693},[679,38102,38103],{"class":681,"line":982},[679,38104,996],{"class":693},[679,38106,38107],{"class":681,"line":988},[679,38108,889],{"emptyLinePlaceholder":797},[679,38110,38111],{"class":681,"line":993},[679,38112,38113],{"class":693},"dependencies {\n",[679,38115,38116,38119,38121,38124,38127,38129,38132,38135,38137],{"class":681,"line":2129},[679,38117,38118],{"class":693}," compile group",[679,38120,2391],{"class":685},[679,38122,38123],{"class":689}," 'org.codehaus.groovy'",[679,38125,38126],{"class":693},", name",[679,38128,2391],{"class":685},[679,38130,38131],{"class":689}," 'groovy-all'",[679,38133,38134],{"class":693},", version",[679,38136,2391],{"class":685},[679,38138,38139],{"class":689}," '2.4.9'\n",[679,38141,38142,38145,38147,38150,38152,38154,38156,38158,38160],{"class":681,"line":2140},[679,38143,38144],{"class":693}," testCompile group",[679,38146,2391],{"class":685},[679,38148,38149],{"class":689}," 'junit'",[679,38151,38126],{"class":693},[679,38153,2391],{"class":685},[679,38155,38149],{"class":689},[679,38157,38134],{"class":693},[679,38159,2391],{"class":685},[679,38161,38162],{"class":689}," '4.12'\n",[679,38164,38165],{"class":681,"line":2145},[679,38166,996],{"class":693},[651,38168,38169],{},"We should now be able to run the compileGroovy task from Gradle. ",[651,38171,38172],{},[660,38173],{"alt":38174,"src":38175},"Compile Groovy before Java","./2017-10-04_08-13-49.png",[4542,38177,38179],{"id":38178},"creating-the-java-application","Creating the Java Application",[651,38181,38182],{},"It's time to focus on the Java side of this project and create our main application. I have created a very simple application that simply creates a new developer. ",[669,38184,38186],{"className":4107,"code":38185,"language":4109,"meta":674,"style":674},"package com.therealdanvega;\n\nimport com.foo.Developer;\n\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class Application {\n\n public static void main(String\\[\\] args) {\n List\u003CString> languages = Arrays.asList(\"Java\",\"Groovy\");\n Developer developer = new Developer(\"Dan\",\"Vega\",\"danvega@gmail.com\",\"cfaddict\", languages);\n }\n\n}\n",[676,38187,38188,38194,38198,38205,38209,38216,38223,38227,38237,38241,38261,38290,38322,38326,38330],{"__ignoreMap":674},[679,38189,38190,38192],{"class":681,"line":682},[679,38191,2543],{"class":685},[679,38193,6039],{"class":693},[679,38195,38196],{"class":681,"line":790},[679,38197,889],{"emptyLinePlaceholder":797},[679,38199,38200,38202],{"class":681,"line":892},[679,38201,1999],{"class":685},[679,38203,38204],{"class":693}," com.foo.Developer;\n",[679,38206,38207],{"class":681,"line":901},[679,38208,889],{"emptyLinePlaceholder":797},[679,38210,38211,38213],{"class":681,"line":909},[679,38212,1999],{"class":685},[679,38214,38215],{"class":693}," java.util.Arrays;\n",[679,38217,38218,38220],{"class":681,"line":918},[679,38219,1999],{"class":685},[679,38221,38222],{"class":693}," java.util.List;\n",[679,38224,38225],{"class":681,"line":935},[679,38226,889],{"emptyLinePlaceholder":797},[679,38228,38229,38231,38233,38235],{"class":681,"line":944},[679,38230,6073],{"class":685},[679,38232,4512],{"class":685},[679,38234,16878],{"class":880},[679,38236,884],{"class":693},[679,38238,38239],{"class":681,"line":959},[679,38240,889],{"emptyLinePlaceholder":797},[679,38242,38243,38245,38247,38249,38251,38253,38255,38257,38259],{"class":681,"line":964},[679,38244,6089],{"class":685},[679,38246,6092],{"class":685},[679,38248,6095],{"class":685},[679,38250,6098],{"class":880},[679,38252,745],{"class":693},[679,38254,4758],{"class":2099},[679,38256,6105],{"class":693},[679,38258,6108],{"class":2099},[679,38260,4390],{"class":693},[679,38262,38263,38265,38267,38270,38272,38275,38278,38280,38283,38285,38288],{"class":681,"line":977},[679,38264,16659],{"class":693},[679,38266,4758],{"class":685},[679,38268,38269],{"class":693},"> languages ",[679,38271,686],{"class":685},[679,38273,38274],{"class":693}," Arrays.",[679,38276,38277],{"class":880},"asList",[679,38279,745],{"class":693},[679,38281,38282],{"class":689},"\"Java\"",[679,38284,1202],{"class":693},[679,38286,38287],{"class":689},"\"Groovy\"",[679,38289,1208],{"class":693},[679,38291,38292,38295,38297,38299,38302,38304,38306,38308,38310,38312,38314,38316,38319],{"class":681,"line":982},[679,38293,38294],{"class":693}," Developer developer ",[679,38296,686],{"class":685},[679,38298,2054],{"class":685},[679,38300,38301],{"class":880}," Developer",[679,38303,745],{"class":693},[679,38305,1414],{"class":689},[679,38307,1202],{"class":693},[679,38309,9343],{"class":689},[679,38311,1202],{"class":693},[679,38313,9679],{"class":689},[679,38315,1202],{"class":693},[679,38317,38318],{"class":689},"\"cfaddict\"",[679,38320,38321],{"class":693},", languages);\n",[679,38323,38324],{"class":681,"line":988},[679,38325,985],{"class":693},[679,38327,38328],{"class":681,"line":993},[679,38329,889],{"emptyLinePlaceholder":797},[679,38331,38332],{"class":681,"line":2129},[679,38333,996],{"class":693},[651,38335,38336],{},"If we try and run a Gradle build now it will fail and this is the problem we were facing at work. ",[4542,38338,38340],{"id":38339},"compiling-groovy-before-java","Compiling Groovy before Java",[651,38342,38343],{},"The reason this fails is that the default action for our Gradle build is to compile the Java class before it compiles the Groovy class. When this takes our Java class will have no idea what our Developer class is because it hasn't been compiled yet. No worries though, we can change this behavior with a little bit of code in our Gradle build. The Groovy plugin actually extends the Java plugin so that we can compile Groovy along with Java. What we are doing here is just telling Groovy to compile everything for us. When you understand what is going on behind the scenes this makes a lot of sense. ",[669,38345,38347],{"className":4107,"code":38346,"language":4109,"meta":674,"style":674},"sourceSets {\n main {\n java {\n srcDirs = \\[\\] // don't compile Java code twice\n }\n groovy {\n srcDirs = \\[ 'src/main/groovy', 'src/main/java' \\]\n }\n }\n}\n",[676,38348,38349,38354,38359,38364,38377,38381,38386,38406,38410,38414],{"__ignoreMap":674},[679,38350,38351],{"class":681,"line":682},[679,38352,38353],{"class":693},"sourceSets {\n",[679,38355,38356],{"class":681,"line":790},[679,38357,38358],{"class":693}," main {\n",[679,38360,38361],{"class":681,"line":892},[679,38362,38363],{"class":693}," java {\n",[679,38365,38366,38369,38371,38374],{"class":681,"line":901},[679,38367,38368],{"class":693}," srcDirs ",[679,38370,686],{"class":685},[679,38372,38373],{"class":693}," \\[\\] ",[679,38375,38376],{"class":1400},"// don't compile Java code twice\n",[679,38378,38379],{"class":681,"line":909},[679,38380,1290],{"class":693},[679,38382,38383],{"class":681,"line":918},[679,38384,38385],{"class":693}," groovy {\n",[679,38387,38388,38390,38392,38395,38398,38400,38403],{"class":681,"line":935},[679,38389,38368],{"class":693},[679,38391,686],{"class":685},[679,38393,38394],{"class":693}," \\[ ",[679,38396,38397],{"class":689},"'src/main/groovy'",[679,38399,2797],{"class":693},[679,38401,38402],{"class":689},"'src/main/java'",[679,38404,38405],{"class":693}," \\]\n",[679,38407,38408],{"class":681,"line":944},[679,38409,1290],{"class":693},[679,38411,38412],{"class":681,"line":959},[679,38413,985],{"class":693},[679,38415,38416],{"class":681,"line":964},[679,38417,996],{"class":693},[651,38419,38420],{},"Now if you were trying to call a Java class from Groovy the normal behavior would work out just fine. ",[4542,38422,9042],{"id":9041},[651,38424,38425,38426,38428],{},"This is a pretty common mistake when you try and mix both Java and Groovy. I loving mixing Java & Groovy in projects whenever I get the chance so hopefully, this taught you something and you will do the same in the near future. _",[2939,38427,11650],{}," Are you using Java & Groovy in your projects today? _",[786,38430,38431],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":38433},[38434,38435,38436,38437,38438],{"id":37782,"depth":790,"text":37783},{"id":37818,"depth":790,"text":37819},{"id":38178,"depth":790,"text":38179},{"id":38339,"depth":790,"text":38340},{"id":9041,"depth":790,"text":9042},{"slug":38440,"published":797,"date":38441,"tags":38442,"cover":38443},"compile-groovy-java-gradle-build","2017-10-04T08:32:40-04:00",[870],"./614px-Groovy-logo.svg_-e1459476180685.png",{"title":582,"description":582},"blog/2017/10/04/compile-groovy-java-gradle-build","weI9hckE_SxQLz2yxuXBTu4deoJqaaJlZQyH6UtyY20",{"id":38448,"title":579,"body":38449,"description":579,"extension":793,"meta":38664,"navigation":797,"path":580,"seo":38669,"stem":38670,"__hash__":38671},"content/blog/2017/11/08/angular-4-java-developers-course-now-live.md",{"type":648,"value":38450,"toc":38656},[38451,38463,38466,38470,38477,38482,38486,38533,38537,38545,38550,38576,38580,38583,38624,38628],[651,38452,38453,38454,2797,38459,38462],{},"I'm so excited to finally announce the release of my new course with ",[812,38455,38458],{"href":38456,"rel":38457},"https://springframework.guru",[816],"John Thompson",[2939,38460,38461],{},"Angular 4 Java Developers","! In this course, you will learn how to build applications using some of the hottest technologies in the industry today, including Spring Boot and Angular. These are two of our favorite technologies to work with right now, it really makes developing applications a breeze.",[651,38464,38465],{},"In this introduction to the course, I am going to give you all of the details, as well as a special offer so please read the entire article. ",[4542,38467,38469],{"id":38468},"about-angular-4-java-developers","About Angular 4 Java Developers",[651,38471,38472,38473,38476],{},"First off, I just want to say congrats to my partner in crime on this course, ",[812,38474,38458],{"href":38456,"rel":38475},[816],". We have put a lot of thought and hard work into this course and I think it's really going to show. We had a clear vision when we started this course and I think we ended up delivering. What we started out building is very different than what it looks like today, but you need to put yourself in your student's shoes and ask yourself \"Am I getting what I need out of this course?\" ",[651,38478,38479],{},[660,38480],{"alt":38461,"src":38481},"./UdemyJohnDan02-1024x576.png",[5909,38483,38485],{"id":38484},"what-you-will-learn","What you will learn",[5316,38487,38488,38497,38503,38509,38515,38518,38521,38527],{},[5332,38489,38490,11634,38493,38496],{},[2939,38491,38492],{},"Building Angular & Spring Boot apps",[2939,38494,38495],{},"without JHipster -"," To start this course off, we are going to build a small tasks application that demonstrates just what goes into putting all of these technologies together.",[5332,38498,38499,38502],{},[2939,38500,38501],{},"What is JHipster?"," Next, we are going to learn about what JHipster is and why you should never start another Angular & Spring Boot project without it again.",[5332,38504,38505,38508],{},[2939,38506,38507],{},"How to setup your development environment -"," Any good cook knows that if you don't have all of your ingredients and tools, it is really hard to prepare a meal. The same goes for you as a developer and your environment. We will walk you through setting up your environment and showing you the tools that you will need.",[5332,38510,38511,38514],{},[2939,38512,38513],{},"Compare starting from scratch & JHipster -"," We will take that same application we built from scratch and show you how we build it in JHipster.",[5332,38516,38517],{},"**JDL Studio -**A great way to build out your domain model.",[5332,38519,38520],{},"**MongoDB -**Learn how to use MongoDB in your applications.",[5332,38522,38523,38526],{},[2939,38524,38525],{},"Microservices -"," Are all the buzz these days. We are going to learn about what they are and more importantly when to use them. After that, we will learn how to build out microservices in our JHipster application.",[5332,38528,38529,38532],{},[2939,38530,38531],{},"Build and deploy a real-world application -"," We won't just teach you about how to build real-world applications, we will actually help you do it. In this course, you are going to build a real-world application from requirements to production.",[4542,38534,38536],{"id":38535},"running-for-brews","Running for Brews",[651,38538,38539,38540,38544],{},"I think the best part of this course is that we built a real-world application from conception to completion. We sat down with the ",[812,38541,38536],{"href":38542,"rel":38543},"http://runningforbrews.com/",[816]," organization and talked through the requirements with them. Running for Brews is a national running club that is all about bringing people together. In this course, we helped them build a loyalty program that is going to reward runners for consistently showing up. I had so much fun on this project that as soon as the weather gets warm again, I am going to organize my own RFB location! ",[651,38546,38547],{},[660,38548],{"alt":38536,"src":38549},"./2017-11-08_07-37-19-1024x647.png",[5316,38551,38552,38558,38564,38570,38573],{},[5332,38553,38554,38557],{},[2939,38555,38556],{},"Starting with requirements -"," Before you can build an application, you need to listen to your client and get the requirements. John and I sit down in an interview with the project stakeholder to find out what he wants this application to do.",[5332,38559,38560,38563],{},[2939,38561,38562],{},"Generate project from scratch -"," We will create this project from scratch so that you can follow right along with us.",[5332,38565,38566,38569],{},[2939,38567,38568],{},"Customize the UI -"," A lot of students have questions on how to customize the default UI in JHipster. We are going to walk through a number of UI changes to give our application a custom look and feel.",[5332,38571,38572],{},"**Helpful Exercises -**Even as we are building out this real-world application, we will stop to give you some helpful exercises to go through.",[5332,38574,38575],{},"**Production -**Where we all want to be, right? After our application is completed, we will show you what it takes to get us into production.",[4542,38577,38579],{"id":38578},"angular-4-java-developers-bonus-content","Angular 4 Java Developers Bonus Content",[651,38581,38582],{},"When you purchase this course, you will get access to exclusive bonus content!",[5316,38584,38585,38591,38600,38606,38612,38618],{},[5332,38586,38587,38590],{},[2939,38588,38589],{},"Downloadable lessons -"," There are 10 modules (over 11 total hours!) that you can take with you anywhere.",[5332,38592,38593,38596,38597,664],{},[2939,38594,38595],{},"IntelliJ Ultimate License -"," As a special bonus, you will have access to download a ",[2939,38598,38599],{},"90 day free trial of IntelliJ Ultimate Edition",[5332,38601,38602,38605],{},[2939,38603,38604],{},"Bonuses to keep you on track -"," Including interviews with the \"Running for Brews\" President on how this application is working out for them.",[5332,38607,38608,38611],{},[2939,38609,38610],{},"Exclusive access to a Facebook community -"," To discuss coursework and trade ideas with your fellow classmates.",[5332,38613,38614,38617],{},[2939,38615,38616],{},"Live Videos"," - As part of our community, you will have access to exclusive Facebook live events and recorded videos.",[5332,38619,38620,38623],{},[2939,38621,38622],{},"T-Shirt giveaways -"," If you liked the JHipster shirt Dan was wearing in some of the lessons, we will be giving a few of these away to members only.",[4542,38625,38627],{"id":38626},"what-are-you-waiting-for","What are you waiting for?",[651,38629,38630,38631,38636,38637,38642,38643,28997,38650,38655],{},"John and I have worked really hard on creating this course and we hope that you find it helpful in your software development journey. This course will normally sell for $200 but as part of the launch, I am giving my loyal blog readers a special gift. As a valued reader, I am going to offer you the course for ",[7300,38632,38633],{},[2939,38634,38635],{},"85% off regular price!"," Yep, you can get access to over 11 hours of content for just ",[7300,38638,38639],{},[2939,38640,38641],{},"$29.99!"," This special launch pricing is available until the end of ",[679,38644,38646],{"style":38645},"aBn",[679,38647,38649],{"style":38648},"aQJ","Sunday",[812,38651,38654],{"href":38652,"rel":38653},"https://therealdanvega.teachable.com/p/jhipster/?product_id=456739&coupon_code=TRDVLAUNCH_2999",[816],"Click here"," to buy the course for just $29.99!",{"title":674,"searchDepth":790,"depth":790,"links":38657},[38658,38661,38662,38663],{"id":38468,"depth":790,"text":38469,"children":38659},[38660],{"id":38484,"depth":892,"text":38485},{"id":38535,"depth":790,"text":38536},{"id":38578,"depth":790,"text":38579},{"id":38626,"depth":790,"text":38627},{"slug":38665,"published":797,"date":38666,"tags":38667,"cover":38668},"angular-4-java-developers-course-now-live","2017-11-08T16:15:32-05:00",[4109,7055],"./greetings_java_hipsters-760x525.png",{"title":579,"description":579},"blog/2017/11/08/angular-4-java-developers-course-now-live","E5TttsKXsBIqyQmZxp7BRJUuplGf_PSCum9C1Xb0LC8",{"id":38673,"title":576,"body":38674,"description":576,"extension":793,"meta":38993,"navigation":797,"path":577,"seo":38997,"stem":38998,"__hash__":38999},"content/blog/2017/12/08/getting-started-with-java.md",{"type":648,"value":38675,"toc":38984},[38676,38679,38688,38692,38695,38698,38701,38704,38709,38713,38716,38757,38761,38764,38767,38771,38775,38970,38972,38978],[651,38677,38678],{},"A few months ago I picked up a brand new 15\" MacBook Pro. I thought to myself this is a perfect time to show others how I set up my local development environment for Java. With a fresh machine, this would be a lot easier to take students from step 1 to writing Java applications.",[651,38680,38681,38682,38687],{},"The reason I am telling you this is because ",[812,38683,38686],{"href":38684,"rel":38685},"https://www.danvega.dev/courses/getting-started-with-java",[816],"I recently launched a new course around this",". In this article, I am going to give you my tips for getting started with Java as well as some details about this course.",[4542,38689,38691],{"id":38690},"getting-started-with-java","Getting Started with Java",[651,38693,38694],{},"When I first started out programming it was a little overwhelming to learn a new language. Part of that is because I am really really really old and we didn't have all the resources you have at your exposure today.",[651,38696,38697],{},"The other part of that is that it was really confusing to know what the easiest way was to set up a local development environment. If you were going to back a pie and didn't have a list of ingredients would you start baking? The answer is no, you need to have the required ingredients to bake and you need to have a solid development environment to write code. ",[651,38699,38700],{},"With this in mind, I set out to help people in 2 phases. First, I want you to quickly stand up a development environment. There are traditional ways of installing software packages and there better ways that make this step really easy to do. Once this is complete you can write your very first Java program.",[651,38702,38703],{},"The other phase is introducing you to concepts like build tools, other languages that run on the JVM and some tools and frameworks that you will come across in your journey to learning Java.",[651,38705,38706],{},[660,38707],{"alt":38691,"src":38708},"./pexels-photo-2-1024x682.jpg",[5909,38710,38712],{"id":38711},"key-terms","Key Terms",[651,38714,38715],{},"When I was \"Getting Started with Java\", one thing that tripped me up was all of the acronyms the language used. What is a JVM, JDK, JRE and how do they differ from each other. Here are a few concepts to help you get started. ",[5316,38717,38718,38723,38729,38735,38740,38746,38752],{},[5332,38719,38720,38722],{},[2939,38721,36422],{}," - Java is the programming language that we are using to write our applications in.",[5332,38724,38725,38728],{},[2939,38726,38727],{},"Java SE"," (Java Standard Edition) - When people are talking about Java, 99% of the time they are talking about the standard edition. There are other editions out there but we won't get into that at this time. ",[5332,38730,38731,38734],{},[2939,38732,38733],{},"JRE"," (Java Runtime Environment) - This is what you need to run Java applications. This is what non-developers will have installed on their machines to run Java applications. ",[5332,38736,38737,38739],{},[2939,38738,17708],{}," (Java Development Kit) - JDK is what we as Java developers need to write Java applications. This contains the JRE along with tools like compilers and debuggers that help us create applications. ",[5332,38741,38742,38745],{},[2939,38743,38744],{},"JVM"," (Java Virtual Machine) - When we start writing Java applications we compile human-readable code into bytecode. That bytecode runs on the virtual machine and the JVM is what allows us to write our code once and run it anywhere. ",[5332,38747,38748,38751],{},[2939,38749,38750],{},"SDK","( Software Development Kit) - This is a collection of libraries that we can use to build out applications. Java itself comes with an SDK for building out Java applications. ",[5332,38753,38754,38756],{},[2939,38755,31937],{}," (Java ARchive) - A JAR is nothing more than a zip file that contains 1 or more classes with some meta information about them. This is how we package up our applications. ",[4542,38758,38760],{"id":38759},"getting-started-with-java-course","Getting Started with Java Course",[651,38762,38763],{},"Now that I have given you a little information about getting started with Java, I would like to give you some details about my course. I set out to record some content that would help beginners get Java installed on their machine and set up a local development environment. What I ended up with was so much more. We also walk through things like advanced tooling, build tools and other languages that run on the JVM.",[651,38765,38766],{},"I didn't set out to make you an expert in all of these but to get you familiar with them. This way when they come up in a conversation you will know what your friends or coworkers are talking about. I have been working on the JVM for most of 18-year career and I have learned more than a few tips and tricks along the way. This course is fairly short at 3-4 hours in total length and this is what we are going to cover. ",[651,38768,38769],{},[660,38770],{"alt":38691,"src":25766},[5909,38772,38774],{"id":38773},"getting-started-with-java-course-curriculum","Getting Started with Java Course Curriculum ",[5316,38776,38777,38791,38816,38835,38857,38880,38909,38928,38947,38957],{},[5332,38778,38779,38780],{},"Introduction\n",[5316,38781,38782,38785,38788],{},[5332,38783,38784],{},"Goals of the course",[5332,38786,38787],{},"Meet your instructor",[5332,38789,38790],{},"Why you should learn Java (article)",[5332,38792,38793,38794],{},"Getting started with Java\n",[5316,38795,38796,38799,38802,38805,38807,38810,38813],{},[5332,38797,38798],{},"Java Terminology",[5332,38800,38801],{},"Quiz: Java Terms",[5332,38803,38804],{},"Java version check",[5332,38806,17720],{},[5332,38808,38809],{},"Installing Java",[5332,38811,38812],{},"Exercise: Java version check",[5332,38814,38815],{},"Exercise Review",[5332,38817,38818,38819],{},"Writing your first Java program\n",[5316,38820,38821,38824,38827,38830,38833],{},[5332,38822,38823],{},"Writing your first Java program",[5332,38825,38826],{},"Java program execution",[5332,38828,38829],{},"Hello, World!",[5332,38831,38832],{},"Exercise: Writing your 1st program",[5332,38834,38815],{},[5332,38836,38837,38838],{},"Text Editors & IDEs\n",[5316,38839,38840,38843,38846,38849,38852,38855],{},[5332,38841,38842],{},"Text Editors & IDEs overview",[5332,38844,38845],{},"Text Editor & IDE options (article) ",[5332,38847,38848],{},"Installing IntelliJ ",[5332,38850,38851],{},"Hello, World. IntelliJ Edition",[5332,38853,38854],{},"Exercise: Writing your first application in IntelliJ",[5332,38856,38815],{},[5332,38858,38859,38860],{},"IntelliJ \n",[5316,38861,38862,38865,38868,38871,38874,38877],{},[5332,38863,38864],{},"IntelliJ tour",[5332,38866,38867],{},"Themes",[5332,38869,38870],{},"Plugins",[5332,38872,38873],{},"Code formatting & organizing imports",[5332,38875,38876],{},"Templates",[5332,38878,38879],{},"Quiz",[5332,38881,38882,38883],{},"Advanced Tooling\n",[5316,38884,38885,38888,38891,38894,38897,38900,38903,38906],{},[5332,38886,38887],{},"Advanced tooling overview",[5332,38889,38890],{},"Code generation",[5332,38892,38893],{},"Debugging ",[5332,38895,38896],{},"Running IntelliJ from the command line",[5332,38898,38899],{},"Refactoring",[5332,38901,38902],{},"Github Source Control",[5332,38904,38905],{},"Java Decompiler",[5332,38907,38908],{},"Shortcut Keys",[5332,38910,38911,38912],{},"Java Build Tools\n",[5316,38913,38914,38917,38920,38923,38925],{},[5332,38915,38916],{},"Build Tools Introduction",[5332,38918,38919],{},"Maven",[5332,38921,38922],{},"Maven Dependencies ",[5332,38924,18326],{},[5332,38926,38927],{},"Quiz: Why Build Tools? ",[5332,38929,38930,38931],{},"JVM Languages\n",[5316,38932,38933,38936,38939,38941,38944],{},[5332,38934,38935],{},"Languages Intro",[5332,38937,38938],{},"JVM Languages",[5332,38940,18628],{},[5332,38942,38943],{},"Kotlin",[5332,38945,38946],{},"Scala",[5332,38948,38949,38950],{},"Getting Help\n",[5316,38951,38952,38955],{},[5332,38953,38954],{},"Java Documentation",[5332,38956,21931],{},[5332,38958,38959,38960],{},"Bonus\n",[5316,38961,38962,38965,38968],{},[5332,38963,38964],{},"My Contact Information",[5332,38966,38967],{},"Slide Deck",[5332,38969,25872],{},[4542,38971,9042],{"id":9041},[651,38973,38974,38975,38977],{},"I had a lot of fun putting this course together and I am pretty excited to share it with all of you. I hope you enjoy this course and if you have any questions about it please leave them below. _",[2939,38976,15394],{},": What was the toughest part of learning Java for you? _",[651,38979,38980],{},[812,38981,38983],{"href":38684,"rel":38982},[816],"Enroll in my Getting Started with Java Course",{"title":674,"searchDepth":790,"depth":790,"links":38985},[38986,38989,38992],{"id":38690,"depth":790,"text":38691,"children":38987},[38988],{"id":38711,"depth":892,"text":38712},{"id":38759,"depth":790,"text":38760,"children":38990},[38991],{"id":38773,"depth":892,"text":38774},{"id":9041,"depth":790,"text":9042},{"slug":38690,"published":797,"date":38994,"tags":38995,"cover":38996},"2017-12-08T09:00:23-05:00",[4109],"./apple-computer-desk-18105.jpg",{"title":576,"description":576},"blog/2017/12/08/getting-started-with-java","JqCeSR57usMfACbovPFc5drBt0Czzn3Yx-hN5OP3aco",{"id":39001,"title":573,"body":39002,"description":573,"extension":793,"meta":39135,"navigation":797,"path":574,"seo":39141,"stem":39142,"__hash__":39143},"content/blog/2017/12/29/my-2017-year-in-review.md",{"type":648,"value":39003,"toc":39122},[39004,39007,39011,39014,39018,39021,39027,39030,39033,39037,39040,39043,39047,39050,39053,39057,39060,39063,39067,39070,39073,39077,39080,39094,39097,39110,39114,39116],[651,39005,39006],{},"It seems like we say this every year, but can you believe the year is over already? 2017 is going to go down as one of the more memorable years of my life. I thought I would take this time to give you a little \"year in review\" before I share my 2018 goals. ",[4542,39008,39010],{"id":39009},"_2017-year-in-review-highlights","2017 Year in Review Highlights",[651,39012,39013],{},"As I said before, this has been one of the craziest (in a good way) years of my life. I try and remind myself through the ups and downs just how lucky I am. As I sat down to write this post, it really hit me just how true that is. ",[5909,39015,39017],{"id":39016},"being-a-new-homeowner","Being a new homeowner",[651,39019,39020],{},"This was the first year in our new house and as many of you know, being a first time homeowner has its ups and downs. ",[651,39022,39023],{},[660,39024],{"alt":39025,"src":39026},"My first time being a homeowner","./IMG_3572-1024x768.jpg",[651,39028,39029],{},"We worked on many projects this year, but none bigger than the kitchen remodel. My brother and I woke up on New Years Day (not feeling so great) and started ripping out the kitchen. We stripped the kitchen down to absolutely nothing and built it from scratch. I have to start by thanking my brother Joe because without him we would not have gotten this done. In fact, my whole family pitched in, so I would also like to thank them. Thanks to my wife for her vision of what turned out to be an amazing kitchen! ",[651,39031,39032],{},"homeowner gallery",[4542,39034,39036],{"id":39035},"bachelor-bachelorette-party","Bachelor / Bachelorette Party ",[651,39038,39039],{},"We had a joint bachelor/bachelorette party in New Orleans this year and it was so much fun. We took a huge group of friends down to the Big Easy and had a weekend full of fun, laughter, and great memories. As a group, we spent a lot of time walking the city and enjoying the food and drinks. The food, the architecture and the music are what set this city apart from others. ",[651,39041,39042],{},"the-big-easy gallery",[4542,39044,39046],{"id":39045},"wedding-day","Wedding Day",[651,39048,39049],{},"The main reason this year is going to go down as one of the best for me is I got married! I got to marry my best friend in the whole world and our wedding day was one for the books. My wife paid attention to every single detail and our big day was one to remember. We had perfect weather and all of our friends and family were there to share in our big day. ",[651,39051,39052],{},"wedding-day gallery",[4542,39054,39056],{"id":39055},"honeymoon","Honeymoon",[651,39058,39059],{},"After the wedding, it was off to Jamaica for our honeymoon. We spent almost 2 weeks in one of the most magical places I have ever been to. It was just so great to be able to relax and not worry about anything and just enjoy some alone time with my wife. ",[651,39061,39062],{},"honeymoon gallery",[4542,39064,39066],{"id":39065},"were-expecting","We're expecting! ",[651,39068,39069],{},"As if this year wasn't already the greatest ever, we were so excited to find out that we are expecting our first baby in May 2108! ",[651,39071,39072],{},"baby gallery",[4542,39074,39076],{"id":39075},"the-real-dan-vega","The Real Dan Vega",[651,39078,39079],{},"I had a really good year growing my personal brand. There are going to be changes coming in the new year but for now, I want to focus on what we did this year.",[5316,39081,39082,39085,39088,39091],{},[5332,39083,39084],{},"Went from 10,000 page views a month to 60,000.",[5332,39086,39087],{},"Went from 100 email subscribers to 1300.",[5332,39089,39090],{},"Crossed 35,000 Students worldwide. ",[5332,39092,39093],{},"Complete website redesign & makeover",[5909,39095,38461],{"id":39096},"angular-4-java-developers",[651,39098,39099,39100,39105,39106,664],{},"With the help of my friend ",[812,39101,39104],{"href":39102,"rel":39103},"http://springframeworkguru.com",[816],"John Thompson,"," we launched a new course called \"Angular 4 Java Developers\". We put some real thought and hard work into this course and it shows. I think this is the best course I have created and it's already helping so many people. If you want to find out more about this course visit ",[812,39107,39108],{"href":39108,"rel":39109},"https://courses.therealdanvega.com/p/jhipster",[816],[651,39111,39112,21854],{},[660,39113],{"alt":38461,"src":38481},[4542,39115,9042],{"id":9041},[651,39117,39118,39119,39121],{},"Well, there it is, my 2017 year in review. I didn't post any goals for this year yet, so I have nothing to hold myself accountable to. That is going to change and in a few days, I am going to let you know what my personal and professional goals are for the year. I think it's so important to sit down and actually put your goals for the upcoming year on paper to make a solid plan for yourself and hold yourself accountable, so stay tuned for mine, and I encourage you to do this as well. Thanks for reading... until next time! _",[2939,39120,11650],{}," What big things happened in 2017 for you? What goals do you have for 2018? _",{"title":674,"searchDepth":790,"depth":790,"links":39123},[39124,39127,39128,39129,39130,39131,39134],{"id":39009,"depth":790,"text":39010,"children":39125},[39126],{"id":39016,"depth":892,"text":39017},{"id":39035,"depth":790,"text":39036},{"id":39045,"depth":790,"text":39046},{"id":39055,"depth":790,"text":39056},{"id":39065,"depth":790,"text":39066},{"id":39075,"depth":790,"text":39076,"children":39132},[39133],{"id":39096,"depth":892,"text":38461},{"id":9041,"depth":790,"text":9042},{"slug":39136,"published":797,"date":39137,"tags":39138,"cover":39140},"my-2017-year-in-review","2017-12-29T09:14:08-05:00",[39139],"goals","./pexels-photo-282919-760x571.jpeg",{"title":573,"description":573},"blog/2017/12/29/my-2017-year-in-review","zO8sVVn93YXleMMKuRC4Df0w6p7vX3mR2nATEWdsbmk",{"id":39145,"title":570,"body":39146,"description":570,"extension":793,"meta":39565,"navigation":797,"path":571,"seo":39569,"stem":39570,"__hash__":39571},"content/blog/2018/01/01/my-2018-goals.md",{"type":648,"value":39147,"toc":39555},[39148,39157,39160,39164,39167,39171,39174,39184,39187,39224,39226,39229,39239,39243,39246,39256,39260,39263,39272,39276,39279,39286,39290,39293,39303,39306,39311,39331,39338,39341,39344,39354,39358,39361,39371,39375,39378,39382,39385,39395,39399,39408,39418,39437,39440,39443,39453,39462,39466,39486,39490,39499,39513,39523,39527,39536,39544,39547,39549],[651,39149,39150,39151,39156],{},"Another year has come and gone and after ",[812,39152,39155],{"href":39153,"rel":39154},"https://therealdanvega.com/blog/2017/12/29/my-2017-year-in-review",[816],"spending some time reflecting on 2017",", it's time to start looking forward to 2018.",[651,39158,39159],{},"Just so we are clear on where this is going, this isn't a \"new year, new me\" post. If you want to accomplish goals, you have to define them first. I sat down this week and wrote down some things I wanted to accomplish in 2018.",[4542,39161,39163],{"id":39162},"my-2018-goals","My 2018 Goals",[651,39165,39166],{},"These goals are not in any specific order but I did try to group them into buckets.",[5909,39168,39170],{"id":39169},"personal","Personal",[651,39172,39173],{},"Personally, 2018 is all about focusing on my health. With a baby on the way and the big 40 arriving this year, I need to make this more of a priority. Exercise has never been an issue for me, but I need to make clean eating a must and not just something I focus on when I want to lose some weight. I have already given up diet soda (which I was literally addicted to) for the past 2 months of the year.",[651,39175,39176],{},[812,39177,39180],{"href":39178,"rel":39179},"https://therealdanvega.com/wp-content/uploads/2017/12/pexels-photo-315737.jpeg",[816],[660,39181],{"alt":39182,"src":39183},"2018 Goals - Eating clean","./pexels-photo-315737-1024x663.jpeg",[651,39185,39186],{},"I have also been reading a lot more about the importance of a morning routine. A lot of successful people I look up to swear by it and I think this is going to help me with so many things. I am usually up around 6 AM and don't start work (I work from home) until 8:00 AM. This is going to be a good time to meditate, move for 5 minutes ( squats, pushups, situps, burpees) and spend 30 -60 min reading or listening to podcasts.",[5316,39188,39189,39192,39195,39198,39215,39218,39221],{},[5332,39190,39191],{},"Exercise 3-5x per week",[5332,39193,39194],{},"Run 20 miles per week (When the weather breaks here in Ohio)",[5332,39196,39197],{},"No more soda",[5332,39199,39200,39201],{},"Morning routine\n",[5316,39202,39203,39206,39209,39212],{},[5332,39204,39205],{},"Meditate 5 - 10 minutes",[5332,39207,39208],{},"Move for 5 minutes",[5332,39210,39211],{},"Journal (Productivity and Focus) 5 min",[5332,39213,39214],{},"Read for 30 min",[5332,39216,39217],{},"Complete my home office",[5332,39219,39220],{},"Complete the nursery (Baby on the way)",[5332,39222,39223],{},"Spend more time with my family",[5909,39225,18062],{"id":18061},[651,39227,39228],{},"I am not sure if you know this about me, but I am a serial learner. There are some things that I want to make sure I focus on learning this year.",[651,39230,39231],{},[812,39232,39235],{"href":39233,"rel":39234},"https://therealdanvega.com/wp-content/uploads/2017/12/open-book-library-education-read-159621.jpeg",[816],[660,39236],{"alt":39237,"src":39238},"Education in 2018","./open-book-library-education-read-159621-1024x769.jpeg",[5259,39240,39242],{"id":39241},"build-an-ios-app","Build an iOS app",[651,39244,39245],{},"I really want to create an iOS application and launch it in the app store this year. Money is not my primary focus here, and I get it.. the app market is a really hard one to get into. It is just one of those things that I have always wanted to do (along with writing a book) so I am making it a priority this year. I don't know what my app is going to do, but I will start brainstorming this soon.",[651,39247,39248],{},[812,39249,39252],{"href":39250,"rel":39251},"https://therealdanvega.com/wp-content/uploads/2017/12/twitter-facebook-together-exchange-of-information-147413.jpeg",[816],[660,39253],{"alt":39254,"src":39255},"2018 Goals - Build an iOS app","./twitter-facebook-together-exchange-of-information-147413-1024x682.jpeg",[5259,39257,39259],{"id":39258},"cryptocurrency","Cryptocurrency ",[651,39261,39262],{},"I have heard the cryptocurrency rumblings for a couple of years now, and like most people, I just ignored it. It seems like 2017 was a big year for Cryptocurrency and it doesn't seem to be going anywhere. Between some books and courses, I would like to inform myself more about a few things. The blockchain, which is the technology behind the emergence of cryptocurrency, is very interesting. This technology could very well shape the internet as we know it going forward and I don't want to be left behind. I also have a little bit of money invested in some coins, but this is something I would like to expand in the new year as well.",[651,39264,39265],{},[812,39266,39269],{"href":39267,"rel":39268},"https://therealdanvega.com/wp-content/uploads/2017/12/pexels-photo-730569.jpeg",[816],[660,39270],{"alt":674,"src":39271},"./pexels-photo-730569-1024x768.jpeg",[5259,39273,39275],{"id":39274},"alexa-skills","Alexa Skills",[651,39277,39278],{},"This Christmas I got an Amazon echo and I am absolutely loving it. I'm intrigued by the idea of voice-activated applications, and with Echo's popularity, I believe this is a great platform to be on. The programmer in me wants to dive right in and start teaching Alexa some new skills. I will admit I know nothing about writing applications for the echo, so I will be starting from scratch here.",[651,39280,39281],{},[812,39282,39284],{"href":39283},"./61yI7vWa83L._SX522_.jpg",[660,39285],{"alt":674,"src":39283},[5259,39287,39289],{"id":39288},"marketing","Marketing",[651,39291,39292],{},"I have never been interested in marketing. I have always taken the stance that I will produce content and let someone else worry about the marketing. As an entrepreneur, and someone who puts out a ton of content, it really hit me this year that I was going about it the wrong way.",[651,39294,39295],{},[812,39296,39299],{"href":39297,"rel":39298},"https://therealdanvega.com/wp-content/uploads/2017/12/pexels-photo-266176.jpeg",[816],[660,39300],{"alt":39301,"src":39302},"2018 Goals - Marekting","./pexels-photo-266176-1024x838.jpeg",[651,39304,39305],{},"I produce content for 2 very simple reasons. First, I really enjoy helping people. I remember being a new software developer and learning from people just like the present day me. I also enjoy the extra income some of my work produces, but this should never be the motivating factor. If you can provide a product or service that truly transforms someone's life, then that is a good place to be. With that said, I want to find more ways to get my content out there, and I am not focused enough on this side of my business. I am working on a plan as we speak of books, courses, mentors and even hiring an agency to handle this to make this a bigger priority in 2018.",[651,39307,39308],{},[2939,39309,39310],{},"Marketing Areas of focus",[5316,39312,39313,39316,39319,39322,39325,39328],{},[5332,39314,39315],{},"Facebook Ads",[5332,39317,39318],{},"Google Ads",[5332,39320,39321],{},"Google Analytics",[5332,39323,39324],{},"Sales Funnels",[5332,39326,39327],{},"Chat Bots",[5332,39329,39330],{},"Digital Marketing in general",[651,39332,39333],{},[7300,39334,39335,39337],{},[2939,39336,11650],{}," Do you have any recommendations for books, courses or agencies to improve in any of topics mentioned above?",[5259,39339,6016],{"id":39340},"books",[651,39342,39343],{},"I mentioned this in my morning routine, but I want to become a more consistent reader. This isn't going to happen if I don't actually set aside time for it. I know that I am not going to be able to read 30 min every single day, but if I build this into my routine, I should be able to stick to a goal. My goal for is 2018 is to read about 30 books. This is going to be mostly software development and business related, but I am going to try and sneak in a few for fun. I am going to keep a list so we can come back and look at this later. If you're interested in me writing a quick review of each book I read this year please let me know in the comments below.",[651,39345,39346],{},[812,39347,39350],{"href":39348,"rel":39349},"https://therealdanvega.com/wp-content/uploads/2017/12/books-bookstore-book-reading-159711.jpeg",[816],[660,39351],{"alt":39352,"src":39353},"Read more books","./books-bookstore-book-reading-159711-1024x681.jpeg",[5259,39355,39357],{"id":39356},"augmented-reality","Augmented Reality",[651,39359,39360],{},"This is another technology that I keep hearing is going to take over the world. I don't know that I will get into any actual development in the space this year, but at the very least I want to get more informed on where it's current state is and where it's going.",[651,39362,39363],{},[812,39364,39367],{"href":39365,"rel":39366},"https://therealdanvega.com/wp-content/uploads/2017/12/pexels-photo-123335.jpeg",[816],[660,39368],{"alt":39369,"src":39370},"2108 Goals - Augmented Reality","./pexels-photo-123335-1024x682.jpeg",[5909,39372,39374],{"id":39373},"professionally","Professionally",[651,39376,39377],{},"If you aren't aware, I have been with the same company for almost 6 years now and everything there is going great. I do have some goals there but they aren't really things I want to share here so, for now, I will just leave it at that.",[5259,39379,39381],{"id":39380},"course-development","Course Development",[651,39383,39384],{},"I know this might not seem like a huge goal, but I want to release 4 new courses this year. I already know what two of them are going to be (coming soon) and have some ideas for the other two. If you have some ideas for courses that you would like me to pursue, please leave me a comment below.",[651,39386,39387],{},[812,39388,39391],{"href":39389,"rel":39390},"https://therealdanvega.com/wp-content/uploads/2017/12/pexels-photo-450035.jpeg",[816],[660,39392],{"alt":39393,"src":39394},"2018 Goals - 4 new courses","./pexels-photo-450035-1024x682.jpeg",[5259,39396,39398],{"id":39397},"video-live-video-video-production","Video, Live Video & Video Production",[651,39400,39401,39402,39407],{},"When it comes to content on the web, video is king, and I have been preaching this for a couple years now. It's time I take my own advice and focus more on video. In 2017, I really focused on blogging, and while I am going to continue to blog, I am also going to start my content story with video. This means more live videos on ",[812,39403,39406],{"href":39404,"rel":39405},"http://www.facebook.com/therealdanvega",[816],"my Facebook page",", more YouTube videos and just being more comfortable in front of the camera.",[651,39409,39410],{},[812,39411,39414],{"href":39412,"rel":39413},"https://therealdanvega.com/wp-content/uploads/2017/12/pexels-photo-257904.jpeg",[816],[660,39415],{"alt":39416,"src":39417},"2018 Goals - Video Production","./pexels-photo-257904-1024x682.jpeg",[651,39419,39420,39421,39426,39427,39432,39433],{},"With a shift in focus to video, I need to get more creative with video editing. I know the basics of editing and know my way around Final Cut Pro and Adobe Premiere Pro. I know the basics, but would really like to improve the quality of my course promo videos, YouTube tutorials and some videos I will be working on for Facebook ads in the near future. ",[812,39422,39425],{"href":39423,"rel":39424},"https://videoschoolonline.com/",[816],"Phil Ebiner"," is a friend and someone I look up to when it comes to all things video. That is why I am taking his course \"",[812,39428,39431],{"href":39429,"rel":39430},"https://www.udemy.com/adobe-premiere-pro-video-editing",[816],"Adobe Premiere Pro CC for beginners","\". This is a promo he created for a new course of his and I absolutely fell in love with it. This is a great example of how to create a perfect course promo video. ",[812,39434,39435],{"href":39435,"rel":39436},"https://www.youtube.com/watch?v=Tu6vV3huI6E",[816],[5909,39438,26378],{"id":39439},"podcasts",[651,39441,39442],{},"There is a big question mark next to this one so I am not sure what to do here. I love podcasts so much that it is probably to a fault. I listen to them in the morning, in the car, on a run and whenever I can find some extra time. The reason I say that this is probably to a fault is that I listen too much and don't take enough action. I have a voice and would love to share my thoughts on a variety of topics. I have been a software developer & serial entrepreneur for half of my life. These are things I would love to talk about, but I am not even sure where to start.",[651,39444,39445],{},[812,39446,39449],{"href":39447,"rel":39448},"https://therealdanvega.com/wp-content/uploads/2017/12/microphone-audio-computer-sound-recording-55800-1.jpeg",[816],[660,39450],{"alt":39451,"src":39452},"2018 Goals - Start a podcast","./microphone-audio-computer-sound-recording-55800-1-1024x680.jpeg",[651,39454,39455,39456,39461],{},"I do know that If I come up with a good plan for a podcast that I would probably learn from the best before I dive right in. Pat Flynn has a course called ",[812,39457,39460],{"href":39458,"rel":39459},"https://courses.smartpassiveincome.com/p/power-up-podcasting",[816],"Power-Up Podcasting"," that I have had my eye on for a while now.",[5259,39463,39465],{"id":39464},"new-business-ventures","New Business Ventures",[651,39467,39468,39469,13517,39472,39479,39480,39485],{},"Towards the end of this year, I turned my side hustle into a legitimate business. The primary focus was to separate my personal and business finances, but I do have some ideas that need to get put into action in the new year. For now, I am just going to share the name and logo with you and fill you in on this in the coming months. ",[2939,39470,39471],{},"Code Monkey, LLC",[812,39473,39476],{"href":39474,"rel":39475},"https://therealdanvega.com/wp-content/uploads/2017/12/badge-logo.png",[816],[660,39477],{"alt":39471,"src":39478},"./badge-logo-1024x1024.png"," Please ",[812,39481,39484],{"href":39482,"rel":39483},"https://www.facebook.com/codemonkeyu/",[816],"follow Code Monkey on Facebook"," for updates. I also have a very good idea for a physical space right here in Northeast Ohio that could be a game changer. I can guarantee someone is going to do this within the next 2 years and the only question is, will it be me?",[5259,39487,39489],{"id":39488},"non-profit","Non-Profit",[651,39491,39492,39493,39498],{},"I am the President of a non-profit called CLE Cares. For the past 10 years, we have put on an event called ",[812,39494,39497],{"href":39495,"rel":39496},"http://www.toysforshots.com",[816],"Toys for Shots",". Up until this point, we have strictly been focused on this one single event and this year that stops. We are much more than a single event and in 2018, I have a few goals that I would like us to accomplish.",[5316,39500,39501,39504,39507,39510],{},[5332,39502,39503],{},"CLE Cares Branding & Website",[5332,39505,39506],{},"Summer Event",[5332,39508,39509],{},"Sponsors for Toys for Shots",[5332,39511,39512],{},"Increase attendance for Toys for Shots 2018",[651,39514,39515],{},[812,39516,39519],{"href":39517,"rel":39518},"https://therealdanvega.com/wp-content/uploads/2017/12/santa_sign.png",[816],[660,39520],{"alt":39521,"src":39522},"CLE Cares & Toys for Shots","./santa_sign-1024x1024.png",[4542,39524,39526],{"id":39525},"productivity-and-focus","Productivity and Focus",[651,39528,39529,39530,39535],{},"This seems like a lot, and as I am writing this, I am already a little overwhelmed. We all have the same 24 hours in a day, so how is that some people change the world and others don't? I believe a lot of this has to do with productivity and focus. So how am I going to accomplish these goals? The first thing I did was pick up ",[812,39531,39534],{"href":39532,"rel":39533},"https://fullfocusplanner.com/",[816],"Michael Hyatt's Full Focus Planner",". I am a huge fan of Michael's, and he certainly knows his stuff when it comes to productivity and focus. This is going to become part of my morning journaling and will help me work towards daily, weekly and monthly goals.",[651,39537,39538],{},[812,39539,39541],{"href":39540},"./about-fullfocusplanner-cover.png",[660,39542],{"alt":39543,"src":39540},"2018 Goals - Full Focus Planner",[651,39545,39546],{},"On the 1st of every month, I go through a monthly checklist of items that need my attention like updating growth reports, earnings and expenses. I am also going to add a \"Goals check-in\" task to this day. When the 1st rolls around, I am going to read this post and ask myself \"what have I done to work towards any of these goals?\" and check off the ones I have completed.",[4542,39548,9042],{"id":9041},[651,39550,39551,39552,39554],{},"Well, there you have it, my goals for 2018. I have always been taught that if you aren't setting goals that are a little scary, you're doing it wrong. I need to challenge myself and I think this list does exactly that. We will revisit this post at the end of 2018 and see where I ended up. _",[2939,39553,11650],{}," What are your goals for 2108? What are some new things that you want to learn? _",{"title":674,"searchDepth":790,"depth":790,"links":39556},[39557,39563,39564],{"id":39162,"depth":790,"text":39163,"children":39558},[39559,39560,39561,39562],{"id":39169,"depth":892,"text":39170},{"id":18061,"depth":892,"text":18062},{"id":39373,"depth":892,"text":39374},{"id":39439,"depth":892,"text":26378},{"id":39525,"depth":790,"text":39526},{"id":9041,"depth":790,"text":9042},{"slug":39162,"published":797,"date":39566,"tags":39567,"cover":39568},"2018-01-01T08:50:40-05:00",[39139],"./2018-cover.png",{"title":570,"description":570},"blog/2018/01/01/my-2018-goals","1GWcM6hJ4X3O8HYwaLqPj9YvfqKIn31_92TDfzz6by8",{"id":39573,"title":567,"body":39574,"description":567,"extension":793,"meta":40984,"navigation":797,"path":568,"seo":40989,"stem":40990,"__hash__":40991},"content/blog/2018/03/01/what-is-new-spring-boot-2.md",{"type":648,"value":39575,"toc":40969},[39576,39579,39592,39596,39604,39609,39612,39728,39732,39735,39739,39748,39751,39754,39758,39761,39765,39773,39777,39780,39787,39791,39803,39806,39814,39817,39826,39830,39833,39853,39856,39860,39863,39866,39869,39903,39912,39936,39939,39942,39945,39954,39959,39962,40030,40034,40037,40041,40044,40058,40067,40071,40081,40090,40093,40268,40295,40300,40303,40311,40315,40318,40321,40337,40341,40344,40351,40365,40368,40371,40380,40478,40492,40494,40497,40519,40524,40528,40531,40543,40546,40549,40563,40566,40574,40576,40579,40777,40780,40789,40805,40828,40831,40840,40842,40846,40849,40890,40892,40895,40904,40916,40918,40921,40925,40933,40942,40946,40958,40960,40966],[651,39577,39578],{},"Spring Boot 2 was just released and I am so pumped to see it in action in the coming months. This release is the culmination of 17 months of work and over 6800 commits by 215 different individuals. There are some really great features to talk about so let's talk about What's new in Spring Boot 2. In this article we are going to look at:",[5316,39580,39581,39584,39587,39590],{},[5332,39582,39583],{},"The history of Spring Boot",[5332,39585,39586],{},"What's new in Spring Boot",[5332,39588,39589],{},"Spring Boot 2 Migration Guide",[5332,39591,21931],{},[4542,39593,39595],{"id":39594},"spring-boot-history","Spring Boot History",[651,39597,39598,39599,664],{},"Before we dive into what’s new I want to take this chance to take a quick look at the history of Spring Boot. In a blog post published in August 2013, ",[812,39600,39603],{"href":39601,"rel":39602},"https://spring.io/blog/2013/08/06/spring-boot-simplifying-spring-for-everyone/",[816],"Phil Webb announced the first milestone release of a new project called Spring Boot",[1004,39605,39606],{},[651,39607,39608],{},"Spring Boot aims to make it easy to create Spring-powered, production-grade applications and services with minimum fuss. It takes an opinionated view of the Spring platform so that new and existing users can quickly get to the bits they need. You can use it to create stand-alone Java applications that can be started using ‘java -jar’ or more traditional WAR deployments. ",[651,39610,39611],{},"About 9 months later in April 2014 Spring Boot 1.0 was released. Since then there have been numerous minor releases that have brought us some really great features. ",[5316,39613,39614,39635,39656,39680,39704],{},[5332,39615,39616,39621],{},[812,39617,39620],{"href":39618,"rel":39619},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.1-Release-Notes",[816],"Spring Boot 1.1 (June 2014) ",[5316,39622,39623,39626,39629,39632],{},[5332,39624,39625],{},"spring-boot-starter-test",[5332,39627,39628],{},"Metrics & Health Endpoints",[5332,39630,39631],{},"Elastic Search, Apache Solr, Spring Social & Spring Integration Auto-configuration ",[5332,39633,39634],{},"Additional Templating support (Adding Freemaker, Groov, and Velocity)",[5332,39636,39637,39642],{},[812,39638,39641],{"href":39639,"rel":39640},"https://github.com/spring-projects/spring-boot/wiki/spring-boot-1.2-release-notes",[816],"Spring Boot 1.2 (March 2015) ",[5316,39643,39644,39647,39650,39653],{},[5332,39645,39646],{},"Servlet 3.1, Tomcat 8 & Jetty 9",[5332,39648,39649],{},"Spring 4.1",[5332,39651,39652],{},"@SpringBootApplication Annotation",[5332,39654,39655],{},"Email Support",[5332,39657,39658,39663],{},[812,39659,39662],{"href":39660,"rel":39661},"https://github.com/spring-projects/spring-boot/wiki/spring-boot-1.3-release-notes",[816],"Spring Boot 1.3 (December 2016)",[5316,39664,39665,39668,39671,39674,39677],{},[5332,39666,39667],{},"Upgrade to Spring Framework 4.2",[5332,39669,39670],{},"Upgrade to Spring Security 4.0",[5332,39672,39673],{},"Developer Tools",[5332,39675,39676],{},"Caching Auto-configuration ",[5332,39678,39679],{},"Fully executable JARs and service support",[5332,39681,39682,39687],{},[812,39683,39686],{"href":39684,"rel":39685},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.4-Release-Notes",[816],"Spring Boot 1.4 (January 2017)",[5316,39688,39689,39692,39695,39698,39701],{},[5332,39690,39691],{},"Spring 4.3",[5332,39693,39694],{},"Hibernate 5",[5332,39696,39697],{},"Testing Improvements",[5332,39699,39700],{},"Integration Starter",[5332,39702,39703],{},"Couchbase and Neo4J Support",[5332,39705,39706,39711],{},[812,39707,39710],{"href":39708,"rel":39709},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.5-Release-Notes",[816],"Spring Boot 1.5 (February 2017)",[5316,39712,39713,39716,39719,39722,39725],{},[5332,39714,39715],{},"Loggers endpoint",[5332,39717,39718],{},"Apache Kafka support",[5332,39720,39721],{},"Cloud Foundry actuator extensions",[5332,39723,39724],{},"LDAP support",[5332,39726,39727],{},"Testing updates",[4542,39729,39731],{"id":39730},"whats-new-in-spring","What's new in Spring ",[651,39733,39734],{},"What's new in Spring Boot 2? If we are going to talk about the biggest feature we need to start with the upgrade to Spring Framework 5. While Spring Framework 5 has been out since September 2017, most developers (like me) have been waiting for the release of Spring Boot 2. Spring Framework 5 introduces a long list of new features but I just want to touch on a few of the major ones here.",[5909,39736,39738],{"id":39737},"whats-new-in-spring-framework-5","What’s new in Spring Framework 5",[651,39740,39741],{},[812,39742,39745],{"href":39743,"rel":39744},"https://therealdanvega.com/wp-content/uploads/2018/02/mark-solarski-209233-unsplash.jpg",[816],[660,39746],{"alt":674,"src":39747},"./mark-solarski-209233-unsplash-1024x682.jpg",[5259,39749,39750],{"id":18229},"Java 8+ Baseline",[651,39752,39753],{},"If you wish to build Spring Framework applications going forward you must be on a minimum of Java 8. Your first thought might be this is a great change for all of us but in fact its an even bigger one for the Spring team. This gave them a chance to make some major changes to update the code base to all the new features in Java 8 like Lambdas and streams. This not only ads readability throughout the code but also offers some performance improvements to the core of the framework. ",[5259,39755,39757],{"id":39756},"java-9-support","Java 9 Support",[651,39759,39760],{},"If you want to use Java 9 you will need to update to Spring Framework 5 and therefore need to update to Spring Boot 2. I know many of us might not be on the latest and greatest version of Java in production but this is a great chance for you to play around with all the cool new toys. This works pretty seamlessly out of the box using the standard classpath but I have read a few people having issues moving to Java 9 modules. ",[5259,39762,39764],{"id":39763},"spring-mvc","Spring MVC",[651,39766,39767,39768,664],{},"While Spring MVC isn't the main character in this story there were some nice upgrades. I won't go through them all here so head over to ",[812,39769,39772],{"href":39770,"rel":39771},"https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-5.x#spring-web-mvc",[816],"documentation for Spring Framework 5",[5259,39774,39776],{"id":39775},"spring-webflux","Spring Webflux",[651,39778,39779],{},"The reactive stack is our main character in the story of Spring Framework 5. This is a different way of thinking but fortunately for us, we don't have to learn a whole new way of writing applications. Spring WebFlux is a fully asynchronous and non-blocking web framework built from the ground up allowing us to handle a massive number of concurrent connections. While this is a complete paradigm shift it is really easy to get started. ",[651,39781,39782],{},[812,39783,39785],{"href":39784},"./reactive_stack.png",[660,39786],{"alt":674,"src":39784},[5259,39788,39790],{"id":39789},"kotlin-support","Kotlin Support",[651,39792,39793,39794,39797,39798,664],{},"Kotlin support was added to ",[812,39795,30440],{"href":30440,"rel":39796},[816]," but in Spring Framework 5 there is dedicated support for the language. With the dedicated support came some nice features that you can ",[812,39799,39802],{"href":39800,"rel":39801},"https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-5.x#kotlin-support",[816],"read about here",[5259,39804,39697],{"id":39805},"testing-improvements",[651,39807,39808,39809,39813],{},"The biggest change in the testing landscape is the complete support for ",[812,39810,18276],{"href":39811,"rel":39812},"http://junit.org/junit5/",[816],"'s Jupiter programming and extension model. I will mention this again later but when you fire up a new Spring Boot 2 application you are still using JUnit 4 by default however it is a simple change if you want to start using JUnit 5. ",[5909,39815,567],{"id":39816},"whats-new-in-spring-boot-2",[651,39818,39819],{},[812,39820,39823],{"href":39821,"rel":39822},"https://therealdanvega.com/wp-content/uploads/2018/02/jon-tyson-520864-unsplash.jpg",[816],[660,39824],{"alt":674,"src":39825},"./jon-tyson-520864-unsplash-1024x768.jpg",[5259,39827,39829],{"id":39828},"third-party-library-upgrades","Third-party Library Upgrades",[651,39831,39832],{},"With any new release of Spring Boot, the Spring team has an opportunity to update various dependencies. ",[5316,39834,39835,39838,39841,39844,39847,39850],{},[5332,39836,39837],{},"Thymeleaf 3 * ",[5332,39839,39840],{},"Jetty 9.4",[5332,39842,39843],{},"Tomcat 8.5",[5332,39845,39846],{},"Hibernate 5.2",[5332,39848,39849],{},"Flyway 5",[5332,39851,39852],{},"Gradle 4",[651,39854,39855],{},"* The Thymeleaf starter now includes thymeleaf-extras-java8time out of the box.",[5259,39857,39859],{"id":39858},"reactive-spring-data-spring-security","Reactive Spring Data & Spring Security ",[651,39861,39862],{},"With the move to Spring WebFlux, Spring Data also provides support for reactive applications. Currently, Cassandra, MongoDB, Couchbase and Redis all have reactive API support. Spring Boot includes starter-POMs for all of these which should make getting started really easy. We also gain the ability to use Spring Security 5.0 in our reactive applications. When Spring Security is on the classpath auto-configuration is provided for WebFlux applications.",[5259,39864,39865],{"id":22678},"Actuator",[651,39867,39868],{},"Spring Boot Actuator is nothing new but it has been rewritten from the ground up. If you're not familiar with the Actuator project it automatically exposes endpoints to get information about that status of your application. The Actuator in Spring Boot 1.x was written against the servlet API and with the new reactive stack, the Spring team needed a solution that would work with both. Along with that, there were many changes to Actuator: ",[5316,39870,39871,39874,39877,39880,39886,39889],{},[5332,39872,39873],{},"Redesign for both servlet & reactive",[5332,39875,39876],{},"Status & health (all the details) were separated out",[5332,39878,39879],{},"Simplified Security model",[5332,39881,39882,39883],{},"Move to micrometer (think SLF4J ",[679,39884,39885],{"style":37504},"but for metrics)",[5332,39887,39888],{},"Improved JSON Structures",[5332,39890,39891,39892],{},"Simplified process for creating User-Defined Endpoints.\n",[5316,39893,39894,39897,39900],{},[5332,39895,39896],{},"@Endpoint",[5332,39898,39899],{},"@WebEndpoint",[5332,39901,39902],{},"@JmxEndpoint",[651,39904,39905],{},[812,39906,39909],{"href":39907,"rel":39908},"https://therealdanvega.com/wp-content/uploads/2018/02/pexels-photo-241544.jpeg",[816],[660,39910],{"alt":674,"src":39911},"./pexels-photo-241544-1024x683.jpeg",[651,39913,39914,39915,39918,39919,39922,39923,39925,39926,39929,39930,39935],{},"This might be an area that folks have some trouble upgrading because of the security model changes which we will talk about those in a few. By default, all web endpoints are available beneath the path ",[676,39916,39917],{},"/actuator"," with URLs of the form ",[676,39920,39921],{},"/actuator/{id}"," . The ",[676,39924,39917],{}," base path can be configured by using the ",[676,39927,39928],{},"management.endpoints.web.base-path"," property. There is a ",[812,39931,39934],{"href":39932,"rel":39933},"https://docs.spring.io/spring-boot/docs/2.0.x/actuator-api/html/",[816],"dedicated detailed document for Spring Boot Actuator Web API Endpoints"," and this is a great place to start. ",[5259,39937,39938],{"id":24331},"Gradle Plugin",[651,39940,39941],{},"I have always been a big fan of Gradle and I am really excited to see that the team took this opportunity to rewrite the Gradle plugin. ",[651,39943,39944],{},"he Spring Boot Gradle Plugin provides Spring Boot support in Gradle, allowing you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by spring-boot-dependencies. Spring Boot’s Gradle plugin requires Gradle 4.0 or later.",[651,39946,39947],{},[812,39948,39951],{"href":39949,"rel":39950},"https://therealdanvega.com/wp-content/uploads/2018/02/pexels-photo-209728.jpeg",[816],[660,39952],{"alt":674,"src":39953},"./pexels-photo-209728-1024x687.jpeg",[651,39955,39956],{},[2939,39957,39958],{},"Getting Started",[651,39960,39961],{},"To get started with the plugin it needs to be applied to your project.",[669,39963,39965],{"className":4107,"code":39964,"language":4109,"meta":674,"style":674},"buildscript {\n repositories {\n maven { url 'https://repo.spring.io/libs-milestone' }\n }\n\n dependencies {\n classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RC1'\n }\n}\n\napply plugin: 'org.springframework.boot'\n",[676,39966,39967,39972,39977,39988,39992,39996,40001,40009,40013,40017,40021],{"__ignoreMap":674},[679,39968,39969],{"class":681,"line":682},[679,39970,39971],{"class":693},"buildscript {\n",[679,39973,39974],{"class":681,"line":790},[679,39975,39976],{"class":693}," repositories {\n",[679,39978,39979,39982,39985],{"class":681,"line":892},[679,39980,39981],{"class":693}," maven { url ",[679,39983,39984],{"class":689},"'https://repo.spring.io/libs-milestone'",[679,39986,39987],{"class":693}," }\n",[679,39989,39990],{"class":681,"line":901},[679,39991,21405],{"class":693},[679,39993,39994],{"class":681,"line":909},[679,39995,889],{"emptyLinePlaceholder":797},[679,39997,39998],{"class":681,"line":918},[679,39999,40000],{"class":693}," dependencies {\n",[679,40002,40003,40006],{"class":681,"line":935},[679,40004,40005],{"class":693}," classpath ",[679,40007,40008],{"class":689},"'org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RC1'\n",[679,40010,40011],{"class":681,"line":944},[679,40012,21405],{"class":693},[679,40014,40015],{"class":681,"line":959},[679,40016,996],{"class":693},[679,40018,40019],{"class":681,"line":964},[679,40020,889],{"emptyLinePlaceholder":797},[679,40022,40023,40025,40027],{"class":681,"line":977},[679,40024,38047],{"class":693},[679,40026,2391],{"class":685},[679,40028,40029],{"class":689}," 'org.springframework.boot'\n",[651,40031,40032],{},[2939,40033,24364],{},[651,40035,40036],{},"The bootRepackage task has been replaced with bootJar and bootWar tasks for building executable jars and wars respectively. Both tasks extend their equivalent standard Gradle jar or war task, giving you access to all of the usual configuration options and behavior.",[651,40038,40039],{},[2939,40040,24385],{},[651,40042,40043],{},"Spring Boot’s Gradle plugin no longer automatically applies the dependency management plugin. Instead, Spring Boot’s plugin now reacts to the dependency management plugin being applied by importing the correct version of the spring-boot-dependencies BOM. This gives you more control over how and when dependency management is configured. For most applications applying the dependency management plugin will be sufficient:",[669,40045,40047],{"className":4107,"code":40046,"language":4109,"meta":674,"style":674},"apply plugin: 'io.spring.dependency-management'\n",[676,40048,40049],{"__ignoreMap":674},[679,40050,40051,40053,40055],{"class":681,"line":682},[679,40052,38047],{"class":693},[679,40054,2391],{"class":685},[679,40056,40057],{"class":689}," 'io.spring.dependency-management'\n",[651,40059,40060,40061,40066],{},"The ",[812,40062,40065],{"href":40063,"rel":40064},"https://docs.spring.io/spring-boot/docs/2.0.x/gradle-plugin/reference/html/",[816],"Gradle Plugin also has its own documentation"," and there is some really great information in there. Anyone that wants to get started using Gradle should start by checking out the link below. ",[5259,40068,40070],{"id":40069},"simplified-security","Simplified Security ",[651,40072,40073,40074,40077,40078,664],{},"In Spring Boot 2.x, one of the main goals was to simplify security configuration and make adding custom security easy. By default, everything is secured, including static resources and Actuator endpoints. If Spring Security is on the classpath, Spring Boot will add ",[2939,40075,40076],{},"@EnableWebSecurity"," and rely on Spring Security's content-negoation to decide which authentication mechanism to use. Once users decide that they want to add custom security, the default security configuration provided by Spring Boot will back off completely. At this point, users need to explicitly define all the bits they want to secure. This means security configuration is now in one place and avoids any kind of ordering issues with existing ",[2939,40079,40080],{},"WebSecurityConfigurerAdapter",[651,40082,40083],{},[812,40084,40087],{"href":40085,"rel":40086},"https://therealdanvega.com/wp-content/uploads/2018/02/pexels-photo-113726.jpeg",[816],[660,40088],{"alt":674,"src":40089},"./pexels-photo-113726-1024x683.jpeg",[651,40091,40092],{},"Here is an example of a custom security:",[669,40094,40096],{"className":4107,"code":40095,"language":4109,"meta":674,"style":674},"http\n .authorizeRequests()\n // 1\n .requestMatchers(EndpointRequest.to(\"status\", \"info\"))\n .permitAll()\n // 2\n .requestMatchers(EndpointRequest.toAnyEndpoint())\n .hasRole(\"ACTUATOR\")\n // 3 \n .requestMatchers(StaticResourceRequest.toCommonLocations())\n .permitAll()\n // 4\n .antMatchers(\"/\\*\\*\")\n .hasRole(\"USER\")\n .and()\n ... // additional configuration\n",[676,40097,40098,40103,40113,40118,40144,40154,40159,40173,40187,40192,40206,40214,40219,40238,40251,40260],{"__ignoreMap":674},[679,40099,40100],{"class":681,"line":682},[679,40101,40102],{"class":693},"http\n",[679,40104,40105,40108,40111],{"class":681,"line":790},[679,40106,40107],{"class":693}," .",[679,40109,40110],{"class":880},"authorizeRequests",[679,40112,17545],{"class":693},[679,40114,40115],{"class":681,"line":892},[679,40116,40117],{"class":1400}," // 1\n",[679,40119,40120,40122,40125,40128,40131,40133,40136,40138,40141],{"class":681,"line":901},[679,40121,21331],{"class":693},[679,40123,40124],{"class":880},"requestMatchers",[679,40126,40127],{"class":693},"(EndpointRequest.",[679,40129,40130],{"class":880},"to",[679,40132,745],{"class":693},[679,40134,40135],{"class":689},"\"status\"",[679,40137,2797],{"class":693},[679,40139,40140],{"class":689},"\"info\"",[679,40142,40143],{"class":693},"))\n",[679,40145,40146,40149,40152],{"class":681,"line":909},[679,40147,40148],{"class":693}," .",[679,40150,40151],{"class":880},"permitAll",[679,40153,17545],{"class":693},[679,40155,40156],{"class":681,"line":918},[679,40157,40158],{"class":1400}," // 2\n",[679,40160,40161,40163,40165,40167,40170],{"class":681,"line":935},[679,40162,21331],{"class":693},[679,40164,40124],{"class":880},[679,40166,40127],{"class":693},[679,40168,40169],{"class":880},"toAnyEndpoint",[679,40171,40172],{"class":693},"())\n",[679,40174,40175,40177,40180,40182,40185],{"class":681,"line":944},[679,40176,40148],{"class":693},[679,40178,40179],{"class":880},"hasRole",[679,40181,745],{"class":693},[679,40183,40184],{"class":689},"\"ACTUATOR\"",[679,40186,1339],{"class":693},[679,40188,40189],{"class":681,"line":959},[679,40190,40191],{"class":1400}," // 3 \n",[679,40193,40194,40196,40198,40201,40204],{"class":681,"line":964},[679,40195,21331],{"class":693},[679,40197,40124],{"class":880},[679,40199,40200],{"class":693},"(StaticResourceRequest.",[679,40202,40203],{"class":880},"toCommonLocations",[679,40205,40172],{"class":693},[679,40207,40208,40210,40212],{"class":681,"line":977},[679,40209,40148],{"class":693},[679,40211,40151],{"class":880},[679,40213,17545],{"class":693},[679,40215,40216],{"class":681,"line":982},[679,40217,40218],{"class":1400}," // 4\n",[679,40220,40221,40223,40226,40228,40231,40234,40236],{"class":681,"line":988},[679,40222,21331],{"class":693},[679,40224,40225],{"class":880},"antMatchers",[679,40227,745],{"class":693},[679,40229,40230],{"class":689},"\"/",[679,40232,40233],{"class":931},"\\*\\*",[679,40235,4857],{"class":689},[679,40237,1339],{"class":693},[679,40239,40240,40242,40244,40246,40249],{"class":681,"line":993},[679,40241,40148],{"class":693},[679,40243,40179],{"class":880},[679,40245,745],{"class":693},[679,40247,40248],{"class":689},"\"USER\"",[679,40250,1339],{"class":693},[679,40252,40253,40255,40258],{"class":681,"line":2129},[679,40254,40107],{"class":693},[679,40256,40257],{"class":880},"and",[679,40259,17545],{"class":693},[679,40261,40262,40265],{"class":681,"line":2140},[679,40263,40264],{"class":693}," ... ",[679,40266,40267],{"class":1400},"// additional configuration\n",[27665,40269,40270,40279,40286,40289],{},[5332,40271,40272,29436,40275,40278],{},[676,40273,40274],{},"/status",[676,40276,40277],{},"/info"," endpoints do not require authentication.",[5332,40280,40281,40282,40285],{},"All other actuator endpoints are protected by the ",[676,40283,40284],{},"ACTUATOR"," role.",[5332,40287,40288],{},"Common static resource locations are open to all.",[5332,40290,40291,40292,40285],{},"All other application endpoints are protected by the ",[676,40293,40294],{},"USER",[651,40296,40297,40299],{},[2939,40298,22961],{}," With the actuator endpoints locked down, you will need to include or exclude the endpoints you want to see. These are the properties that now control that and have changed since 1.x ",[651,40301,40302],{},"# ENDPOINTS WEB CONFIGURATION (WebEndpointProperties)\nmanagement.endpoints.web.exposure.include=info,health # Endpoint IDs that should be included or '*' for all.\nmanagement.endpoints.web.exposure.exclude= # Endpoint IDs that should be excluded.\nmanagement.endpoints.web.base-path=/actuator # Base path for Web endpoints. Relative to server.servlet.context-path or management.server.servlet.context-path if management.server.port is configured.\nmanagement.endpoints.web.path-mapping= # Mapping between endpoint IDs and the path that should expose them.",[651,40304,40305,40306,15266],{},"If you're ever unsure what properties to use bookmark the ",[812,40307,40310],{"href":40308,"rel":40309},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/common-application-properties.html",[816],"common application properties documentation",[5259,40312,40314],{"id":40313},"http2-support","HTTP/2 Support",[651,40316,40317],{},"It's hard to believe but the HTTP 1.1 specification was released back in 1996. I think this comes without saying but the web is very different today. If you want to enable HTTP/2 in your Spring MVC or WebFlux applications you can use the following property. ",[651,40319,40320],{},"server.http2.enabled=true",[651,40322,40323,40324,40329,40330],{},"This support depends on the chosen web server and the application environment since that protocol is not supported out-of-the-box by JDK8. Please ",[812,40325,40328],{"href":40326,"rel":40327},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/howto-embedded-web-servers.html#howto-configure-http2",[816],"check out the documentation"," for more details. ",[812,40331,40334],{"href":40332,"rel":40333},"https://therealdanvega.com/wp-content/uploads/2018/02/mac-freelancer-macintosh-macbook-40185.jpeg",[816],[660,40335],{"alt":674,"src":40336},"./mac-freelancer-macintosh-macbook-40185-1024x634.jpeg",[5259,40338,40340],{"id":40339},"configuration-properties","Configuration Properties",[651,40342,40343],{},"In Spring Boot 1.x this notion of relaxed binding was supported and all that meant is there were multiple ways you could create a property name (camel case, underscore, hyphen) and that property would all bind to the same property. ",[651,40345,40346,40347,40350],{},"This still works the same but what they did tighten up was the way that you read variables in your own code. The ",[676,40348,40349],{},"@Value"," annotation is a core container feature, and it does not provide the same features as type-safe configuration properties.",[651,40352,40353,13517,40361],{},[812,40354,40357],{"href":40355,"rel":40356},"https://therealdanvega.com/wp-content/uploads/2018/02/2018-02-28_07-12-58.png",[816],[660,40358],{"alt":40359,"src":40360},"What's new in Spring Boot 2 - Configuration Properties","./2018-02-28_07-12-58-1024x180.png",[812,40362,40363],{"href":40363,"rel":40364},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/boot-features-external-config.html#boot-features-external-config-relaxed-binding",[816],[5259,40366,40367],{"id":22828},"Metrics",[651,40369,40370],{},"Spring Boot’s own metrics have been replaced by Micrometer. This is being developed by Pivotal and quickly being adopted across projects within Pivotal. ",[651,40372,40373,40374,40379],{},"Spring Boot Actuator provides dependency management and auto-configuration for ",[812,40375,40378],{"href":40376,"rel":40377},"https://micrometer.io/",[816],"Micrometer",", an application metrics facade that supports numerous monitoring systems, including:",[5316,40381,40382,40390,40398,40406,40414,40422,40430,40438,40446,40454,40462,40470],{},[5332,40383,40384],{},[812,40385,40389],{"href":40386,"rel":40387,"title":40388},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-atlas",[816],"54.2.1 Atlas","Atlas",[5332,40391,40392],{},[812,40393,40397],{"href":40394,"rel":40395,"title":40396},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-datadog",[816],"54.2.2 Datadog","Datadog",[5332,40399,40400],{},[812,40401,40405],{"href":40402,"rel":40403,"title":40404},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-ganglia",[816],"54.2.3 Ganglia","Ganglia",[5332,40407,40408],{},[812,40409,40413],{"href":40410,"rel":40411,"title":40412},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-graphite",[816],"54.2.4 Graphite","Graphite",[5332,40415,40416],{},[812,40417,40421],{"href":40418,"rel":40419,"title":40420},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-influx",[816],"54.2.5 Influx","Influx",[5332,40423,40424],{},[812,40425,40429],{"href":40426,"rel":40427,"title":40428},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-jmx",[816],"54.2.6 JMX","JMX",[5332,40431,40432],{},[812,40433,40437],{"href":40434,"rel":40435,"title":40436},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-newrelic",[816],"54.2.7 New Relic","New Relic",[5332,40439,40440],{},[812,40441,40445],{"href":40442,"rel":40443,"title":40444},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-prometheus",[816],"54.2.8 Prometheus","Prometheus",[5332,40447,40448],{},[812,40449,40453],{"href":40450,"rel":40451,"title":40452},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-signalfx",[816],"54.2.9 SignalFx","SignalFx",[5332,40455,40456],{},[812,40457,40461],{"href":40458,"rel":40459,"title":40460},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-simple",[816],"54.2.10 Simple","Simple (in-memory)",[5332,40463,40464],{},[812,40465,40469],{"href":40466,"rel":40467,"title":40468},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-statsd",[816],"54.2.11 StatsD","StatsD",[5332,40471,40472],{},[812,40473,40477],{"href":40474,"rel":40475,"title":40476},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/production-ready-metrics.html#production-ready-metrics-export-wavefront",[816],"54.2.12 Wavefront","Wavefront",[651,40479,40480,40487,40488,40491],{},[812,40481,40484],{"href":40482,"rel":40483},"https://therealdanvega.com/wp-content/uploads/2018/02/2018-02-28_06-59-52.png",[816],[660,40485],{"alt":674,"src":40486},"./2018-02-28_06-59-52-1024x579.png"," To learn more about Micrometer visit ",[812,40489,40376],{"href":40376,"rel":40490},[816]," This might cause a little bit of an upgrade pain for those doing a ton of logging of custom metrics.",[5259,40493,30454],{"id":30453},[651,40495,40496],{},"Spring Boot 2 provides support for the Quartz scheduler that can be used via the spring-boot-starter-quartz dedicated starter. Both in-memory and JDBC stores can be configured.",[669,40498,40500],{"className":9101,"code":40499,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-quartz\u003C/artifactId>\n\u003C/dependency>\n",[676,40501,40502,40506,40510,40515],{"__ignoreMap":674},[679,40503,40504],{"class":681,"line":682},[679,40505,9110],{},[679,40507,40508],{"class":681,"line":790},[679,40509,9115],{},[679,40511,40512],{"class":681,"line":892},[679,40513,40514],{}," \u003CartifactId>spring-boot-starter-quartz\u003C/artifactId>\n",[679,40516,40517],{"class":681,"line":901},[679,40518,9125],{},[651,40520,40521],{},[660,40522],{"alt":674,"src":40523},"./pexels-photo-768472-1024x768.jpeg",[5259,40525,40527],{"id":40526},"hikaricp-connection-pool","HikariCP Connection Pool",[651,40529,40530],{},"The default connection pool has switched from Tomcat to HikariCP. If you used spring.datasource.type to force the use of Hikari in a Tomcat-based application, you can now remove that override. Similarly, if you want to stay with the Tomcat connection pool, simply add the following to your configuration:",[669,40532,40534],{"className":5851,"code":40533,"language":5853,"meta":674,"style":674},"spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource\n",[676,40535,40536],{"__ignoreMap":674},[679,40537,40538,40540],{"class":681,"line":682},[679,40539,23964],{"class":880},[679,40541,40542],{"class":689},"=org.apache.tomcat.jdbc.pool.DataSource\n",[5259,40544,39673],{"id":40545},"developer-tools",[651,40547,40548],{},"By default, each time your application restarts, a report showing the condition evaluation delta is logged. The report shows the changes to your application’s auto-configuration as you make changes such as adding or removing beans and setting configuration properties. To disable the logging of the report, set the following property:",[669,40550,40552],{"className":5851,"code":40551,"language":5853,"meta":674,"style":674},"spring.devtools.restart.log-condition-evaluation-delta=false\n",[676,40553,40554],{"__ignoreMap":674},[679,40555,40556,40559,40561],{"class":681,"line":682},[679,40557,40558],{"class":880},"spring.devtools.restart.log-condition-evaluation-delta",[679,40560,686],{"class":689},[679,40562,2649],{"class":931},[5259,40564,39790],{"id":40565},"kotlin-support-1",[651,40567,40568,40569,15266],{},"We talked earlier in this article about the official support for Kotlin. There is also a ",[812,40570,40573],{"href":40571,"rel":40572},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/boot-features-kotlin.html",[816],"dedicated portion of the documentation to Kotlin",[5259,40575,18276],{"id":18275},[651,40577,40578],{},"As I said earlier the default for a Spring Boot application is to still use JUnit 4. If you want to switch to JUnit 5 you will need to exclude JUnit 4 from the spring boot starter test and add the required dependencies. You will also need to add the plugin listed below. ",[669,40580,40582],{"className":9101,"code":40581,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-test\u003C/artifactId>\n \u003Cscope>test\u003C/scope>\n \u003Cexclusions>\n \u003Cexclusion>\n \u003CgroupId>junit\u003C/groupId>\n \u003CartifactId>junit\u003C/artifactId>\n \u003C/exclusion>\n \u003C/exclusions>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.junit.jupiter\u003C/groupId>\n \u003CartifactId>junit-jupiter-api\u003C/artifactId>\n \u003Cscope>test\u003C/scope>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.junit.jupiter\u003C/groupId>\n \u003CartifactId>junit-jupiter-engine\u003C/artifactId>\n \u003Cscope>test\u003C/scope>\n \u003C/dependency>\n\u003C/dependencies>\n\u003Cbuild>\n\u003Cplugins>\n \u003Cplugin>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-maven-plugin\u003C/artifactId>\n \u003C/plugin>\n \u003Cplugin>\n \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n \u003CartifactId>maven-surefire-plugin\u003C/artifactId>\n \u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.junit.platform\u003C/groupId>\n \u003CartifactId>junit-platform-surefire-provider\u003C/artifactId>\n \u003Cversion>${junit-platform.version}\u003C/version>\n \u003C/dependency>\n \u003C/dependencies>\n \u003C/plugin>\n\u003C/plugins>\n\u003C/build>\n",[676,40583,40584,40588,40592,40596,40600,40604,40609,40614,40619,40624,40629,40634,40638,40642,40647,40652,40656,40660,40664,40668,40673,40677,40681,40685,40690,40695,40700,40704,40709,40714,40718,40723,40728,40733,40738,40743,40748,40753,40758,40763,40767,40772],{"__ignoreMap":674},[679,40585,40586],{"class":681,"line":682},[679,40587,19199],{},[679,40589,40590],{"class":681,"line":790},[679,40591,19204],{},[679,40593,40594],{"class":681,"line":892},[679,40595,19209],{},[679,40597,40598],{"class":681,"line":901},[679,40599,19349],{},[679,40601,40602],{"class":681,"line":909},[679,40603,19354],{},[679,40605,40606],{"class":681,"line":918},[679,40607,40608],{}," \u003Cexclusions>\n",[679,40610,40611],{"class":681,"line":935},[679,40612,40613],{}," \u003Cexclusion>\n",[679,40615,40616],{"class":681,"line":944},[679,40617,40618],{}," \u003CgroupId>junit\u003C/groupId>\n",[679,40620,40621],{"class":681,"line":959},[679,40622,40623],{}," \u003CartifactId>junit\u003C/artifactId>\n",[679,40625,40626],{"class":681,"line":964},[679,40627,40628],{}," \u003C/exclusion>\n",[679,40630,40631],{"class":681,"line":977},[679,40632,40633],{}," \u003C/exclusions>\n",[679,40635,40636],{"class":681,"line":982},[679,40637,19219],{},[679,40639,40640],{"class":681,"line":988},[679,40641,19204],{},[679,40643,40644],{"class":681,"line":993},[679,40645,40646],{}," \u003CgroupId>org.junit.jupiter\u003C/groupId>\n",[679,40648,40649],{"class":681,"line":2129},[679,40650,40651],{}," \u003CartifactId>junit-jupiter-api\u003C/artifactId>\n",[679,40653,40654],{"class":681,"line":2140},[679,40655,19354],{},[679,40657,40658],{"class":681,"line":2145},[679,40659,19219],{},[679,40661,40662],{"class":681,"line":2154},[679,40663,19204],{},[679,40665,40666],{"class":681,"line":2159},[679,40667,40646],{},[679,40669,40670],{"class":681,"line":2164},[679,40671,40672],{}," \u003CartifactId>junit-jupiter-engine\u003C/artifactId>\n",[679,40674,40675],{"class":681,"line":3134},[679,40676,19354],{},[679,40678,40679],{"class":681,"line":3139},[679,40680,19219],{},[679,40682,40683],{"class":681,"line":3144},[679,40684,19363],{},[679,40686,40687],{"class":681,"line":3149},[679,40688,40689],{},"\u003Cbuild>\n",[679,40691,40692],{"class":681,"line":3169},[679,40693,40694],{},"\u003Cplugins>\n",[679,40696,40697],{"class":681,"line":3185},[679,40698,40699],{}," \u003Cplugin>\n",[679,40701,40702],{"class":681,"line":3194},[679,40703,19209],{},[679,40705,40706],{"class":681,"line":3199},[679,40707,40708],{}," \u003CartifactId>spring-boot-maven-plugin\u003C/artifactId>\n",[679,40710,40711],{"class":681,"line":3212},[679,40712,40713],{}," \u003C/plugin>\n",[679,40715,40716],{"class":681,"line":3217},[679,40717,40699],{},[679,40719,40720],{"class":681,"line":3222},[679,40721,40722],{}," \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n",[679,40724,40725],{"class":681,"line":3227},[679,40726,40727],{}," \u003CartifactId>maven-surefire-plugin\u003C/artifactId>\n",[679,40729,40730],{"class":681,"line":3232},[679,40731,40732],{}," \u003Cdependencies>\n",[679,40734,40735],{"class":681,"line":3499},[679,40736,40737],{}," \u003Cdependency>\n",[679,40739,40740],{"class":681,"line":3509},[679,40741,40742],{}," \u003CgroupId>org.junit.platform\u003C/groupId>\n",[679,40744,40745],{"class":681,"line":3516},[679,40746,40747],{}," \u003CartifactId>junit-platform-surefire-provider\u003C/artifactId>\n",[679,40749,40750],{"class":681,"line":3531},[679,40751,40752],{}," \u003Cversion>${junit-platform.version}\u003C/version>\n",[679,40754,40755],{"class":681,"line":3536},[679,40756,40757],{}," \u003C/dependency>\n",[679,40759,40760],{"class":681,"line":3541},[679,40761,40762],{}," \u003C/dependencies>\n",[679,40764,40765],{"class":681,"line":3546},[679,40766,40713],{},[679,40768,40769],{"class":681,"line":3551},[679,40770,40771],{},"\u003C/plugins>\n",[679,40773,40774],{"class":681,"line":3557},[679,40775,40776],{},"\u003C/build>\n",[4542,40778,39589],{"id":40779},"spring-boot-2-migration-guide",[651,40781,40782,40783,40788],{},"It probably goes without saying but with any major release like this simply flipping the version number in production isn't your best upgrade path. The first thing I would do is read through the ",[812,40784,40787],{"href":40785,"rel":40786},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide",[816],"Spring Boot 2.0 Migration Guide",". For me I found most of my issues revolved around the simplified security model changes and property changes. The migration guide includes an awesome tip for migrating your property files. ",[1004,40790,40791],{},[651,40792,40793,40794,40796,40797,40800,40801,40804],{},"With Spring Boot 2.0, many configuration properties were renamed/removed and developers need to update their ",[676,40795,16242],{}," / ",[676,40798,40799],{},"application.yml"," accordingly. To help you with that, Spring Boot ships a new ",[676,40802,40803],{},"spring-boot-properties-migrator"," module. Once added as a dependency to your project, this will not only analyze your application’s environment and print diagnostics at startup, but also temporarily migrate properties at runtime for you. This is a must have during your application migration:",[669,40806,40808],{"className":9101,"code":40807,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-properties-migrator\u003C/artifactId>\n\u003C/dependency>\n",[676,40809,40810,40814,40819,40824],{"__ignoreMap":674},[679,40811,40812],{"class":681,"line":682},[679,40813,9110],{},[679,40815,40816],{"class":681,"line":790},[679,40817,40818],{}," \u003CgroupId>org.springframework.boot\u003C/groupId>\n",[679,40820,40821],{"class":681,"line":892},[679,40822,40823],{}," \u003CartifactId>spring-boot-properties-migrator\u003C/artifactId>\n",[679,40825,40826],{"class":681,"line":901},[679,40827,9125],{},[651,40829,40830],{},"I am not sure what your thoughts are on this but I would certainly start playing with Spring Boot 2 and working on migrating your code over to it but with any major release, I usually wait until the next point release. This isn't just for Spring, I do this with pretty much everything from Apple to Pivotal to Angry Birds! ",[651,40832,40833],{},[812,40834,40837],{"href":40835,"rel":40836},"https://therealdanvega.com/wp-content/uploads/2018/02/pexels-photo-860379.jpeg",[816],[660,40838],{"alt":674,"src":40839},"./pexels-photo-860379-1024x659.jpeg",[4542,40841,21931],{"id":21930},[5909,40843,40845],{"id":40844},"documentation","Documentation",[651,40847,40848],{},"There are a ton of great resources out there but I want to start with the documentation. The documentation for Spring Framework and Spring Boot has really improved over the years. I love some of the dedicated guides we got this time around with Actuator and the Gradle Plugin. ",[5316,40850,40851,40858,40865,40871,40877,40884],{},[5332,40852,40853,40854],{},"Spring Boot Reference Guide: ",[812,40855,40856],{"href":40856,"rel":40857},"https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/",[816],[5332,40859,40860,40861],{},"Spring Boot API: ",[812,40862,40863],{"href":40863,"rel":40864},"https://docs.spring.io/spring-boot/docs/2.0.x/api/",[816],[5332,40866,40867,40868],{},"Spring Boot Actuator Web API: ",[812,40869,39932],{"href":39932,"rel":40870},[816],[5332,40872,40873,40874],{},"Spring Boot Gradle Plugin Reference: ",[812,40875,40063],{"href":40063,"rel":40876},[816],[5332,40878,40879,40880],{},"Spring Boot Maven Plugin Reference: ",[812,40881,40882],{"href":40882,"rel":40883},"https://docs.spring.io/spring-boot/docs/2.0.x/maven-plugin/",[816],[5332,40885,40886,40887],{},"Common Application Properties: ",[812,40888,40308],{"href":40308,"rel":40889},[816],[5909,40891,6016],{"id":39340},[651,40893,40894],{},"I have both of these books and think they both have some really good value. If you're looking for more of a reactive path check out Learning Spring Boot 2.0. ",[651,40896,40897],{},[812,40898,40901],{"href":40899,"rel":40900},"https://www.amazon.com/Learning-Spring-Boot-2-0-microservices/dp/1786463784/ref=as_li_ss_il?ie=UTF8&qid=1519826464&sr=8-1-spons&keywords=spring+boot+2&psc=1&linkCode=li3&tag=therealdanveg-20&linkId=02c093ca835465896029afb5ff59d6ce",[816],[660,40902],{"alt":674,"src":40903},"//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=1786463784&Format=_SL250_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=therealdanveg-20",[651,40905,40906,40913],{},[812,40907,40910],{"href":40908,"rel":40909},"https://www.amazon.com/Beginning-Spring-Boot-Applications-Microservices/dp/1484229304/ref=as_li_ss_il?ie=UTF8&qid=1519826464&sr=8-3&keywords=spring+boot+2&linkCode=li3&tag=therealdanveg-20&linkId=431754c7b1270cf3b194761f5df51b34",[816],[660,40911],{"alt":674,"src":40912},"//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=1484229304&Format=_SL250_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=therealdanveg-20",[660,40914],{"alt":674,"src":40915},"https://ir-na.amazon-adsystem.com/e/ir?t=therealdanveg-20&l=li3&o=1&a=1484229304",[5909,40917,21973],{"id":21972},[651,40919,40920],{},"I am working on wrapping up my next course titled \"Getting Started with Spring Boot 2\".",[5259,40922,40924],{"id":40923},"course-overview","Course Overview",[651,40926,40927,40928,15266],{},"This course offers hands-on experience building Spring Framework applications using Spring Boot. The first thing that is going to stand out is that we are going to move away from the boring, non-useful demos. In the new course, we are going to build a practical application from start to finish. This will allow you to see the process I use for building applications from idea to launch. One of the big thing I heard from my students in my first course is that they wanted more information on how to go into production. A great application isn’t that useful if we can deploy it into a production environment. We will take a look at using one of the most popular cloud services around, Amazon Web Services (AWS). On completion, students will have the foundation they need to build enterprise-ready Java applications. If you're interested in signing up for the waitlist and receive an introductory discount ",[812,40929,40932],{"href":40930,"rel":40931},"https://therealdanvega.com/spring-boot-2",[816],"please head over to the course page",[651,40934,40935],{},[812,40936,40939],{"href":40937,"rel":40938},"https://therealdanvega.com/wp-content/uploads/2018/02/getting_started_spring_boot_2.png",[816],[660,40940],{"alt":674,"src":40941},"./getting_started_spring_boot_2-1024x304.png",[5909,40943,40945],{"id":40944},"me-me-me","Me, Me, Me",[651,40947,40948,40949,40953,40954],{},"Please consider signing up for my mailing list (below) to be notified when I publish new content. I am making a large push towards more video and less long blog posts (like this one) so ",[812,40950,40952],{"href":22072,"rel":40951},[816],"please consider subscribing to my YouTube Channel"," as well. A video of me talking about this blog post. ",[812,40955,40956],{"href":40956,"rel":40957},"https://www.youtube.com/watch?v=91n6y-T-PC8",[816],[4542,40959,9042],{"id":9041},[651,40961,40962,40963,40965],{},"I have been looking forward to this release for a long time now and If you can't tell, I am super excited. _",[2939,40964,11650],{}," What are your favorite features in Spring Boot 2? _",[786,40967,40968],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":40970},[40971,40972,40976,40977,40983],{"id":39594,"depth":790,"text":39595},{"id":39730,"depth":790,"text":39731,"children":40973},[40974,40975],{"id":39737,"depth":892,"text":39738},{"id":39816,"depth":892,"text":567},{"id":40779,"depth":790,"text":39589},{"id":21930,"depth":790,"text":21931,"children":40978},[40979,40980,40981,40982],{"id":40844,"depth":892,"text":40845},{"id":39340,"depth":892,"text":6016},{"id":21972,"depth":892,"text":21973},{"id":40944,"depth":892,"text":40945},{"id":9041,"depth":790,"text":9042},{"slug":40985,"published":797,"date":40986,"tags":40987,"cover":40988},"what-is-new-spring-boot-2","2018-03-01T06:47:52-05:00",[4109,7055],"./pexels-photo-273238-2-760x507.jpeg",{"title":567,"description":567},"blog/2018/03/01/what-is-new-spring-boot-2","C0aZ8L-eAxY35nEQzDaiQtQs3b8Fe-Ksx_Vp-mtXWlI",{"id":40993,"title":564,"body":40994,"description":564,"extension":793,"meta":41117,"navigation":797,"path":565,"seo":41123,"stem":41124,"__hash__":41125},"content/blog/2018/04/25/java-development-2018.md",{"type":648,"value":40995,"toc":41110},[40996,40999,41002,41006,41009,41012,41016,41030,41039,41043,41046,41052,41058,41064,41070,41075,41081,41087,41093,41098,41100,41103],[651,40997,40998],{},"In this tutorial, we are going to look at what you should learn as a Java Developer in 2018.",[651,41000,41001],{},"When you're trying to keep up to date or learn a new language it can often be confusing on what you should be learning in that space. New projects pop up and often go away and its hard to tell what's still relevant. In this article (and the video above), we will try and make sense of what you should be learning today as a Java Developer.",[4542,41003,41005],{"id":41004},"java-development-in-2018","Java Development in 2018",[651,41007,41008],{},"This project started out as a really simple fun exercise for me. I wanted to get a list of what I thought Java developers should be learning in 2018 depending on their career path. That simple project I started has grown into the large list that you will see today. I just kept finding a reason to create another node or drill down even further.",[651,41010,41011],{},"When looking at this list I really want you to keep a couple of things in mind. When you see this large list of technologies please don’t let it scare you off. This is not a “you should know every single one of these” lists. It is just what I believe to be relevant in 2018. That last part is important, what I BELIEVE. I don’t know everything and I want this to be a fluid list, if you think I should add something to this list please let me know in the comments below and why and I will add it the mind map.",[4542,41013,41015],{"id":41014},"java-development-in-2018-mind-map","Java Development in 2018 Mind Map",[651,41017,41018,41019,41024,41025,664],{},"I really love using Mind Mapping software to organize my thoughts. It's actually really great for teaching and learning something new. It might make more sense if you watch the screencast below but if you want to jump right in you can check out the ",[812,41020,41023],{"href":41021,"rel":41022},"https://coggle.it/diagram/WqgTTNMJtPiHph_q/t/java-development-in-2018",[816],"Java Development in 2018 mind map here",". Also if you're new to mind mapping, in general, you can check out another short presentation I did on ",[812,41026,41029],{"href":41027,"rel":41028},"https://www.youtube.com/watch?v=FINqHqYjTlE",[816],"mind mapping for programmers",[651,41031,41032],{},[812,41033,41036],{"href":41034,"rel":41035},"https://therealdanvega.com/wp-content/uploads/2018/04/2018-04-25_06-01-13.png",[816],[660,41037],{"alt":41005,"src":41038},"./2018-04-25_06-01-13-1024x846.png",[5909,41040,41042],{"id":41041},"java-development-mind-map-sections","Java Development Mind Map Sections",[651,41044,41045],{},"This is a quick breakdown of what we cover in the mind mapping presentation. I will also give you a time next to the title in case you want to jump directly to that section.",[651,41047,41048,41051],{},[2939,41049,41050],{},"Core Fundamentals"," (3:10) These are core fundamentals that we should be learning during our development journey.",[651,41053,41054,41057],{},[2939,41055,41056],{},"Basic Web Development"," (6:30) Even if you don't want to become a full-stack developer you should know the basics of front-end development. I would argue that no matter what language you're learning that having a foundation in HTML, CSS & JavaScript is important.",[651,41059,41060,41063],{},[2939,41061,41062],{},"Advanced Front-end development"," (9:20) As you start to move through web development you might find out that you really like it. If you want to become a Java Developer and still want to learn some advanced front-end development skills you could become a full-stack developer.",[651,41065,41066,41069],{},[2939,41067,41068],{},"Java Language"," (16:00) This is going to be the largest area of learning on your Java Developer path. In this section, I lay out what I think are the fundamentals to learn as well as what are the things you should be learning from the new releases.",[651,41071,41072,41074],{},[2939,41073,38938],{}," (22:50) This is a collection of other languages that run on the JVM.",[651,41076,41077,41080],{},[2939,41078,41079],{},"Mobile Development"," (23:55) A quick overview If you want to build applications for different form factors like phones and tablets.",[651,41082,41083,41086],{},[2939,41084,41085],{},"Framework, Libraries & Tools"," (25:00) This is probably the largest section of the mind map. Here we are going to look at different frameworks, libraries or tools you might come across on your development journey.",[651,41088,41089,41092],{},[2939,41090,41091],{},"DevOps"," (35:00) “DevOps is about moving from having a single skill, such as being a developer or network engineer, to having multiple skill sets that adapt to the changing needs of the business and market.”",[651,41094,41095,41097],{},[2939,41096,21931],{}," (40:55) A collection of resources for learning a lot of the things we discussed in this tutorial.",[4542,41099,9042],{"id":9041},[651,41101,41102],{},"I really hope you enjoyed that tutorial friend. This started out as a small little side project and ballooned into what it is today. If you think something is missing from this list please let me know. I hope you have a wonderful day and as always...",[651,41104,41105,41106,41109],{},"Happy Coding",[41107,41108],"br",{},"\nDan",{"title":674,"searchDepth":790,"depth":790,"links":41111},[41112,41113,41116],{"id":41004,"depth":790,"text":41005},{"id":41014,"depth":790,"text":41015,"children":41114},[41115],{"id":41041,"depth":892,"text":41042},{"id":9041,"depth":790,"text":9042},{"slug":41118,"published":797,"date":41119,"tags":41120,"cover":41121,"video":41122},"java-development-2018","2018-04-25T06:39:22-04:00",[4109],"./java_development_in_2018-760x428","https://www.youtube.com/embed/MLXj0hd3usk",{"title":564,"description":564},"blog/2018/04/25/java-development-2018","zi4m0vbOdrdNI7NLhscA8pnevhHTqeP0WViKheI1bMo",{"id":41127,"title":561,"body":41128,"description":561,"extension":793,"meta":41318,"navigation":797,"path":562,"seo":41323,"stem":41324,"__hash__":41325},"content/blog/2018/09/07/i-am-joining-tech-elevator.md",{"type":648,"value":41129,"toc":41308},[41130,41139,41143,41152,41161,41164,41168,41174,41179,41182,41186,41189,41192,41197,41201,41204,41207,41216,41221,41225,41228,41258,41262,41265,41268,41273,41276,41280,41287,41292,41295,41297,41300,41305],[651,41131,41132,41133,41138],{},"I am happy to announce that I am joining ",[812,41134,41137],{"href":41135,"rel":41136},"https://www.techelevator.com/",[816],"Tech Elevator",". I am beyond excited to join this amazing company and I can't wait to tell you a little bit more about them and what they do.",[4542,41140,41142],{"id":41141},"what-i-am-leaving-behind","What I am leaving behind",[651,41144,41145,41146,41151],{},"Before we get to the good news and what I have to look forward to, I want to spend a few minutes talking about what I am leaving behind. For the past 6 1/2 years, I have spent my days working for ",[812,41147,41150],{"href":41148,"rel":41149},"http://www.markelcorp.com/",[816],"Markel",". This is an amazing company that has done some amazing things, and I just want to thank them for the opportunities they gave me.",[651,41153,41154,41155,41160],{},"I was brought into Markel by my good friend ",[812,41156,41159],{"href":41157,"rel":41158},"https://www.linkedin.com/in/jasondelmore",[816],"Jason Delmore"," and I can't thank him enough for everything he has done for me over the years. I had a chance to work with some really great people and work on some really fun projects.",[651,41162,41163],{},"This was a really hard decision for me because I really enjoyed the people I worked with and my commute to work was a short one (I work from home). I gave up some things in this move to do something I am really passionate about. Stepping outside of your comfort zone is never easy, but this is usually where the biggest rewards in life come from.",[4542,41165,41167],{"id":41166},"hello-tech-elevator","Hello, Tech Elevator!",[651,41169,41170,41173],{},[812,41171,41137],{"href":41135,"rel":41172},[816]," is a Coding Bootcamp that started right here in Cleveland, Ohio and now has 4 locations. In just 14 weeks they can teach you the skills that you will need to land a job as a software developer. What's great about Tech Elevator is they have a 92% graduation rate and an 89% placement rate. That placement rate is the top in the country, how awesome is that!",[651,41175,41176],{},[660,41177],{"alt":674,"src":41178},"./ClassroomJosh.jpg",[651,41180,41181],{},"Josh teaching students at our Cleveland Campus",[5909,41183,41185],{"id":41184},"what-is-a-coding-bootcamp","What is a Coding Bootcamp?",[651,41187,41188],{},"Traditionally, if you wanted to learn a specific skill you had to go to college to get it. You could attend a 4-year university or a more targeted trade school that typically takes 2 years. Even in a 2-year program, you're typically taking other classes that don't really have anything to do with the skill you're trying to learn.",[651,41190,41191],{},"This is where a coding boot camp or specialized trade school comes in. In a coding boot camp, you will spend a shorter amount of time in school but you're going to learn about software development the whole time. This allows the boot camp to focus in on the skills you need to land a job. The need for these boot camps is due to the overwhelming amount of available software development positions across the country.",[651,41193,41194],{},[660,41195],{"alt":674,"src":41196},"./brooke-cagle-609873-unsplash.jpg",[5909,41198,41200],{"id":41199},"why-tech-elevator","Why Tech Elevator",[651,41202,41203],{},"Tech Elevator limits the class sizes so that instructors have time to focus on each student and it's one of the things that I really admire about them. They don't just spend 14 weeks in a textbook, they teach you concepts and give you practical examples to work on.",[651,41205,41206],{},"The last 2 weeks is used splitting students up into teams, teaching them agile principles and having them go off and build a real-world project together. I was able to see some of the student's projects and for only having 2 weeks to complete and having just learned to program, I was really impressed! ",[651,41208,41209,41210,41215],{},"They also have a ",[812,41211,41214],{"href":41212,"rel":41213},"https://www.techelevator.com/pathway-program/",[816],"pathway program"," that helps students with interview prep and the placement of graduates. I think this is as important as the computer skills they are learning. When I graduated I was sent out on my own with no help at all and just expected to find my own way. They take a real interest in student placement and it shows by being #1 in the country.",[651,41217,41218],{},[660,41219],{"alt":674,"src":41220},"./Tech-Elevator-small-11-0.jpg",[5259,41222,41224],{"id":41223},"check-us-out","Check us out",[651,41226,41227],{},"If this sounds like something you're interested in, we have 4 locations in the following cities.",[5316,41229,41230,41237,41244,41251],{},[5332,41231,41232],{},[812,41233,41236],{"href":41234,"rel":41235},"https://www.techelevator.com/cleveland/",[816],"Cleveland",[5332,41238,41239],{},[812,41240,41243],{"href":41241,"rel":41242},"https://www.techelevator.com/columbus/",[816],"Columbus",[5332,41245,41246],{},[812,41247,41250],{"href":41248,"rel":41249},"https://www.techelevator.com/cincinnati/",[816],"Cincinnati",[5332,41252,41253],{},[812,41254,41257],{"href":41255,"rel":41256},"https://www.techelevator.com/pittsburgh/",[816],"Pittsburgh",[5909,41259,41261],{"id":41260},"what-is-my-role","What is my Role?",[651,41263,41264],{},"Now that you know a little bit about coding boot camps and Tech Elevator, I want to share with you what I will be doing. I am coming in as a Product Developer and my main product is our curriculum which has 2 tracks and is taught at all 4 of our locations.",[651,41266,41267],{},"It is my responsibility to take a look at the curriculum and make sure that the students are learning the skills that they will need and that employers are looking for. This will be done by taking feedback from instructors, listening to students and staying up to date in a fast-paced industry. We only get 14 weeks with these students and we need to make sure that every single day is an important one.",[651,41269,41270],{},[660,41271],{"alt":674,"src":41272},"./TEAnnouncement_Social.png",[651,41274,41275],{},"I will also be working on supplemental material for the students to help in making their day to day learning experience an even better one. We also have corporate training classes and we will be developing new classes for a variety of topics.",[5909,41277,41279],{"id":41278},"we-are-hiring","We are hiring!",[651,41281,41282,41283],{},"Join our growing, passionate team and contribute your talents to our mission of elevating people, companies, and communities. Browse all openings: ",[812,41284,41285],{"href":41285,"rel":41286},"https://lnkd.in/e3Yuf45",[816],[651,41288,41289],{},[660,41290],{"alt":674,"src":41291},"./fab20838-f363-4400-b0f3-4a535192f1de-original.png",[651,41293,41294],{},"We are hiring! ",[4542,41296,9042],{"id":9041},[651,41298,41299],{},"I consider myself extremely lucky to be a part of something special. I get to wake up and contribute to the next generation of software developers and for that, I am very grateful.",[651,41301,41302],{},[660,41303],{"alt":674,"src":41304},"./2018-09-07_08-28-36.png",[651,41306,41307],{},"This is a picture of the most recent graduates in Cleveland along with the Cleveland team.",{"title":674,"searchDepth":790,"depth":790,"links":41309},[41310,41311,41317],{"id":41141,"depth":790,"text":41142},{"id":41166,"depth":790,"text":41167,"children":41312},[41313,41314,41315,41316],{"id":41184,"depth":892,"text":41185},{"id":41199,"depth":892,"text":41200},{"id":41260,"depth":892,"text":41261},{"id":41278,"depth":892,"text":41279},{"id":9041,"depth":790,"text":9042},{"slug":41319,"published":797,"date":41320,"tags":41321,"cover":41322},"i-am-joining-tech-elevator","2018-09-07T01:00:58-04:00",[41137],"./Stacked-Logo-760x202.jpg",{"title":561,"description":561},"blog/2018/09/07/i-am-joining-tech-elevator","ylpFF57LteekDSkdpIEVWCn3gU3JC8C1iZjX3sjR2mY",{"id":41327,"title":558,"body":41328,"description":558,"extension":793,"meta":41507,"navigation":797,"path":559,"seo":41514,"stem":41515,"__hash__":41516},"content/blog/2018/11/05/cleveland-women-in-tech-presentation.md",{"type":648,"value":41329,"toc":41500},[41330,41343,41346,41349,41353,41356,41361,41364,41367,41370,41373,41379,41381,41384,41388,41411,41416,41453,41458,41490,41492],[651,41331,41332,41333,41336,41337,41342],{},"Today I have the honor of representing ",[812,41334,41137],{"href":18084,"rel":41335},[816]," and presenting at the very 1st ",[812,41338,41341],{"href":41339,"rel":41340},"https://getwitit.org/agenda-cle/",[816],"getWITit conference in Cleveland",". This conference has been a huge success in Columbus and it is just getting started in Cleveland. ",[651,41344,41345],{},"When I first started my new position at Tech Elevator I was approached about speaking at this conference. I jumped all over this opportunity to represent us at this inaugural conference. ",[651,41347,41348],{},"When we started working on an idea for the presentation I wasn't really sure what we were going to do. A coworker suggested that I talk about something I am passionate about, learning. I thought it was a great idea and ran with it. ",[4542,41350,41352],{"id":41351},"presentation-details","Presentation Details",[651,41354,41355],{},"The title of my presentation is \"The rest of your life starts today. A guide to continued learning and personal growth.\"",[651,41357,41358],{},[660,41359],{"alt":674,"src":41360},"./Screen-Shot-2018-11-04-at-9.25.10-PM.png",[651,41362,41363],{},"This is the first time I have ever given a presentation on something that wasn't code related. I mean I found a way to work in some code related slides but that isn't the theme of the presentation.",[651,41365,41366],{},"This presentation is about something I love, learning new skills. It also comes at a great time in my life. I have a newborn at home so time comes at a premium these days. In this presentation I share some of my best tips on how to learn a new skill. After 20 years of \"loving to learn\" I believe that I have a lot to share around this subject. ",[5909,41368,38967],{"id":41369},"slide-deck",[651,41371,41372],{},"I had a lot of fun putting this slide deck together. While it isn't perfect and there are some slides I wish weren't as boring I think for the most part I am happy. They are fun slides and my hope is they will keep the audience engaged. Did I mention there were 52 of them? ",[651,41374,41375],{},[812,41376,41378],{"href":41377},"./Cleveland-Women-in-Tech-Conference.pdf","Cleveland Women in Tech Conference",[5909,41380,21931],{"id":21930},[651,41382,41383],{},"Throughout the presentation, I mentioned a number of resources. I wanted this to be a place where I can mention all of those resources so here they are. ",[651,41385,41386],{},[2939,41387,6016],{},[5316,41389,41390,41397,41404],{},[5332,41391,41392],{},[812,41393,41396],{"href":41394,"rel":41395},"https://amzn.to/2OqS87U",[816],"Outliers: The Story of success",[5332,41398,41399],{},[812,41400,41403],{"href":41401,"rel":41402},"https://amzn.to/2FoMx2J",[816],"The first 20 hours. How to learn anything fast.",[5332,41405,41406],{},[812,41407,41410],{"href":41408,"rel":41409},"https://amzn.to/2OnA7at",[816],"Your best year ever: A 5-stop plan for achieving your most important goals",[651,41412,41413],{},[2939,41414,41415],{},"Tools",[5316,41417,41418,41425,41432,41439,41446],{},[5332,41419,41420],{},[812,41421,41424],{"href":41422,"rel":41423},"https://asana.com/",[816],"Asana",[5332,41426,41427],{},[812,41428,41431],{"href":41429,"rel":41430},"https://trello.com/",[816],"Trello",[5332,41433,41434],{},[812,41435,41438],{"href":41436,"rel":41437},"https://evernote.com/",[816],"Evernote",[5332,41440,41441],{},[812,41442,41445],{"href":41443,"rel":41444},"https://code.visualstudio.com/",[816],"Visual Studio Code",[5332,41447,41448],{},[812,41449,41452],{"href":41450,"rel":41451},"https://www.google.com/chrome/",[816],"Google Chrome",[651,41454,41455],{},[2939,41456,41457],{},"Websites",[5316,41459,41460,41467,41472,41478,41483],{},[5332,41461,41462],{},[812,41463,41466],{"href":41464,"rel":41465},"https://michaelhyatt.com",[816],"Michael Hyatt",[5332,41468,41469],{},[812,41470,41137],{"href":41135,"rel":41471},[816],[5332,41473,41474],{},[812,41475,39076],{"href":41476,"rel":41477},"https://therealdanvega.com",[816],[5332,41479,41480],{},[812,41481,39497],{"href":39495,"rel":41482},[816],[5332,41484,41485],{},[812,41486,41489],{"href":41487,"rel":41488},"https://www.audible.com",[816],"Audible",[5909,41491,9042],{"id":9041},[651,41493,41494,41495,664],{},"I really wanted to thank the conference organizers for allowing me to speak. I also want to thank everyone who came out to my presentation. I know my coworkers will be in full force so I also want to thank them for supporting me. If you're interested in having me give this presentation at your next conference or meetup, ",[812,41496,41499],{"href":41497,"rel":41498},"https://therealdanvega.com/contact",[816],"contact me",{"title":674,"searchDepth":790,"depth":790,"links":41501},[41502],{"id":41351,"depth":790,"text":41352,"children":41503},[41504,41505,41506],{"id":41369,"depth":892,"text":38967},{"id":21930,"depth":892,"text":21931},{"id":9041,"depth":892,"text":9042},{"slug":41508,"published":797,"date":41509,"tags":41510,"cover":41513},"cleveland-women-in-tech-presentation","2018-11-05T06:50:29-05:00",[41511,41512],"conference","presentation","./merherstory-cover.png",{"title":558,"description":558},"blog/2018/11/05/cleveland-women-in-tech-presentation","btW5svEFrdG0hfkDVu31X3Des6ywdZire69sn-AjWM0",{"id":41518,"title":555,"body":41519,"description":555,"extension":793,"meta":41716,"navigation":797,"path":556,"seo":41721,"stem":41722,"__hash__":41723},"content/blog/2018/11/06/cleveland-women-in-tech-conference-recap.md",{"type":648,"value":41520,"toc":41700},[41521,41528,41532,41535,41543,41546,41560,41565,41569,41572,41575,41579,41582,41586,41589,41592,41601,41605,41608,41611,41614,41618,41621,41624,41651,41654,41662,41665,41668,41673,41677,41680,41685,41689,41692,41695,41697],[651,41522,41523,41524,41527],{},"This is going to be just a quick recap of the inaugural ",[812,41525,41378],{"href":41339,"rel":41526},[816],". I think I will have a little bit of a unique perspective to share on this as I am a presenter at this conference and one of a handful of men in attendance. ",[4542,41529,41531],{"id":41530},"opening-keynote","Opening Keynote",[651,41533,41534],{},"The conference was held at Corporate College in Warrensville Heights and last I heard there were about 230+ attendees. The conference in Columbus doubled in year 2 and I really hope we see the same in Cleveland next year. The title of the keynote was \"Making HERstory stick\" and I thought it was an amazing panel discussion with some CIOs from the Cleveland area, each who had some unique perspectives on each of the questions. ",[1004,41536,41537,41540],{},[651,41538,41539],{},"Various industry leading trailblazers from prominent organizations all speak about their journey to leadership and their views on developing and nurturing a welcoming supportive culture of diversity. Discussion will include how innovation in technology changed their organization, delivery to the business and improved results.",[651,41541,41542],{},"Making HERstory stick",[651,41544,41545],{},"The panel was made up of:",[5316,41547,41548,41551,41554,41557],{},[5332,41549,41550],{},"Sandy Rapp - VP & CIO, The Timken Company",[5332,41552,41553],{},"Tracey Petkovic - CIO, Westfield Insurance",[5332,41555,41556],{},"Jane Alexander - CIO, The Cleveland Museum of Art",[5332,41558,41559],{},"Lisa Ward - VP R&D and Strategy, STERIS Corporation",[651,41561,41562],{},[660,41563],{"alt":674,"src":41564},"./IMG_4469.jpg",[5909,41566,41568],{"id":41567},"cio-panel","CIO Panel",[651,41570,41571],{},"I wish I would have had my laptop in front of me to take some notes but I didn't. There were some really great questions asked and some excellent responses.",[651,41573,41574],{},"One thing that really stood out to me that everyone in tech has an obligation to take the businesses requirements and then return to them a solution. You shouldn't be taking solution requests from the business and I agree with this 1000%.",[4542,41576,41578],{"id":41577},"sessions-i-attended","Sessions I attended",[651,41580,41581],{},"I'll be honest and say it was pretty tough being one of only a few guys sitting in on each of these sessions today. It's that perspective that gave me a greater appreciation for the women who make it out to these conferences who are mostly filled with men. Not that I didn't already appreciate you but I think I will make a greater effort next time I see someone in that position to make them feel more welcome.",[5909,41583,41585],{"id":41584},"the-blockchain-disruption-lisa-turner","The Blockchain Disruption - Lisa Turner",[651,41587,41588],{},"This was a good introduction to Blockchain and cryptocurrency. This is another one of those technologies on my list that I am interested in but at the same time not sure why I am interested in it. I get that the idea of decentralized networks are important for privacy.",[651,41590,41591],{},"What I don't get is where all of this currency actually comes from. I mean I understand the role of miners but It still makes not sense to me how we can just invent currency. As much as we hate the big bad banks I at least know that my money is backed by the federal reserve. ",[651,41593,41594,41595,41600],{},"I did find a stand at the conference for an upcoming conference in ",[812,41596,41599],{"href":41597,"rel":41598},"https://blocklandcleveland.com/solutions",[816],"Cleveland put on by Blockland",". I don't know if I can make it to this but this seems like a really good conference that I would love to attend. ",[5909,41602,41604],{"id":41603},"imposter-syndrome-and-how-mentoring-helped-me-combat-it-joanna-hughes","Imposter Syndrome and How mentoring helped me combat it - Joanna Hughes",[651,41606,41607],{},"Joanna is a front-end developer for Vitamix and started the presentation off detailing what Imposter Syndrome is. She started out describing how this affects junior developers and in particular females. ",[651,41609,41610],{},"While both of these are true I want to point out that 20 years into my career I still feel like an imposter at times. I didn't go to some fancy school and I haven't worked for any of the big tech companies that only hire the best of the best. I often get the feeling of who am I and why should anyone listen to me. You just need to keep reassuring yourself that you know what you're doing and to move forward.",[651,41612,41613],{},"She went into a conversation about how mentoring other developers really helped her combat Imposter Syndrome. When you start to see that other developers face the same problems as you do it really helps take down a wall. I thought Joanna did a great job and couldn't believe that it was her first conference talk. You certainly couldn't tell! ",[4542,41615,41617],{"id":41616},"my-presentation","My Presentation",[651,41619,41620],{},"My presentation was titled \"The rest of your life starts today. A guide to continued learning and personal growth.\" and I had the session right after lunch. Now, being a conference attendee pro, I know the sessions right after lunch are tough to stay awake in so I was going to need to be on my game today. ",[651,41622,41623],{},"While I was setting up my presentation the room started to fill up quickly. It filled up so much that everyone started lining the walls and sitting on the floor. Luckily the event staff was on it and wheeled in a ton of chairs. Thanks to everyone for making a guy feel special. ",[5316,41625,41626,41631,41636,41641,41646],{},[5332,41627,41628],{},[660,41629],{"alt":674,"src":41630},"./IMG_4482.jpg",[5332,41632,41633],{},[660,41634],{"alt":674,"src":41635},"./IMG_4481.jpg",[5332,41637,41638],{},[660,41639],{"alt":674,"src":41640},"./IMG_4480.jpg",[5332,41642,41643],{},[660,41644],{"alt":674,"src":41645},"./IMG_4479.jpg",[5332,41647,41648],{},[660,41649],{"alt":674,"src":41650},"./IMG_4478.jpg",[651,41652,41653],{},"I thought my presentation went really well. I practiced enough to where I knew what I was going to talk about in each slide without it feeling rehearsed. I had some great pictures of my daughter in there so even if it wasn't going well I could always fall back on the cute baby.",[651,41655,41656,41657,664],{},"I thought it was a good pace and my hope is that everyone learned something new yesterday. Thanks again to everyone who attended the presentation, asked questions and laughed at my jokes. If you want to grab the slide deck or the resources I discussed you can ",[812,41658,41661],{"href":41659,"rel":41660},"https://therealdanvega.com/blog/2018/11/05/cleveland-women-in-tech-presentation",[816],"get them here",[5909,41663,41137],{"id":41664},"tech-elevator",[651,41666,41667],{},"A special thanks to my Tech Elevator family for not only helping me with the presentation but also supporting it. They were all there bright and early to volunteer for the conference. You ladies rock! ",[651,41669,41670],{},[660,41671],{"alt":674,"src":41672},"./IMG_4471.jpg",[4542,41674,41676],{"id":41675},"finding-mentors","Finding Mentors",[651,41678,41679],{},"I got an interesting question after the presentation. The question is where did my mentors come from and where can we find our mentors at. I feel like I did an ok job answering this in person but there is so much more I want to say on the subject. ",[651,41681,41682],{},[660,41683],{"alt":674,"src":41684},"./neonbrand-618320-unsplash.jpg",[5909,41686,41688],{"id":41687},"mentor","Mentor",[651,41690,41691],{},"I am going to try and put together a whole post on this but here is the short answer. The best place to find mentors are in the places where your types of people are. If you're a junior developer and work for a bigger company with lots of developers, this is a great place to start. If you work for a smaller company try to attend local meetup groups where like minded developers might be. ",[651,41693,41694],{},"Another great approach to this is to get involved in an online community. If you are really into the VueJS framework for instance, be where other VueJS developers are. Try getting involved in discussions in the places where they hang out, comment on blog articles and if you can try and contribute back to the community. I have made some amazing friends and developers I look up to just by being involved in a community and attending meetups & conferences. ",[4542,41696,9042],{"id":9041},[651,41698,41699],{},"I had such an amazing time at this conference. Weather I'm asked back next year or not my hope is that this conference really grows. I think it's a great place for women in tech to have their voices heard and inspire the next generation of movers and shakers. Round of applause Cleveland, you did really well yesterday :)",{"title":674,"searchDepth":790,"depth":790,"links":41701},[41702,41705,41709,41712,41715],{"id":41530,"depth":790,"text":41531,"children":41703},[41704],{"id":41567,"depth":892,"text":41568},{"id":41577,"depth":790,"text":41578,"children":41706},[41707,41708],{"id":41584,"depth":892,"text":41585},{"id":41603,"depth":892,"text":41604},{"id":41616,"depth":790,"text":41617,"children":41710},[41711],{"id":41664,"depth":892,"text":41137},{"id":41675,"depth":790,"text":41676,"children":41713},[41714],{"id":41687,"depth":892,"text":41688},{"id":9041,"depth":790,"text":9042},{"slug":41717,"published":797,"date":41718,"tags":41719,"cover":41720},"cleveland-women-in-tech-conference-recap","2018-11-06T11:59:51-05:00",[41511,41512],"./IMG_4467.jpg",{"title":555,"description":555},"blog/2018/11/06/cleveland-women-in-tech-conference-recap","qcMKcrMTubyUpOqTPdmmSXAhGqD_NhfCQ_cdaQdTKTk",{"id":41725,"title":552,"body":41726,"description":552,"extension":793,"meta":41817,"navigation":797,"path":553,"seo":41821,"stem":41822,"__hash__":41823},"content/blog/2018/11/20/new-course-getting-started-with-spring-boot-2.md",{"type":648,"value":41727,"toc":41810},[41728,41737,41741,41744,41761,41764,41771,41775,41784,41787,41792,41796,41799,41801,41804],[651,41729,41730,41731,41736],{},"I am so excited to announce that ",[812,41732,41735],{"href":41733,"rel":41734},"https://www.udemy.com/spring-boot-2/?couponCode=TRDV_BLOG_10",[816],"my new course is live","! This course was a long time in the making and I am just so thrilled that I get to share it with all of you because I believe it is my best one yet!",[4542,41738,41740],{"id":41739},"whats-included-in-the-course","What's Included in the course?",[651,41742,41743],{},"The biggest feedback I received from you was that you wanted a more project focused course instead of some I'm examples. In this course, we are going to build a real application together from start to finish. We are going to use the latest technologies to build out our application including:",[5316,41745,41746,41749,41752,41755,41758],{},[5332,41747,41748],{},"Spring Framework 5",[5332,41750,41751],{},"Spring Boot 2",[5332,41753,41754],{},"Spring Data JPA",[5332,41756,41757],{},"Spring Security ",[5332,41759,41760],{},"So Much More",[651,41762,41763],{},"This course is packed with over 10 hours of content and I really hope you enjoy it. I created a short trailer to give you a better idea of what to expect. ",[651,41765,41766],{},[812,41767,41770],{"href":41768,"rel":41769},"https://www.youtube.com/watch?v=cz%5C_7KhbfaNE",[816],"https://www.youtube.com/watch?v=cz\\_7KhbfaNE",[4542,41772,41774],{"id":41773},"the-road-to-this-course-launch","The Road to this course launch",[651,41776,41777,41778,41783],{},"If you had a chance to read my ",[812,41779,41782],{"href":41780,"rel":41781},"https://therealdanvega.com/blog/2018/09/24/content-announcements",[816],"content announcements"," in September I gave you a pretty good update as to what has been going on in my life. Trying to find dedicated time to work on this course is probably the biggest reason for the delay of this course. I also wanted to make sure that it wasn't rushed and that I was able to produce the course that you asked for.",[651,41785,41786],{},"I can also tell you that I learned some valuable lesson producing this course. I am in the process of trying to gather my thoughts and put something out there that might help other developers who want to create their own courses. I might not be the best course creator on the planet but I have definitely learned a few things and can hopefully show you what not to do. ",[651,41788,41789],{},[660,41790],{"alt":674,"src":41791},"./wahid-khene-978156-unsplash-1024x594.jpg",[5909,41793,41795],{"id":41794},"video-editing","Video Editing",[651,41797,41798],{},"Launching a new course can be extremely gratifying and very stressful at the same time. I don't have this feeling when I write a new blog post or launch a new video on YouTube and it's probably due to the exchange of money. Even under $10 for 10 hours of content, I still feel a sense of Imposter Syndrome. Who am I to ask for money in exchange for knowledge? What if everyone hates my course? What if they don't learn anything? These are all very real feelings that I continue to battle. Thank you to all of you who continue to support my efforts, I appreciate you more than you will ever know. ",[4542,41800,9042],{"id":9041},[651,41802,41803],{},"I am really happy to finally get this course into your hands. This is not a final product and I will be working on a few things to improve this course over the next month. If you have feedback (good or bad) please let me hear it so I can make this the best Spring Boot 2 course around. ",[651,41805,41806],{},[812,41807,41809],{"href":41733,"rel":41808},[816],"Get this course for $9.99",{"title":674,"searchDepth":790,"depth":790,"links":41811},[41812,41813,41816],{"id":41739,"depth":790,"text":41740},{"id":41773,"depth":790,"text":41774,"children":41814},[41815],{"id":41794,"depth":892,"text":41795},{"id":9041,"depth":790,"text":9042},{"slug":41818,"published":797,"date":41819,"tags":21972,"cover":41820},"new-course-getting-started-with-spring-boot-2","2018-11-20T12:39:53-05:00","./teachable_course_graphic-760x428.png",{"title":552,"description":552},"blog/2018/11/20/new-course-getting-started-with-spring-boot-2","0CafHxuPnktFIAeVr1vqd8OF9AifLFr-L22DQGOvp1A",{"id":41825,"title":549,"body":41826,"description":549,"extension":793,"meta":43155,"navigation":797,"path":550,"seo":43162,"stem":43163,"__hash__":43164},"content/blog/2018/12/21/macbook-pro-setup-my-setup-with-detailed-instructions.md",{"type":648,"value":41827,"toc":43127},[41828,41831,41834,41854,41857,41862,41866,41869,41872,41877,41880,41884,41887,41892,41901,41909,41913,41916,41927,41930,41933,41942,41955,41958,41961,41964,41970,41973,41998,42001,42012,42016,42025,42042,42045,42113,42117,42120,42133,42141,42145,42148,42156,42174,42181,42184,42221,42225,42228,42275,42278,42281,42285,42288,42291,42303,42306,42321,42329,42357,42360,42366,42370,42373,42380,42384,42387,42391,42400,42424,42427,42442,42446,42538,42542,42545,42548,42568,42572,42595,42598,42601,42615,42618,42630,42633,42652,42655,42709,42712,42728,42731,42734,42748,42751,42768,42771,42783,42786,42839,42842,42851,42870,42877,42887,42890,42908,42911,42926,42930,42933,42937,42965,42969,42972,42983,42997,43005,43013,43017,43020,43025,43041,43046,43054,43058,43061,43073,43077,43080,43100,43104,43107,43113,43115,43118,43124],[651,41829,41830],{},"I thought I would take some time and document what I do when I get a new machine. This is going to walk through some preferences I configure, applications I install and settings I use for different programs. I am always curious to see how other software developers are setting up their development machines so I figured it would be a good opportunity to share mine.",[651,41832,41833],{},"I am a Software Developer so most of my configuration will be around programming. My current MacBook configuration is as follows:",[5316,41835,41836,41839,41842,41845,41848,41851],{},[5332,41837,41838],{},"MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)",[5332,41840,41841],{},"Processor: 2.3 GHz Intel Core i5",[5332,41843,41844],{},"Memory: 16 GB",[5332,41846,41847],{},"Startup Disk: Macintosh HD",[5332,41849,41850],{},"Graphics: Intel Iris Plus Graphics 640 1536 MB",[5332,41852,41853],{},"Storage: 500 GB",[651,41855,41856],{},"When you power on your MacBook you are going to run through the setup. I usually configure anything I can here like Wi-fi & Apple ID. The rest of the instructions will assume you made it through the setup and are on the desktop.",[651,41858,41859],{},[7300,41860,41861],{},"If you don't want to have to wait for disk encryption (error in macOS updates section) don't select that during setup and perform that at a later time",[4542,41863,41865],{"id":41864},"macos-updates","macOS Updates",[651,41867,41868],{},"The first thing I do is run any updates that are available. In my case the laptop came preloaded with macOS Sierra and macOS Mojave has been released.",[651,41870,41871],{},"So my first step is going to be to update to macOS Mojave. You can download this and run this from the App Store.",[651,41873,41874],{},[7300,41875,41876],{},"Error: You may not install to this volume because it is currently being encrypted",[651,41878,41879],{},"If you get the error above than you chose the option of encrypting your hard drive during setup. Make sure you have the power plugged in and go to System Preferences > Security & Privacy > File Vault. From there you can see the progress of encryption and when its complete you can launch the macOS Mojave installer.",[4542,41881,41883],{"id":41882},"app-store","App Store",[651,41885,41886],{},"If you have signed in with your Apple Id you will be taken directly to the Apple Store. This is a good time to run any updates that are available.",[651,41888,41889],{},[7300,41890,41891],{},"You might run into issues here because the apple id that downloaded them is not you. In this case I just removed those apps I am not using (GarageBand,Numbers,Pages,iMovie…)",[651,41893,41894,41895,41900],{},"To remove applications I like installing ",[812,41896,41899],{"href":41897,"rel":41898},"https://freemacsoft.net/appcleaner/",[816],"AppCleaner",". This will make sure that the application and any related files will be removed.",[651,41902,41903,41904],{},"Another application I really love for this and so many other things is ",[812,41905,41908],{"href":41906,"rel":41907},"https://macpaw.com/cleanmymac",[816],"Clean My Mac",[5909,41910,41912],{"id":41911},"garage-band","Garage Band",[651,41914,41915],{},"Unfortunately AppCleaner only works for 3rd party installed applications and wont remove GarageBand. You need to remove this manually and while there are a few more locations these are the main 3 you should remove. If you're going to use apple sound affects in other programs please read up on this before deleting.",[5316,41917,41918,41921,41924],{},[5332,41919,41920],{},"/Applications/GarageBand.app",[5332,41922,41923],{},"/Library/Application Support/GarageBand/",[5332,41925,41926],{},"/Library/Audio/Apple Loops/Apple/",[651,41928,41929],{},"Empty Trash",[4542,41931,39958],{"id":41932},"getting-started",[651,41934,41935,41936,41941],{},"Anything I can install using ",[812,41937,41940],{"href":41938,"rel":41939},"https://brew.sh/",[816],"HomeBrew"," I will. Before you install HomeBrew though you need to install the xcode command line utilities. Open up a new terminal and type the following command. Even if you plan on installing xcode I believe you still need to install these now as they moved them out of the standard installation.",[669,41943,41945],{"className":5851,"code":41944,"language":5853,"meta":674,"style":674},"xcode-select --install\n",[676,41946,41947],{"__ignoreMap":674},[679,41948,41949,41952],{"class":681,"line":682},[679,41950,41951],{"class":880},"xcode-select",[679,41953,41954],{"class":931}," --install\n",[651,41956,41957],{},"The Command Line Tools Package is a small self-contained package available for download separately from Xcode and that allows you to do command line development in OS X. It consists of two components: OS X SDK and command-line tools such as Clang, which are installed in /usr/bin.",[5909,41959,41940],{"id":41960},"homebrew",[651,41962,41963],{},"As I said earlier I use HomeBrew to install anything that it can install. I recently found out about cask which makes my life so much easier. If you normally use brew to install something like google-chrome you know that you have to then drag it into the applications folder. If you you use cask it will not only download the package but also move it into the applications folder.",[651,41965,41966],{},[812,41967,41969],{"href":41938,"rel":41968},[816],"HomeBrew Website",[651,41971,41972],{},"Installation:",[669,41974,41976],{"className":5851,"code":41975,"language":5853,"meta":674,"style":674},"/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"\n",[676,41977,41978],{"__ignoreMap":674},[679,41979,41980,41983,41986,41989,41992,41995],{"class":681,"line":682},[679,41981,41982],{"class":880},"/usr/bin/ruby",[679,41984,41985],{"class":931}," -e",[679,41987,41988],{"class":689}," \"$(",[679,41990,41991],{"class":880},"curl",[679,41993,41994],{"class":931}," -fsSL",[679,41996,41997],{"class":689}," https://raw.githubusercontent.com/Homebrew/install/master/install)\"\n",[651,41999,42000],{},"Post Installation",[5316,42002,42003,42006,42009],{},[5332,42004,42005],{},"If you need help with brew you can run brew help.",[5332,42007,42008],{},"brew update - You shouldn't have anything to update but its good to check.",[5332,42010,42011],{},"brew search 'term' to search for brews",[4542,42013,42015],{"id":42014},"terminal-bash-iterm","Terminal / Bash / iTerm",[651,42017,42018,42019,42024],{},"Now that we have HomeBrew installed it's time to start installing some software. The default version of bash is v3.2 and I want to go ahead and upgrade this to 4.x. There is a ",[812,42020,42023],{"href":42021,"rel":42022},"http://clubmate.fi/upgrade-to-bash-4-in-mac-os-x/",[816],"really good guide"," here that you can follow to upgrade bash.",[5316,42026,42027,42030,42033,42036],{},[5332,42028,42029],{},"bash -v (bash-3.2)",[5332,42031,42032],{},"brew install bash",[5332,42034,42035],{},"if you close terminal or open a new tab it will show 4.4 but this still isn't the default version.",[5332,42037,42038,42041],{},[2939,42039,42040],{},"which bash"," will show you what bash you're using.",[651,42043,42044],{},"Now that we have bash updated we need to make that our default shell. To do so you need to edit /etc/shells",[669,42046,42048],{"className":5851,"code":42047,"language":5853,"meta":674,"style":674},"sudo vi /etc/shells\nadd the path /usr/local/bin/bash\ncomment out the others\n\nChange to the new shell\nchsh -s /usr/local/bin/bash \n",[676,42049,42050,42061,42072,42085,42089,42103],{"__ignoreMap":674},[679,42051,42052,42055,42058],{"class":681,"line":682},[679,42053,42054],{"class":880},"sudo",[679,42056,42057],{"class":689}," vi",[679,42059,42060],{"class":689}," /etc/shells\n",[679,42062,42063,42065,42067,42069],{"class":681,"line":790},[679,42064,12952],{"class":880},[679,42066,22351],{"class":689},[679,42068,18596],{"class":689},[679,42070,42071],{"class":689}," /usr/local/bin/bash\n",[679,42073,42074,42077,42080,42082],{"class":681,"line":892},[679,42075,42076],{"class":880},"comment",[679,42078,42079],{"class":689}," out",[679,42081,22351],{"class":689},[679,42083,42084],{"class":689}," others\n",[679,42086,42087],{"class":681,"line":901},[679,42088,889],{"emptyLinePlaceholder":797},[679,42090,42091,42094,42096,42098,42100],{"class":681,"line":909},[679,42092,42093],{"class":880},"Change",[679,42095,21703],{"class":689},[679,42097,22351],{"class":689},[679,42099,2054],{"class":689},[679,42101,42102],{"class":689}," shell\n",[679,42104,42105,42108,42111],{"class":681,"line":918},[679,42106,42107],{"class":880},"chsh",[679,42109,42110],{"class":931}," -s",[679,42112,42071],{"class":689},[5909,42114,42116],{"id":42115},"bash-profile","Bash Profile",[651,42118,42119],{},"Now that we have bash updated I need to customize my shell. Look in your home directory and see if you have a .bash_profile and if you don't you can create one using the following command.",[669,42121,42123],{"className":5851,"code":42122,"language":5853,"meta":674,"style":674},"touch .bash_profile\n",[676,42124,42125],{"__ignoreMap":674},[679,42126,42127,42130],{"class":681,"line":682},[679,42128,42129],{"class":880},"touch",[679,42131,42132],{"class":689}," .bash_profile\n",[651,42134,42135,42136,42140],{},"This is where you can add all kinds of customization's to bash. I have included ",[812,42137,42139],{"href":42138},".bash_profile","my .bash_profile"," in this repository if you want to check it out. As always if you have questions about what's in there please let me know.",[5909,42142,42144],{"id":42143},"iterm2","iTerm2",[651,42146,42147],{},"For the longest time I used the terminal and even had a couple of people call me out on it 😂",[651,42149,42150,42151,664],{},"I am now using iTerm2 full time and I absolutely love it. If you want to find out about some of the features & configurations it gives you please ",[812,42152,42155],{"href":42153,"rel":42154},"https://www.iterm2.com/features.html",[816],"check out their website",[669,42157,42159],{"className":5851,"code":42158,"language":5853,"meta":674,"style":674},"brew cask install iterm2\n",[676,42160,42161],{"__ignoreMap":674},[679,42162,42163,42166,42169,42171],{"class":681,"line":682},[679,42164,42165],{"class":880},"brew",[679,42167,42168],{"class":689}," cask",[679,42170,24571],{"class":689},[679,42172,42173],{"class":689}," iterm2\n",[651,42175,42176,42177],{},"One thing I like to do is customize the colors and a great resource for that is ",[812,42178,42179],{"href":42179,"rel":42180},"https://iterm2colorschemes.com/",[816],[651,42182,42183],{},"This is a list of my favorite color schemes.",[5316,42185,42186,42189,42192,42194,42197,42200,42203,42206,42209,42212,42215,42218],{},[5332,42187,42188],{},"Dracula",[5332,42190,42191],{},"FirefoxDev",[5332,42193,17458],{},[5332,42195,42196],{},"Grape",[5332,42198,42199],{},"Grass",[5332,42201,42202],{},"Hipster Green",[5332,42204,42205],{},"Homebrew",[5332,42207,42208],{},"Man Page",[5332,42210,42211],{},"Material",[5332,42213,42214],{},"MaterialDark",[5332,42216,42217],{},"Novel",[5332,42219,42220],{},"OceanicMaterial",[4542,42222,42224],{"id":42223},"development-setup","Development Setup",[651,42226,42227],{},"Now that I have a nice looking command line full of features its time to start installing all of the different applications I will use. If you have any questions about any of these or why I install them please see the contact me section below.",[5316,42229,42230,42233,42236,42239,42242,42245,42248,42251,42254,42257,42260,42263,42266,42269],{},[5332,42231,42232],{},"brew install git",[5332,42234,42235],{},"brew cask install google-chrome",[5332,42237,42238],{},"brew cask install google-chrome-canary",[5332,42240,42241],{},"brew cask install firefox",[5332,42243,42244],{},"brew cask install firefox-developer-edition",[5332,42246,42247],{},"brew cask install visual-studio-code",[5332,42249,42250],{},"brew cask install visual-studio-code-insiders",[5332,42252,42253],{},"brew cask install intellij-idea",[5332,42255,42256],{},"brew cask install eclipse-java",[5332,42258,42259],{},"brew cask install postman",[5332,42261,42262],{},"brew cask install docker",[5332,42264,42265],{},"brew cask install spectacle",[5332,42267,42268],{},"brew install tree",[5332,42270,42271],{},[812,42272,42273],{"href":42273,"rel":42274},"https://github.com/sindresorhus/quick-look-plugins",[816],[5909,42276,41445],{"id":42277},"visual-studio-code",[651,42279,42280],{},"If you aren't using Visual Studio Code, what the heck are you waiting for? All jokes aside I am sure you have probably heard of code by now and here is a few ways that I customize it.",[5259,42282,42284],{"id":42283},"extensions","Extensions",[651,42286,42287],{},"I used to hate having to install Visual Studio Code on a new machine. The reason for that is I have grown to love the editor so much that I have an extension for everything. I use to install them one by one and when you have to do that 35 times it gets really old.",[651,42289,42290],{},"If you want to get a list of extensions currently installed on your machine you can use the following command.",[669,42292,42294],{"className":5851,"code":42293,"language":5853,"meta":674,"style":674},"code --list-extensions\n",[676,42295,42296],{"__ignoreMap":674},[679,42297,42298,42300],{"class":681,"line":682},[679,42299,676],{"class":880},[679,42301,42302],{"class":931}," --list-extensions\n",[651,42304,42305],{},"The nice thing about that is you can install visual studio code extensions using the command line.",[669,42307,42309],{"className":5851,"code":42308,"language":5853,"meta":674,"style":674},"code --install-extension ${extension-name}\n",[676,42310,42311],{"__ignoreMap":674},[679,42312,42313,42315,42318],{"class":681,"line":682},[679,42314,676],{"class":880},[679,42316,42317],{"class":931}," --install-extension",[679,42319,42320],{"class":693}," ${extension-name}\n",[651,42322,42323,42324,664],{},"You can pipe the results of your list into a file or if you wish you can grab mine from this repository. Once you have that file you can run the following to install all of your extensions. if you're interested in what extensions I am using I have ",[812,42325,42328],{"href":42326,"rel":42327},"https://github.com/cfaddict/new-macbook-setup/blob/master/vscode-extensions.txt",[816],"included my list of extensions",[669,42330,42332],{"className":5851,"code":42331,"language":5853,"meta":674,"style":674},"cat extensions.txt | xargs -L1 code --install-extension\n",[676,42333,42334],{"__ignoreMap":674},[679,42335,42336,42339,42342,42345,42348,42351,42354],{"class":681,"line":682},[679,42337,42338],{"class":880},"cat",[679,42340,42341],{"class":689}," extensions.txt",[679,42343,42344],{"class":685}," |",[679,42346,42347],{"class":880}," xargs",[679,42349,42350],{"class":931}," -L1",[679,42352,42353],{"class":689}," code",[679,42355,42356],{"class":931}," --install-extension\n",[651,42358,42359],{},"After I did this and tweeted about it a lot of people let me know about another extension called sync settings. This will sync all of your visual studio extensions, settings, key bindings, etc across machines.",[651,42361,42362],{},[812,42363,42364],{"href":42364,"rel":42365},"https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync",[816],[5259,42367,42369],{"id":42368},"fonts","Fonts",[651,42371,42372],{},"I am a huge fan of the Dank Mono font. I install this and use it for most of my development. If I am writing documentation I will sometimes use a different font, it just depends.",[651,42374,42375],{},[812,42376,42379],{"href":42377,"rel":42378},"https://dank.sh/",[816],"Dank Mono",[5259,42381,42383],{"id":42382},"user-settings","User Settings",[651,42385,42386],{},"I have included my user settings in case you want to see what I use. I don't have much here but will try and update these soon.",[5909,42388,42390],{"id":42389},"node-npm","Node & NPM",[651,42392,42393,42394,42399],{},"If you're going to install Node I think the best way to do so is by using ",[812,42395,42398],{"href":42396,"rel":42397},"https://github.com/creationix/nvm",[816],"Node Version Manager (NVM)",". This to me has a few advantages over installing it from brew or even directly downloading it from their website.",[5316,42401,42402,42413],{},[5332,42403,42404,42405],{},"You can install multiple versions of Node\n",[5316,42406,42407,42410],{},[5332,42408,42409],{},"You can set a default version",[5332,42411,42412],{},"You can switch between these versions easily",[5332,42414,42415,42416],{},"Installs in your home directory\n",[5316,42417,42418,42421],{},[5332,42419,42420],{},"You don't need special privileges",[5332,42422,42423],{},"No more sudo when installing packages globally",[651,42425,42426],{},"Once you have NVM you can install the latest stable realease (v10.12.0 at the time of this writing) using the following command.",[669,42428,42430],{"className":5851,"code":42429,"language":5853,"meta":674,"style":674},"nvm install stable\n",[676,42431,42432],{"__ignoreMap":674},[679,42433,42434,42437,42439],{"class":681,"line":682},[679,42435,42436],{"class":880},"nvm",[679,42438,24571],{"class":689},[679,42440,42441],{"class":689}," stable\n",[5259,42443,42445],{"id":42444},"global-packages-to-install","Global Packages to Install",[669,42447,42449],{"className":5851,"code":42448,"language":5853,"meta":674,"style":674},"npm install -g tldr\nnpm install -g typescript\nnpm install -g @vue/cli\nnpm install -g vuepress\nnpm install -g @angular/cli\nnpm install -g eslint\nnpm install -g gitbook-cli\nnpm install -g lodash\n",[676,42450,42451,42462,42473,42484,42495,42505,42516,42527],{"__ignoreMap":674},[679,42452,42453,42455,42457,42459],{"class":681,"line":682},[679,42454,24568],{"class":880},[679,42456,24571],{"class":689},[679,42458,24574],{"class":931},[679,42460,42461],{"class":689}," tldr\n",[679,42463,42464,42466,42468,42470],{"class":681,"line":790},[679,42465,24568],{"class":880},[679,42467,24571],{"class":689},[679,42469,24574],{"class":931},[679,42471,42472],{"class":689}," typescript\n",[679,42474,42475,42477,42479,42481],{"class":681,"line":892},[679,42476,24568],{"class":880},[679,42478,24571],{"class":689},[679,42480,24574],{"class":931},[679,42482,42483],{"class":689}," @vue/cli\n",[679,42485,42486,42488,42490,42492],{"class":681,"line":901},[679,42487,24568],{"class":880},[679,42489,24571],{"class":689},[679,42491,24574],{"class":931},[679,42493,42494],{"class":689}," vuepress\n",[679,42496,42497,42499,42501,42503],{"class":681,"line":909},[679,42498,24568],{"class":880},[679,42500,24571],{"class":689},[679,42502,24574],{"class":931},[679,42504,27367],{"class":689},[679,42506,42507,42509,42511,42513],{"class":681,"line":918},[679,42508,24568],{"class":880},[679,42510,24571],{"class":689},[679,42512,24574],{"class":931},[679,42514,42515],{"class":689}," eslint\n",[679,42517,42518,42520,42522,42524],{"class":681,"line":935},[679,42519,24568],{"class":880},[679,42521,24571],{"class":689},[679,42523,24574],{"class":931},[679,42525,42526],{"class":689}," gitbook-cli\n",[679,42528,42529,42531,42533,42535],{"class":681,"line":944},[679,42530,24568],{"class":880},[679,42532,24571],{"class":689},[679,42534,24574],{"class":931},[679,42536,42537],{"class":689}," lodash\n",[5909,42539,42541],{"id":42540},"git-config","Git Config",[651,42543,42544],{},"There is usually a default install of git but we used brew to install the latest earlier. Now that we are on the latest version of git we need to do a little configuration.",[651,42546,42547],{},".gitconfig",[5316,42549,42550,42557,42560],{},[5332,42551,42552,42553,4857],{},"git config --global user.email \"",[812,42554,42556],{"href":42555},"mailto:dan@techelevator.com","dan@techelevator.com",[5332,42558,42559],{},"git config --global user.name \"Dan Vega\"",[5332,42561,42562,42563],{},"Aliases\n",[5316,42564,42565],{},[5332,42566,42567],{},"git config --global alias.add-commit '!git add -A && git commit'",[5909,42569,42571],{"id":42570},"databases","Databases",[5316,42573,42574,42581,42588],{},[5332,42575,42576],{},[812,42577,42580],{"href":42578,"rel":42579},"https://www.postgresql.org/",[816],"PostgreSQL",[5332,42582,42583],{},[812,42584,42587],{"href":42585,"rel":42586},"https://www.mongodb.com/",[816],"Mongodb",[5332,42589,42590],{},[812,42591,42594],{"href":42592,"rel":42593},"https://www.mysql.com/",[816],"MySQL",[5259,42596,42580],{"id":42597},"postgresql",[651,42599,42600],{},"The easiest way to install PostgreSQL is by using HomeBrew.",[669,42602,42604],{"className":5851,"code":42603,"language":5853,"meta":674,"style":674},"brew install postgresql\n",[676,42605,42606],{"__ignoreMap":674},[679,42607,42608,42610,42612],{"class":681,"line":682},[679,42609,42165],{"class":880},[679,42611,24571],{"class":689},[679,42613,42614],{"class":689}," postgresql\n",[651,42616,42617],{},"When this is done installing you can have it start up automatically using the following command.",[669,42619,42621],{"className":5851,"code":42620,"language":5853,"meta":674,"style":674},"brew postgresql-upgrade-database\n",[676,42622,42623],{"__ignoreMap":674},[679,42624,42625,42627],{"class":681,"line":682},[679,42626,42165],{"class":880},[679,42628,42629],{"class":689}," postgresql-upgrade-database\n",[651,42631,42632],{},"I don't need it that often so when I want to run it I can run the following command:",[669,42634,42636],{"className":5851,"code":42635,"language":5853,"meta":674,"style":674},"pg_ctl -D /usr/local/var/postgres start\n",[676,42637,42638],{"__ignoreMap":674},[679,42639,42640,42643,42646,42649],{"class":681,"line":682},[679,42641,42642],{"class":880},"pg_ctl",[679,42644,42645],{"class":931}," -D",[679,42647,42648],{"class":689}," /usr/local/var/postgres",[679,42650,42651],{"class":689}," start\n",[651,42653,42654],{},"Better yet I can add a few of aliases to my bash profile to make this even easier.",[669,42656,42658],{"className":5851,"code":42657,"language":5853,"meta":674,"style":674},"alias start_postgres=\"pg_ctl -D /usr/local/var/postgres start\"\nalias stop_postgres=\"pg_ctl -D /usr/local/var/postgres stop -s -m fast\"\nalias pgup=\"start_postgres\"\nalias pgdown=\"stop_postgres\"\n",[676,42659,42660,42673,42685,42697],{"__ignoreMap":674},[679,42661,42662,42665,42668,42670],{"class":681,"line":682},[679,42663,42664],{"class":685},"alias",[679,42666,42667],{"class":693}," start_postgres",[679,42669,686],{"class":685},[679,42671,42672],{"class":689},"\"pg_ctl -D /usr/local/var/postgres start\"\n",[679,42674,42675,42677,42680,42682],{"class":681,"line":790},[679,42676,42664],{"class":685},[679,42678,42679],{"class":693}," stop_postgres",[679,42681,686],{"class":685},[679,42683,42684],{"class":689},"\"pg_ctl -D /usr/local/var/postgres stop -s -m fast\"\n",[679,42686,42687,42689,42692,42694],{"class":681,"line":892},[679,42688,42664],{"class":685},[679,42690,42691],{"class":693}," pgup",[679,42693,686],{"class":685},[679,42695,42696],{"class":689},"\"start_postgres\"\n",[679,42698,42699,42701,42704,42706],{"class":681,"line":901},[679,42700,42664],{"class":685},[679,42702,42703],{"class":693}," pgdown",[679,42705,686],{"class":685},[679,42707,42708],{"class":689},"\"stop_postgres\"\n",[651,42710,42711],{},"Our students also use DBVisualizer so I like to have that installed as well.",[669,42713,42715],{"className":5851,"code":42714,"language":5853,"meta":674,"style":674},"brew cask install dbvisualizer\n",[676,42716,42717],{"__ignoreMap":674},[679,42718,42719,42721,42723,42725],{"class":681,"line":682},[679,42720,42165],{"class":880},[679,42722,42168],{"class":689},[679,42724,24571],{"class":689},[679,42726,42727],{"class":689}," dbvisualizer\n",[5259,42729,42594],{"id":42730},"mysql",[651,42732,42733],{},"To get started with MySQL run the following command:",[669,42735,42737],{"className":5851,"code":42736,"language":5853,"meta":674,"style":674},"brew install mysql\n",[676,42738,42739],{"__ignoreMap":674},[679,42740,42741,42743,42745],{"class":681,"line":682},[679,42742,42165],{"class":880},[679,42744,24571],{"class":689},[679,42746,42747],{"class":689}," mysql\n",[651,42749,42750],{},"To have MySQL automatically run when you computer starts:",[669,42752,42754],{"className":5851,"code":42753,"language":5853,"meta":674,"style":674},"brew services start mysql\n",[676,42755,42756],{"__ignoreMap":674},[679,42757,42758,42760,42763,42766],{"class":681,"line":682},[679,42759,42165],{"class":880},[679,42761,42762],{"class":689}," services",[679,42764,42765],{"class":689}," start",[679,42767,42747],{"class":689},[651,42769,42770],{},"OR you can start / stop it manually",[669,42772,42774],{"className":5851,"code":42773,"language":5853,"meta":674,"style":674},"mysql.server start\n",[676,42775,42776],{"__ignoreMap":674},[679,42777,42778,42781],{"class":681,"line":682},[679,42779,42780],{"class":880},"mysql.server",[679,42782,42651],{"class":689},[651,42784,42785],{},"To be consistent with our PostgreSQL we can create a few aliases.",[669,42787,42789],{"className":5851,"code":42788,"language":5853,"meta":674,"style":674},"alias start_mysql=\"mysql.server start\"\nalias stop_mysql=\"mysql.server stop\"\nalias mysqlup=\"start_mysql\"\nalias mysqldown=\"stop_mysql\"\n",[676,42790,42791,42803,42815,42827],{"__ignoreMap":674},[679,42792,42793,42795,42798,42800],{"class":681,"line":682},[679,42794,42664],{"class":685},[679,42796,42797],{"class":693}," start_mysql",[679,42799,686],{"class":685},[679,42801,42802],{"class":689},"\"mysql.server start\"\n",[679,42804,42805,42807,42810,42812],{"class":681,"line":790},[679,42806,42664],{"class":685},[679,42808,42809],{"class":693}," stop_mysql",[679,42811,686],{"class":685},[679,42813,42814],{"class":689},"\"mysql.server stop\"\n",[679,42816,42817,42819,42822,42824],{"class":681,"line":892},[679,42818,42664],{"class":685},[679,42820,42821],{"class":693}," mysqlup",[679,42823,686],{"class":685},[679,42825,42826],{"class":689},"\"start_mysql\"\n",[679,42828,42829,42831,42834,42836],{"class":681,"line":901},[679,42830,42664],{"class":685},[679,42832,42833],{"class":693}," mysqldown",[679,42835,686],{"class":685},[679,42837,42838],{"class":689},"\"stop_mysql\"\n",[5909,42840,17720],{"id":42841},"sdkman",[651,42843,42844,42845,42850],{},"This is one of my favorite version managers because I use a lot of the Software Development Kits (SDKs) it manages. If you haven't heard of ",[812,42846,42849],{"href":42847,"rel":42848},"https://sdkman.io/install",[816],"SDKMan check them out here",". This is a list of SDKs I manage using SDKMan.",[5316,42852,42853,42855,42857,42859,42861,42863,42866,42868],{},[5332,42854,36422],{},[5332,42856,18628],{},[5332,42858,7097],{},[5332,42860,18326],{},[5332,42862,38919],{},[5332,42864,42865],{},"Micronaut",[5332,42867,7077],{},[5332,42869,38943],{},[651,42871,42872,42873],{},"Here is a full list of SDKs ",[812,42874,42875],{"href":42875,"rel":42876},"https://sdkman.io/sdks",[816],[651,42878,42879,42881,42882,42886],{},[2939,42880,41972],{}," curl -s \"",[812,42883,42884],{"href":42884,"rel":42885},"https://get.sdkman.io",[816],"\" | bash",[651,42888,42889],{},"If you just type sdk install candidate it will install the latest stable version or you can install a specific version",[669,42891,42893],{"className":5851,"code":42892,"language":5853,"meta":674,"style":674},"sdk install java 8.0.191-oracle\n",[676,42894,42895],{"__ignoreMap":674},[679,42896,42897,42900,42902,42905],{"class":681,"line":682},[679,42898,42899],{"class":880},"sdk",[679,42901,24571],{"class":689},[679,42903,42904],{"class":689}," java",[679,42906,42907],{"class":689}," 8.0.191-oracle\n",[651,42909,42910],{},"If you need to get a list of versions you can ask for it:",[669,42912,42914],{"className":5851,"code":42913,"language":5853,"meta":674,"style":674},"sdk list java\n",[676,42915,42916],{"__ignoreMap":674},[679,42917,42918,42920,42923],{"class":681,"line":682},[679,42919,42899],{"class":880},[679,42921,42922],{"class":689}," list",[679,42924,42925],{"class":689}," java\n",[5909,42927,42929],{"id":42928},"browser-configuration","Browser Configuration",[651,42931,42932],{},"Turn on sync and sign into chrome, this brings all of my bookmarks and extensions. If you have any questions about any of these or why I install them please see the contact me section below.",[651,42934,42935],{},[2939,42936,42284],{},[5316,42938,42939,42942,42944,42947,42950,42953,42956,42959,42962],{},[5332,42940,42941],{},"LastPass",[5332,42943,19087],{},[5332,42945,42946],{},"Color Picker",[5332,42948,42949],{},"LiveReload",[5332,42951,42952],{},"uBlock Origin",[5332,42954,42955],{},"privacy badger",[5332,42957,42958],{},"oneTab",[5332,42960,42961],{},"JSONViewer",[5332,42963,42964],{},"Vue devtools",[4542,42966,42968],{"id":42967},"system-preferences","System Preferences",[651,42970,42971],{},"I have a few things that I customize in preferences:",[651,42973,42974,42977,42979,42980,42982],{},[2939,42975,42976],{},"Trackpad:",[41107,42978],{},"\nSystem Preferences > Trackpad > Scroll & Zoom:",[41107,42981],{},"\nI uncheck scroll direction: Natural (It doesn’t feel natural for me)",[651,42984,42985,42988,42990,42991,42993,42994,42996],{},[2939,42986,42987],{},"Dock:",[41107,42989],{},"\nSystem Preferences > Dock",[41107,42992],{},"\nChange the size to small and turn on magnification",[41107,42995],{},"\nI also remove all icons from the dock that i don't use",[651,42998,42999,43002,43004],{},[2939,43000,43001],{},"Avatar:",[41107,43003],{},"\nSystem Preferences > Users & Groups > Edit Avatar",[651,43006,43007,43010,43012],{},[2939,43008,43009],{},"Theme:",[41107,43011],{},"\nmacOS Mojave introduced light & dark themes. I still stick with the light theme even though I use dark themes in almost every editor or program that I use. If you want to change this later simply go to System Preferences > General > Appearance",[4542,43014,43016],{"id":43015},"finder","Finder",[651,43018,43019],{},"I like to customize Finder so I can get to all of my most used places quickly.",[651,43021,43022],{},[2939,43023,43024],{},"Locations",[5316,43026,43027,43030,43033],{},[5332,43028,43029],{},"Add Macintosh HD to locations so I can always get to the root hard drive",[5332,43031,43032],{},"Home /Users/vega",[5332,43034,43035,43036],{},"screenshots (configure screenshot utility to save here)\n",[5316,43037,43038],{},[5332,43039,43040],{},"open screenshot > options > other location",[651,43042,43043],{},[2939,43044,43045],{},"A few tips in finder",[5316,43047,43048,43051],{},[5332,43049,43050],{},"cmd+shift+h (takes you home)",[5332,43052,43053],{},"cmd . (show hidden files and folders)",[4542,43055,43057],{"id":43056},"work-setup","Work Setup",[651,43059,43060],{},"These are a list of applications and configuration I need for work. You may not need some of these tools but I thought I would share them here anyways.",[651,43062,43063,43064,43066,43067,43069,43070,43072],{},"Last Pass",[41107,43065],{},"\nSlack",[41107,43068],{},"\nTwitter (App Store)",[41107,43071],{},"\nScreenflow",[5909,43074,43076],{"id":43075},"adobe-creative-suite","Adobe Creative Suite",[651,43078,43079],{},"I am huge fan of Adobe and all of their products!",[5316,43081,43082,43085,43088,43091,43094,43097],{},[5332,43083,43084],{},"Photoshop CC",[5332,43086,43087],{},"Illustrator CC",[5332,43089,43090],{},"Premiere Pro CC",[5332,43092,43093],{},"After Effects CC",[5332,43095,43096],{},"Premiere Rush CC",[5332,43098,43099],{},"XD",[5909,43101,43103],{"id":43102},"windows-10","Windows 10",[651,43105,43106],{},"At work I need to dual boot with macOS and Windows 10. We have a .NET track where the students use Windows (Visual Studio, SQL Server, etc…) and I need to be able to support them. The great thing about this is Apple has made this stupid simple using BootCamp Assistant. If you want to read more about this check out the document below.",[651,43108,43109],{},[812,43110,43111],{"href":43111,"rel":43112},"https://github.com/cfaddict/new-macbook-setup/blob/master/windws10.md",[816],[4542,43114,9042],{"id":9041},[651,43116,43117],{},"I had a lot of fun putting this together and I hope you picked up a new trick or 2. Whenever I find something new or a way to improve the installation process I will update the link below if you would like to follow along. Please let me know if I missed something that I should look into or if you have any tips and tricks.",[651,43119,43120],{},[812,43121,43122],{"href":43122,"rel":43123},"https://github.com/cfaddict/new-macbook-setup",[816],[786,43125,43126],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}",{"title":674,"searchDepth":790,"depth":790,"links":43128},[43129,43130,43133,43136,43140,43148,43149,43150,43154],{"id":41864,"depth":790,"text":41865},{"id":41882,"depth":790,"text":41883,"children":43131},[43132],{"id":41911,"depth":892,"text":41912},{"id":41932,"depth":790,"text":39958,"children":43134},[43135],{"id":41960,"depth":892,"text":41940},{"id":42014,"depth":790,"text":42015,"children":43137},[43138,43139],{"id":42115,"depth":892,"text":42116},{"id":42143,"depth":892,"text":42144},{"id":42223,"depth":790,"text":42224,"children":43141},[43142,43143,43144,43145,43146,43147],{"id":42277,"depth":892,"text":41445},{"id":42389,"depth":892,"text":42390},{"id":42540,"depth":892,"text":42541},{"id":42570,"depth":892,"text":42571},{"id":42841,"depth":892,"text":17720},{"id":42928,"depth":892,"text":42929},{"id":42967,"depth":790,"text":42968},{"id":43015,"depth":790,"text":43016},{"id":43056,"depth":790,"text":43057,"children":43151},[43152,43153],{"id":43075,"depth":892,"text":43076},{"id":43102,"depth":892,"text":43103},{"id":9041,"depth":790,"text":9042},{"slug":43156,"published":797,"date":43157,"tags":43158,"cover":43161},"macbook-pro-setup-my-setup-with-detailed-instructions","2018-12-21T14:17:22-05:00",[43159,43160],"mac","web development","./tobias-lystad-606045-unsplash-1024x683.jpg",{"title":549,"description":549},"blog/2018/12/21/macbook-pro-setup-my-setup-with-detailed-instructions","3uBaIODvuTrj6K9y3fpWgZ5ngHzrUiFffSaJcvJV6Ts",{"id":43166,"title":546,"body":43167,"description":546,"extension":793,"meta":43373,"navigation":797,"path":547,"seo":43377,"stem":43378,"__hash__":43379},"content/blog/2018/12/24/my-2018-year-in-review.md",{"type":648,"value":43168,"toc":43354},[43169,43172,43175,43180,43183,43192,43197,43200,43204,43207,43234,43238,43241,43245,43248,43252,43256,43259,43263,43265,43268,43270,43273,43277,43279,43282,43284,43287,43291,43293,43302,43306,43315,43319,43323,43326,43330,43333,43337,43341,43344,43349,43351],[651,43170,43171],{},"It is that time again friends. Time to find out what goals I fell short of and which ones I made good on. This is my 2018 year in review and in a couple weeks I will posting my goals for 2019.",[651,43173,43174],{},"This year was life-changing as my wife and I welcomed our first child, Isabella Margaret Vega into the world. I never knew I could love anything as much as I love this little girl and she was completely changed my life, for the better.",[651,43176,43177],{},[660,43178],{"alt":674,"src":43179},"./UNADJUSTEDNONRAW_thumb_1e55-768x1024.jpg",[651,43181,43182],{},"I think I could pretty much end the post there. I knew having a child would be life-changing but I didn't understand just how much life would change. I use to have so much time to work on a project or learning a new skill but that for the most part has gone out the window.",[651,43184,43185,43186,43191],{},"I also ",[812,43187,43190],{"href":43188,"rel":43189},"https://therealdanvega.com/blog/2018/09/07/i-am-joining-tech-elevator",[816],"joined Tech Elevator"," this year and looking back on it that was certainly the right decision for me. I love the people that I work with, the mission that we have and seeing our students transform their lives in just 14 weeks.",[651,43193,43194],{},[660,43195],{"alt":674,"src":43196},"./0-1024x995.jpeg",[651,43198,43199],{},"I am going to revisit each of the goals I had last year and let you know whether I completed them or not. If you didn't already guess I fell short on a lot of them but I am glad I did because some of them are no longer goals I want to pursue.",[4542,43201,43203],{"id":43202},"personal-goals","Personal Goals",[651,43205,43206],{},"For the most part, I met my personal goals. I didn't drink a single ounce of soda in 2108 and I am pretty proud of that one. The only one I didn't complete here was running 20 miles per week. That was probably pretty ambitious considereing I spend 3-4 days a week in the gym.",[5316,43208,43209,43211,43213,43215,43227,43229,43232],{},[5332,43210,39191],{},[5332,43212,39194],{},[5332,43214,39197],{},[5332,43216,39200,43217],{},[5316,43218,43219,43221,43223,43225],{},[5332,43220,39205],{},[5332,43222,39211],{},[5332,43224,39214],{},[5332,43226,39208],{},[5332,43228,39217],{},[5332,43230,43231],{},"Complete the nursery (Baby on the way) ",[5332,43233,39223],{},[4542,43235,43237],{"id":43236},"education-goals","Education Goals",[651,43239,43240],{},"These were a list of things that I wanted to learn more about. To be fair this list is very long but these are what I narrowed it down to in 2018 and looking back on it I made some bad choices here.",[5909,43242,43244],{"id":43243},"building-an-ios-app","Building an iOS app",[651,43246,43247],{},"I am not sure why but I have always wanted to build a mobile application and more specifically one for iOS. I have done some coding challenges in Swift and I love the programming language. I haven't however had a good idea for a practical application that I want to build. Until I do so I think this is going to sit on the shelf.",[651,43249,43250],{},[660,43251],{"alt":39254,"src":39255},[5909,43253,43255],{"id":43254},"cryptocurrency-blockchain","Cryptocurrency & Blockchain",[651,43257,43258],{},"In one of my best moves of 2018 I didn't purchase any bitcoin which turned out to be a huge win. With that said I am still a big believer in the blockchain and want to learn more. Just like the iOS skill though I haven't found a practical need to learn anything in this space so for now it's probably going to be down on my list.",[651,43260,43261],{},[660,43262],{"alt":674,"src":39271},[5909,43264,39275],{"id":39274},[651,43266,43267],{},"1 year later and I love the 2 Echo's that we have in our house. I mainly use them to play music, books or podcasts in different rooms but there are also a few skills that I am using. This is another instance of me wanting to learn something just to learn something with no practical reason to learn it. This will go on the backlog but stay pretty hi.",[5909,43269,39289],{"id":39288},[651,43271,43272],{},"This is something I did spend some time learning at the beginning of the year. I learned a few things about ads, analytics and using different sales funnels. As my time to work on this sort of thing became less and less I started to focus on creating new content and not worrying about the marketing sides of things. I wouldn't call this one a pass but I also wouldn't say its a failure.",[651,43274,43275],{},[660,43276],{"alt":39301,"src":39302},[5909,43278,39357],{"id":39356},[651,43280,43281],{},"This is one of those technologies that I think is going to continue to get more interesting. I didn't do anything this year in this this space and sadly this is going to go on the backburner.",[4542,43283,6016],{"id":39340},[651,43285,43286],{},"I set a pretty lofty goal of reading 30 books and I missed that by a lot. I will blame missing this goal on the baby but I probably could have found a way to read more than 5 books this year.",[651,43288,43289],{},[660,43290],{"alt":674,"src":39353},[4542,43292,39381],{"id":39380},[651,43294,43295,43296,43301],{},"My goal was to release 4 new courses this year and that didn't happen. I did, however, release one new course, ",[812,43297,43300],{"href":43298,"rel":43299},"https://therealdanvega.com/blog/2018/11/20/new-course-getting-started-with-spring-boot-2",[816],"Getting Started with Spring Boot 2."," This was a course I spent a lot of time working on and it ended up being a course with over 13 hours of content so you could say I created 4 courses and wrapped them up into 1. My main focus with my time is going to be spent creating new content but going into 2019 I need to have a better approach. Creating 1 course a year is not going to do much for me.",[5909,43303,43305],{"id":43304},"video-production","Video Production",[651,43307,43308,43309,43314],{},"One of my biggest goals for the year was to get better at video production. I am going to call this goal completed. Now, I am no rockstar but I got much better this year at using Adobe Premiere Pro & After Effects. ",[812,43310,43313],{"href":43311,"rel":43312},"https://www.youtube.com/watch?v=cz_7KhbfaNE&t=1s",[816],"Here is the trailer"," I created for my Spring Boot 2 course.",[651,43316,43317],{},[660,43318],{"alt":39451,"src":39452},[4542,43320,43322],{"id":43321},"podcasting","Podcasting",[651,43324,43325],{},"I love podcasts. They are one of the tools that I use to learn and advance my skill set. It is that love of learning that has always made me curious about creating a podcast. I don't know what my podcast would be about except that it would be somewhere in the Software Development space. I had no time to pursue this goal this year but it's going towards the top of the list for something I want to explore further in 2019.",[4542,43327,43329],{"id":43328},"business-ventures","Business Ventures",[651,43331,43332],{},"Last year I announced that I started a new company called CodeMonkey, LLC. This was mainly to organize what I was doing with content creation. I did have some ideas around this but the theme of this year continues and I didn't get to any of them. I did reach to the person that owns Code Monkey dot com and he said he would sell me the domain for 100k so he could buy a Porsche. So there's that.",[651,43334,43335],{},[660,43336],{"alt":39471,"src":39478},[5909,43338,43340],{"id":43339},"toys-for-shots-2018","Toys for Shots 2018",[651,43342,43343],{},"This was another successful year that took up a lot of my free time after September. We ended up having a little over 500 people attend the event and more importantly, we raised 20+ boxes of toys. This provided almost $15,000 worth of toys for children right here in Northeast Ohio. What we accompolish in 1 night is so amazing and I am so thankful for everyone involved.",[651,43345,43346],{},[660,43347],{"alt":674,"src":43348},"./48373962_1086875221489929_4036450573048872960_o-1024x819.jpg",[4542,43350,9042],{"id":9041},[651,43352,43353],{},"From a goal setting standpoint this year was a pretty big failure. I think it's more of a failure on my part on being a little bit smarter about the goals that I set. With that I can't wait to share with you what my plans are for 2019. From a personal standpoint I could care less about failing on some of these goals because I have spent the bulk of my year raising the most amazing daughter ever.",{"title":674,"searchDepth":790,"depth":790,"links":43355},[43356,43357,43364,43365,43368,43369,43372],{"id":43202,"depth":790,"text":43203},{"id":43236,"depth":790,"text":43237,"children":43358},[43359,43360,43361,43362,43363],{"id":43243,"depth":892,"text":43244},{"id":43254,"depth":892,"text":43255},{"id":39274,"depth":892,"text":39275},{"id":39288,"depth":892,"text":39289},{"id":39356,"depth":892,"text":39357},{"id":39340,"depth":790,"text":6016},{"id":39380,"depth":790,"text":39381,"children":43366},[43367],{"id":43304,"depth":892,"text":43305},{"id":43321,"depth":790,"text":43322},{"id":43328,"depth":790,"text":43329,"children":43370},[43371],{"id":43339,"depth":892,"text":43340},{"id":9041,"depth":790,"text":9042},{"slug":43374,"published":797,"date":43375,"tags":43376,"cover":39568},"my-2018-year-in-review","2018-12-24T08:52:12-05:00",[39139],{"title":546,"description":546},"blog/2018/12/24/my-2018-year-in-review","M_sQiNTAFOvxOTJN4TY0u_b1TaMyqPRtPPaMSmsDCDA",{"id":43381,"title":543,"body":43382,"description":543,"extension":793,"meta":43700,"navigation":797,"path":544,"seo":43705,"stem":43706,"__hash__":43707},"content/blog/2019/01/01/happy-new-year-my-2019-goals.md",{"type":648,"value":43383,"toc":43685},[43384,43393,43396,43398,43401,43423,43427,43430,43447,43451,43454,43456,43459,43463,43465,43468,43554,43558,43562,43565,43569,43583,43592,43595,43599,43607,43611,43614,43617,43621,43624,43629,43644,43648,43651,43655,43658,43663,43667,43676,43680,43682],[651,43385,43386,43387,43392],{},"It is hard to believe that another year has come and gone. After I spent some time last week ",[812,43388,43391],{"href":43389,"rel":43390},"https://therealdanvega.com/blog/2018/12/24/my-2018-year-in-review",[816],"reflecting on 2018"," it's time we look ahead to the new year and set some new goals.",[651,43394,43395],{},"If you read my last post you know that my free time to work on side projects and learn new things is minimal these days. With that in mind, my goals have to be smart and planned out this year. These are my goals for 2019 in no specific order.",[4542,43397,43203],{"id":43202},[651,43399,43400],{},"The best thing I did in 2018 was giving up soda. I was drinking too much Diet Coke in 2017 and I decided to completely give it up this. I am taking this into 2019 and absolutely no soda is for the entire year. At this point health is very important to me now and going forward so its going to be the majority of my personal goals.",[5316,43402,43403,43406,43417,43420],{},[5332,43404,43405],{},"No Diet Coke or soda of any kind",[5332,43407,43408,43409],{},"Exercise 3-5x a week.\n",[5316,43410,43411,43414],{},[5332,43412,43413],{},"Minimum of 12x per month",[5332,43415,43416],{},"Run 10 miles a week when possible (I live in Cleveland)",[5332,43418,43419],{},"Be a good person & be a great role model for my daughter.",[5332,43421,43422],{},"Get back to journaling every morning.",[5909,43424,43426],{"id":43425},"household-projects","Household Projects",[651,43428,43429],{},"There are a lot of projects around the house that need to get done but these are the projects that need my attention in 2019.",[5316,43431,43432,43435,43438,43441,43444],{},[5332,43433,43434],{},"Baby proof the entire house",[5332,43436,43437],{},"New carpet upstairs and downstairs",[5332,43439,43440],{},"New bathtub in the main bathroom.",[5332,43442,43443],{},"Update the garage, add storage and update gym.",[5332,43445,43446],{},"Continue with updates to the office",[4542,43448,43450],{"id":43449},"learning-goals","Learning Goals",[651,43452,43453],{},"I say this every year but I am a serial learner. I LOVE to learn new things and then tell all of you about what I just learned. WIth that most of my goals this year revolve around investing in myself and continuing to learn.",[5909,43455,6016],{"id":39340},[651,43457,43458],{},"Last year I had a goal of 30 books to read and while I got out to a hot start it came to a complete stop once my daughter was born. This year I am going to dial back the goal to an easy 8 books. I already have 4 that I want to read to start the year off so I will start with those and move forward.",[651,43460,43461],{},[660,43462],{"alt":674,"src":39353},[5909,43464,26378],{"id":39439},[651,43466,43467],{},"I absolutely love listening to podcasts on my way to work, at work, going home from work and If I can get back to the gym with a treadmill I will use it there as well. There is just something about being entertained and learning something new at the same time. With that these are some of my favorite podcasts but certainly not my entire list. If you have any suggestions for podcasts I would love to hear them below.",[5316,43469,43470,43477,43484,43491,43498,43505,43512,43519,43526,43533,43540,43547],{},[5332,43471,43472],{},[812,43473,43476],{"href":43474,"rel":43475},"https://www.indiehackers.com/",[816],"Indie Hackers",[5332,43478,43479],{},[812,43480,43483],{"href":43481,"rel":43482},"https://syntax.fm/",[816],"Syntax",[5332,43485,43486],{},[812,43487,43490],{"href":43488,"rel":43489},"https://www.hanselminutes.com/",[816],"Hansel Minutes",[5332,43492,43493],{},[812,43494,43497],{"href":43495,"rel":43496},"https://softwareengineeringdaily.com/",[816],"Software Engineering Dialy",[5332,43499,43500],{},[812,43501,43504],{"href":43502,"rel":43503},"https://changelog.com/podcast",[816],"The Changelog",[5332,43506,43507],{},[812,43508,43511],{"href":43509,"rel":43510},"https://changelog.com/jsparty",[816],"JS Party",[5332,43513,43514],{},[812,43515,43518],{"href":43516,"rel":43517},"http://www.fullstackradio.com/",[816],"Full Stack Radio",[5332,43520,43521],{},[812,43522,43525],{"href":43523,"rel":43524},"https://news.vuejs.org/",[816],"The Official Vue News",[5332,43527,43528],{},[812,43529,43532],{"href":43530,"rel":43531},"https://devchat.tv/views-on-vue/",[816],"Views on Vue",[5332,43534,43535],{},[812,43536,43539],{"href":43537,"rel":43538},"https://www.codenewbie.org/podcast",[816],"Code Newbie",[5332,43541,43542],{},[812,43543,43546],{"href":43544,"rel":43545},"https://techguylabs.com/",[816],"The Tech Guy with Leo Laporte",[5332,43548,43549],{},[812,43550,43553],{"href":43551,"rel":43552},"https://tim.blog/podcast/",[816],"The Tim Ferris Show",[651,43555,43556],{},[660,43557],{"alt":39451,"src":39452},[5909,43559,43561],{"id":43560},"things-to-learn","Things to Learn",[651,43563,43564],{},"As always my \"stuff to learn\" list is a mile long and instead of getting crazy ambitious here I am going to narrow it down to 3 things I want to learn in 2019.",[5259,43566,43568],{"id":43567},"vuejs","VueJS",[651,43570,43571,43572,43576,43577,43582],{},"I already have a solid foundation in Vue but I will be doubling down on Vue in 2019. We are teaching Vue this year at work to our ",[812,43573,43575],{"href":41135,"rel":43574},[816],"bootcamp students"," and I am running a ",[812,43578,43581],{"href":43579,"rel":43580},"https://www.meetup.com/vuecle/",[816],"local meetup group called VueCLE",". This will involve diving deep into everything Vue related and becoming an expert at things I haven't learned yet like Nuxt.js.",[651,43584,43585],{},[812,43586,43589],{"href":43587,"rel":43588},"https://vuejs.org/",[816],[660,43590],{"alt":43568,"src":43591},"./2018-12-29_06-31-02.png",[651,43593,43594],{},"I don't think I have ever been as excited about anything as a programmer as I am about where JavaScript is heading and a framework like Vue.",[5259,43596,43598],{"id":43597},"live-video","Live Video",[651,43600,43601,43602,664],{},"My life's work is centered around delivering content. Live video is becoming more and more popular and the tools available to make this easy to use and available to everyone is just getting better and better. I want to start creating some live streams on platforms like YouTube, Facebook & Twitch. There is some awesome software for helping you do this on the Mac called ",[812,43603,43606],{"href":43604,"rel":43605},"https://www.ecamm.com/mac/ecammlive/",[816],"ecamm live",[651,43608,43609],{},[660,43610],{"alt":674,"src":25821},[651,43612,43613],{},"SAMSUNG CSC",[651,43615,43616],{},"I need to learn how to use that software, the different platforms and define what my live sessions will look like and how often to go live. This is going to be a fun process so look for more of this in 2019.",[5259,43618,43620],{"id":43619},"the-cloud","The Cloud",[651,43622,43623],{},"I realize this last one is kind of vague but it needs to be. I don't know a whole lot about DevOps or the cloud so I don't know what I need to know. What I do know is that I like to build things and when I build things I need a way to get them out to the public for everyone to see. I am fascinated with all things AWS and just the amount of services they provide.",[651,43625,43626],{},[660,43627],{"alt":674,"src":43628},"./aws-logo.png",[651,43630,43631,43632,43637,43638,43643],{},"I was recently introduced to ",[812,43633,43636],{"href":43634,"rel":43635},"http://www.acloud.guru",[816],"A Cloud Guru"," and I will be going through some of their courses to get caught up on all things \"cloud\". I was also really inspired by ",[812,43639,43642],{"href":43640,"rel":43641},"https://acloud.guru/series/serverlessconf-sf-2018/view/6c7c00f8-3183-db34-83fc-f8931e070da5",[816],"a talk given from the founder and CEO of A Cloud Guru"," on how they built their entire platform on serverless.",[4542,43645,43647],{"id":43646},"proffessional-goals","Proffessional Goals",[651,43649,43650],{},"At Tech Elevator we have some exciting things happening this year that I can't talk about just yet so I am going to leave you in suspense but I hope to talk more about these as the year goes on.",[5909,43652,43654],{"id":43653},"content-creation","Content Creation",[651,43656,43657],{},"My main focus at my day job and outside of work is content creation. Outside of work I really need to define what that looks like. I have a blog, YouTube Channel, Courses and I feel like I am all over the place with this. I'm not quite sure blogging makes much sense for me anymore except maybe to talk about a video I posted or a course I created. I just don't know, I have a lot of questions here that I need to answer. When I figure out what this looks like I will share it with you.",[651,43659,43660],{},[660,43661],{"alt":15432,"src":43662},"./pexels-photo-1024x682.jpg",[5909,43664,43666],{"id":43665},"side-hustle","Side Hustle",[651,43668,43669,43670,43675],{},"Last year I started a company called ",[812,43671,43674],{"href":43672,"rel":43673},"https://codemonkeyu.com/",[816],"CodeMonkey LLC"," just so I could separate what I was making teaching online from my personal income. I haven't done anything with this yet and I am not sure that I will. I did reach out the person who owns the dot com domain and he offered to sell it to me for 100k. So I guess you could say I got that going for me.",[651,43677,43678],{},[660,43679],{"alt":39471,"src":39478},[4542,43681,9042],{"id":9041},[651,43683,43684],{},"I just want to wish all of you a happy New Year! I wish all of you and your families continued health and happiness. 2019 looks to be an exciting year for me personally and professionally and I am looking forward to it.",{"title":674,"searchDepth":790,"depth":790,"links":43686},[43687,43690,43695,43699],{"id":43202,"depth":790,"text":43203,"children":43688},[43689],{"id":43425,"depth":892,"text":43426},{"id":43449,"depth":790,"text":43450,"children":43691},[43692,43693,43694],{"id":39340,"depth":892,"text":6016},{"id":39439,"depth":892,"text":26378},{"id":43560,"depth":892,"text":43561},{"id":43646,"depth":790,"text":43647,"children":43696},[43697,43698],{"id":43653,"depth":892,"text":43654},{"id":43665,"depth":892,"text":43666},{"id":9041,"depth":790,"text":9042},{"slug":43701,"published":797,"date":43702,"tags":43703,"cover":43704},"happy-new-year-my-2019-goals","2019-01-01T08:38:10-05:00",[39139],"./2019-cover.png",{"title":543,"description":543},"blog/2019/01/01/happy-new-year-my-2019-goals","XLMTXwCLeJaGxZgIwFLtH_YKKn6PptRynpS7aNjMZK0",{"id":43709,"title":540,"body":43710,"description":44164,"extension":793,"meta":44165,"navigation":797,"path":541,"seo":44171,"stem":44172,"__hash__":44173},"content/blog/2019/01/31/hello-gridsome.md",{"type":648,"value":43711,"toc":44149},[43712,43715,43726,43729,43738,43741,43754,43757,43760,43764,43773,43776,43779,43788,43791,43794,43798,43814,43839,43842,43845,43849,43856,43860,43868,43872,43875,43880,43884,43890,44056,44077,44081,44090,44094,44097,44100,44132,44134,44141,44146],[651,43713,43714],{},"I would like to welcome you to a new side project of mine. I thought I would take this opportunity to tell you about the motivation behind creating this website and the technologies that power it.",[651,43716,43717],{},[7300,43718,43719,43720,43725],{},"TLDR; I want to move my ",[812,43721,43724],{"href":43722,"rel":43723},"https://www.therealdanvega.com",[816],"blog"," from WordPress to Gridsome.",[4542,43727,43728],{"id":19164},"Blogging",[651,43730,43731,43732,43737],{},"While writing this article I did a bit of digging through my archives and found out that my ",[812,43733,43736],{"href":43734,"rel":43735},"https://therealdanvega.com/blog/2005/10/07/why-you-should-now-about-sifr",[816],"first blog post"," was written October 7, 2005. That means I will be celebrating 14 years of blogging later this year which on one hand is pretty amazing and on the other makes me feel really old.",[5909,43739,43740],{"id":19020},"Why I started blogging",[651,43742,43743,43744,43748,43749,43753],{},"I started blogging because I thought it would be a great chance for me to learn about how open source software worked. At the time I was a ColdFusion developer and ",[812,43745,19028],{"href":43746,"rel":43747},"https://github.com/teamcfadvance/BlogCFC5",[816]," was a popular open source blogging platform written by ",[812,43750,43752],{"href":19034,"rel":43751},[816],"Raymond Camden",". I learned so much by looking at how someone else wrote code and structured a project and I will always be thankful for that experience.",[651,43755,43756],{},"Another reason I started blogging was to get involved in the community. There were a lot of really great developers that would often blog about what they working on and I really looked up to them. I wanted to be one of the cool kids and tell others what I was working on.",[651,43758,43759],{},"Finally, I thought this was a great chance for me to help others. In the early days, I often found myself struggling with projects that I was working on and thought to myself someone else must be experiencing the same issues as me. This was a great opportunity for me to take what I was learning and share it with other developers so that they didn't make the same mistakes that I did.",[5909,43761,43763],{"id":43762},"moving-to-wordpress","Moving to WordPress",[651,43765,43766,43767,43772],{},"In 2014 I decided to ",[812,43768,43771],{"href":43769,"rel":43770},"https://therealdanvega.com/blog/2014/11/25/welcome-new-home",[816],"move my blog"," over to WordPress. It had been a while since I was using ColdFusion and I just didn't want to maintain it any longer. I also thought that this was a good chance to give the blog a fresh look. The migration to WordPress went pretty smooth thanks in part to open source. There were some migration scripts floating around that really helped me through that process.",[651,43774,43775],{},"While the WordPress platform has served me well over the years I have had my problems with it. First I originally was running this site on a Digital Ocean droplet for $5 a month which was great because at the time it wasn't really generating any revenue.",[651,43777,43778],{},"I quickly found out that WordPress doesn't perform well on that plan because it was a huge resource hog. It would go down often and require me to login and restart the server. After upgrading to the $20/month plan a lot of those problems went away.",[651,43780,43781,43782,43787],{},"There was still the problem of me having to manage a server. I am not a server admin and really didn't enjoy everything that went along with that. I ended up getting really lucky and finding someone on Upwork to manage the server. His name is Justin and he was able to fix my server when it when down as well as make sure the server was up to date with security patches. He runs a hosting company called ",[812,43783,43786],{"href":43784,"rel":43785},"https://bigscoots.com/",[816],"Big Scoots"," which is now where my website is hosted. I can't recommend him or his company enough if you're looking for a WordPress host. I have had 0 problems since moving to them.",[651,43789,43790],{},"The other main issue I have with WordPress is that I am not a PHP developer nor do I care to be. I have been able to get in there and mess around with the layouts and templates but that is about as far as I have gone. I also don't have a local WordPress installation so working on theme updates isn't easy for me.",[651,43792,43793],{},"While I have had some issues I do think WordPress is an amazing product. It's just no longer the product for me.",[5909,43795,43797],{"id":43796},"new-solution-requirements","New Solution Requirements",[651,43799,43800,43801,43804,43805,43807,43808,43810,43811,43813],{},"Now that you know my story and where I have been we can talk about what I am looking for. I have become a huge fan of the ",[2939,43802,43803],{},"JAMStack"," which stands for ",[2939,43806,31941],{},"avaScript + ",[2939,43809,7704],{},"PI + ",[2939,43812,7740],{},"arkup. The idea behind this is pretty simple, serve up a static site for everything that you can and then reach out to an API for any data or functionality that your site needs. This is a big shift from the traditional server-side monolithic applications of the past and one I am very excited about.",[651,43815,43816,43817,43822,43823,43828,43829,43834,43835,43838],{},"Static Site Generators (SSGs) like ",[812,43818,43821],{"href":43819,"rel":43820},"https://www.gatsbyjs.org/",[816],"Gatsby"," have become really popular and they have their advantages. First, we get to serve a static site to our users which means it is going to be extremely fast. My visitors are coming to my site to check out content, not some loading bar. The other big advantage is the options we have when it comes to hosting our static sites. If you want to host it on something like ",[812,43824,43827],{"href":43825,"rel":43826},"https://aws.amazon.com/free/free-tier/",[816],"Amazon Web Services (AWS)"," it can be very inexpensive. There is also one of my favorite services around ",[812,43830,43833],{"href":43831,"rel":43832},"https://www.netlify.com/",[816],"Netlify",", which is ",[2939,43836,43837],{},"FREE"," for personal projects.",[651,43840,43841],{},"Another advantage of this approach is that I am not tied to some server-side language. I get to build my layouts and views in good old HTML/CSS/JavaScript. Another feature I am looking for is the ability to write all of my blog posts in Markdown. I write a ton of documentation for work and in my personal projects so I have become very comfortable writing in this format.",[651,43843,43844],{},"I had a bunch of options when building out this new site. I can't wait to tell you about what I found but for now, I am going to just talk about the one ended up using.",[4542,43846,43848],{"id":43847},"gridsome","Gridsome",[651,43850,43851,43852,664],{},"I mentioned Gatsby earlier and while this was definitely a possible solution for me I wanted something that was Vue based. I really love Vue, the ecosystem and the community so I wanted a solution that would keep me there. After looking around and creating a few projects i decided on ",[812,43853,43848],{"href":43854,"rel":43855},"https://gridsome.org/",[816],[5909,43857,43859],{"id":43858},"what-is-gridsome","What is Gridsome",[651,43861,43862,43863,43867],{},"Gridsome is a static site generator similar to Gatsby (though still new so not as feature rich) for the ",[812,43864,43866],{"href":43587,"rel":43865},[816],"Vue"," framework. Gridsome has a ton of features but here are just a few. What this means for me is I get to write Vue applications that can then generate a static website. Now I know what you're thinking, Vue can do that on its own and you're right. Let's take a look at a few more features of Gridsome.",[5909,43869,43871],{"id":43870},"how-gridsome-works","How Gridsome Works",[651,43873,43874],{},"If you were just going to have a plain static site you wouldn't really need something like Gridsome. Where Gridsome really shines is its ability to use multiple data source and combine them into a single GraphQL data layer. This means that you can work with local files like Markdown as well headless CMS's like WordPress, Contentful and so on. If you're new to GraphQL don't worry I was too and it's pretty easy to pickup.",[651,43876,43877],{},[660,43878],{"alt":43871,"src":43879},"./how-gridsome-works.png",[5909,43881,43883],{"id":43882},"markdown","Markdown",[651,43885,43886,43887],{},"As I mentioned one of those data sources is Markdown which made me very happy because I can start writing in a familiar environment. I am going to go over this in detail in another post but the quick version is that this was really easy to set up. You start with some configuration to use the plugin ",[676,43888,43889],{},"@gridsome/source-filesystem",[669,43891,43893],{"className":25132,"code":43892,"language":25134,"meta":674,"style":674},"module.exports = {\n plugins: [\n {\n use: \"@gridsome/source-filesystem\",\n options: {\n path: \"blog/**/*.md\",\n typeName: \"Post\",\n route: \"/blog/:slug\",\n resolveAbsolutePaths: true,\n remark: {\n autolinkClassName: \"fas fa-hashtag\",\n externalLinksTarget: \"_blank\",\n externalLinksRel: [\"nofollow\", \"noopener\", \"noreferrer\"],\n plugins: [[\"gridsome-plugin-remark-shiki\", { theme: \"nord\" }]]\n }\n }\n }\n ]\n};\n",[676,43894,43895,43908,43913,43917,43927,43932,43942,43952,43962,43971,43976,43986,43996,44017,44034,44038,44042,44046,44051],{"__ignoreMap":674},[679,43896,43897,43899,43901,43904,43906],{"class":681,"line":682},[679,43898,27515],{"class":931},[679,43900,664],{"class":693},[679,43902,43903],{"class":931},"exports",[679,43905,6883],{"class":685},[679,43907,884],{"class":693},[679,43909,43910],{"class":681,"line":790},[679,43911,43912],{"class":693}," plugins: [\n",[679,43914,43915],{"class":681,"line":892},[679,43916,28496],{"class":693},[679,43918,43919,43922,43925],{"class":681,"line":901},[679,43920,43921],{"class":693}," use: ",[679,43923,43924],{"class":689},"\"@gridsome/source-filesystem\"",[679,43926,12083],{"class":693},[679,43928,43929],{"class":681,"line":909},[679,43930,43931],{"class":693}," options: {\n",[679,43933,43934,43937,43940],{"class":681,"line":918},[679,43935,43936],{"class":693}," path: ",[679,43938,43939],{"class":689},"\"blog/**/*.md\"",[679,43941,12083],{"class":693},[679,43943,43944,43947,43950],{"class":681,"line":935},[679,43945,43946],{"class":693}," typeName: ",[679,43948,43949],{"class":689},"\"Post\"",[679,43951,12083],{"class":693},[679,43953,43954,43957,43960],{"class":681,"line":944},[679,43955,43956],{"class":693}," route: ",[679,43958,43959],{"class":689},"\"/blog/:slug\"",[679,43961,12083],{"class":693},[679,43963,43964,43967,43969],{"class":681,"line":959},[679,43965,43966],{"class":693}," resolveAbsolutePaths: ",[679,43968,3441],{"class":931},[679,43970,12083],{"class":693},[679,43972,43973],{"class":681,"line":964},[679,43974,43975],{"class":693}," remark: {\n",[679,43977,43978,43981,43984],{"class":681,"line":977},[679,43979,43980],{"class":693}," autolinkClassName: ",[679,43982,43983],{"class":689},"\"fas fa-hashtag\"",[679,43985,12083],{"class":693},[679,43987,43988,43991,43994],{"class":681,"line":982},[679,43989,43990],{"class":693}," externalLinksTarget: ",[679,43992,43993],{"class":689},"\"_blank\"",[679,43995,12083],{"class":693},[679,43997,43998,44001,44004,44006,44009,44011,44014],{"class":681,"line":988},[679,43999,44000],{"class":693}," externalLinksRel: [",[679,44002,44003],{"class":689},"\"nofollow\"",[679,44005,2797],{"class":693},[679,44007,44008],{"class":689},"\"noopener\"",[679,44010,2797],{"class":693},[679,44012,44013],{"class":689},"\"noreferrer\"",[679,44015,44016],{"class":693},"],\n",[679,44018,44019,44022,44025,44028,44031],{"class":681,"line":993},[679,44020,44021],{"class":693}," plugins: [[",[679,44023,44024],{"class":689},"\"gridsome-plugin-remark-shiki\"",[679,44026,44027],{"class":693},", { theme: ",[679,44029,44030],{"class":689},"\"nord\"",[679,44032,44033],{"class":693}," }]]\n",[679,44035,44036],{"class":681,"line":2129},[679,44037,1290],{"class":693},[679,44039,44040],{"class":681,"line":2140},[679,44041,11804],{"class":693},[679,44043,44044],{"class":681,"line":2145},[679,44045,985],{"class":693},[679,44047,44048],{"class":681,"line":2154},[679,44049,44050],{"class":693}," ]\n",[679,44052,44053],{"class":681,"line":2159},[679,44054,44055],{"class":693},"};\n",[651,44057,44058,44059,44064,44065,44070,44071,44076],{},"In that plugin, you configure some options like what the path will be and the component that will handle the type. I'm also using the ",[812,44060,44063],{"href":44061,"rel":44062},"https://www.npmjs.com/package/@gridsome/transformer-remark",[816],"Gridsome Transformer Remark"," plugin which is the Markdown transformer for Gridsome. ",[812,44066,44069],{"href":44067,"rel":44068},"https://github.com/remarkjs/remark",[816],"Remark"," can include a number of options and plugins itself one of which is a syntax highlighter called ",[812,44072,44075],{"href":44073,"rel":44074},"https://github.com/EldoranDev/gridsome-plugin-remark-shiki",[816],"shiki"," which is what formatted the code that is right above this.",[5909,44078,44080],{"id":44079},"gridsome-is-awesome","Gridsome is awesome",[651,44082,44083,44084,44089],{},"This was just the start of what Gridsome can do. I have so many things to share so I hope you will sign up for my newsletter or ",[812,44085,44088],{"href":44086,"rel":44087},"http://twitter.com/therealdanvega",[816],"follow me on Twitter"," and learn about all the cool things you can do in Gridsome and in Vue.",[4542,44091,44093],{"id":44092},"should-i-move-my-existing-website","Should I move my existing website?",[651,44095,44096],{},"Now the real question and much harder than a simple yes or no. Should I move my existing website to Gridsome? I am really happy with this website and the workflow I have for adding new content to it.",[651,44098,44099],{},"Moving my close to 1000 blog posts is easier said than done. 1 question that comes into play is should I just get rid of all the posts that I don't think hold much weight anymore? This could slim down the migration process substantially. These are some of the concerns I have to look into before making my decision.",[5316,44101,44102,44105,44108,44111,44114,44123,44126,44129],{},[5332,44103,44104],{},"Existing URLs must keep the same format /month/day/year/slug",[5332,44106,44107],{},"I have lots of images and should probably move those to something like S3",[5332,44109,44110],{},"I have photo galleries on posts, what to do with that.",[5332,44112,44113],{},"I need to make sure existing code blocks work with this new syntax highlighter.",[5332,44115,44116,44117,44122],{},"Should I use ",[812,44118,44121],{"href":44119,"rel":44120},"https://disqus.com/",[816],"Disqus"," comments (free vs paid) or roll my own with Firebase?",[5332,44124,44125],{},"I want to display tweets in a post.",[5332,44127,44128],{},"When I share a post on social media there are some meta tags to customize the display.",[5332,44130,44131],{},"SSL: I need to make sure the entire site runs on https.",[4542,44133,9042],{"id":9041},[651,44135,44136,44137,664],{},"I still have some things to think about but I would love to hear your thoughts on this. If you have migrated from WordPress to Gridsome I would really love to hear what that process was like for you. If you're interested in hearing how I decided on Gridsome or how I created this site, stay tuned! In the meantime, you can check out the source for this site ",[812,44138,18263],{"href":44139,"rel":44140},"https://github.com/cfaddict/danvega-me/",[816],[651,44142,44143,44144,41109],{},"Happy Coding!",[41107,44145],{},[786,44147,44148],{},"html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":44150},[44151,44156,44162,44163],{"id":19164,"depth":790,"text":43728,"children":44152},[44153,44154,44155],{"id":19020,"depth":892,"text":43740},{"id":43762,"depth":892,"text":43763},{"id":43796,"depth":892,"text":43797},{"id":43847,"depth":790,"text":43848,"children":44157},[44158,44159,44160,44161],{"id":43858,"depth":892,"text":43859},{"id":43870,"depth":892,"text":43871},{"id":43882,"depth":892,"text":43883},{"id":44079,"depth":892,"text":44080},{"id":44092,"depth":790,"text":44093},{"id":9041,"depth":790,"text":9042},"A quick write up on why I started a new blog and what I plan to do with it.",{"slug":44166,"date":44167,"published":797,"tags":44168,"cover":44170},"hello-gridsome","2019-01-31 10:00:00",[44169],"vue","./gridsome.png",{"title":540,"description":44164},"blog/2019/01/31/hello-gridsome","cKPheexcMnalqEr_It6jJMDQKTtfnsuKAr8IOAaeP-4",{"id":44175,"title":537,"body":44176,"description":45876,"extension":793,"meta":45877,"navigation":797,"path":538,"seo":45882,"stem":45883,"__hash__":45884},"content/blog/2019/02/10/creating-your-first-npm-package.md",{"type":648,"value":44177,"toc":45863},[44178,44187,44193,44196,44200,44203,44206,44225,44229,44236,44239,44251,44254,44733,44737,44740,44783,44786,44790,44793,44796,44924,44928,44931,44945,44948,44962,44965,45096,45110,45114,45117,45129,45132,45137,45140,45154,45160,45163,45230,45233,45242,45245,45264,45268,45271,45334,45337,45478,45482,45485,45497,45500,45563,45570,45573,45585,45588,45788,45791,45803,45811,45814,45817,45829,45832,45834,45837,45851,45853,45856,45860],[651,44179,44180,44181,44186],{},"This weekend I started working on my first ever npm package. I can't believe for how long I have been writing code that I never bothered to create my own npm package but here we are. I ",[812,44182,44185],{"href":44183,"rel":44184},"https://www.danvega.me/blog/hello-gridsome",[816],"built the site"," you're reading this article on using Gridsome and markdown. In the markdown files I wanted an easy way to insert a twitter status nad embed the tweet. In fact here is a tweet just to prove its working.",[651,44188,44189],{},[812,44190,44191],{"href":44191,"rel":44192},"https://twitter.com/therealdanvega/status/1094219965480292353",[816],[651,44194,44195],{},"I will tell you more about that Gridsome plugin in a future blog post but for now I want to show you how you can create your very first npm package. I learned a few things while working on this and I would like to share them with you.",[4542,44197,44199],{"id":44198},"prerequisites","Prerequisites",[651,44201,44202],{},"I am going to assume you at least know what node & npm is and have written JavaScript before. If you don't know either of these and want me to write something up on getting started with those please let me know.",[651,44204,44205],{},"There are a few things that you're going to need before we dive in and start writing some code.",[5316,44207,44208,44214,44219],{},[5332,44209,44210,44213],{},[812,44211,41445],{"href":41443,"rel":44212},[816]," or your favorite editor",[5332,44215,44216],{},[812,44217,42390],{"href":27316,"rel":44218},[816],[5332,44220,44221],{},[812,44222,44224],{"href":27322,"rel":44223},[816],"NPM Account",[4542,44226,44228],{"id":44227},"creating-your-npm-package","Creating your npm package",[651,44230,44231,44232,44235],{},"The first thing you are going to do is create a new folder to hold your npm package. For this example I am going to create a new directory called ",[2939,44233,44234],{},"wrap-with-poo",". Yes, you read that correctly.",[651,44237,44238],{},"Go into that folder and type the following:",[669,44240,44242],{"className":5851,"code":44241,"language":5853,"meta":674,"style":674},"npm init\n",[676,44243,44244],{"__ignoreMap":674},[679,44245,44246,44248],{"class":681,"line":682},[679,44247,24568],{"class":880},[679,44249,44250],{"class":689}," init\n",[651,44252,44253],{},"This will ask you a bunch of questions and then create a package.json. If you don't know answers to certain questions just yet don't worry, you can come back and answer them later.",[669,44255,44257],{"className":5851,"code":44256,"language":5853,"meta":674,"style":674},"This utility will walk you through creating a package.json file.\nIt only covers the most common items, and tries to guess sensible defaults.\n\nSee `npm help json` for definitive documentation on these fields\nand exactly what they do.\n\nUse `npm install \u003Cpkg>` afterwards to install a package and\nsave it as a dependency in the package.json file.\n\nPress ^C at any time to quit.\npackage name: (wrap-with-poop)\nversion: (1.0.0) 0.0.1\ndescription: This package will take any string you give it and wrap it with the poop emjoi\nentry point: (index.js)\ntest command:\ngit repository:\nkeywords: node,npm\nauthor: Dan Vega\nlicense: (ISC) MIT\nAbout to write to /Users/vega/dev/npm/wrap-with-poop/package.json:\n\n{\n \"name\": \"wrap-with-poop\",\n \"version\": \"0.0.1\",\n \"description\": \"This package will take any string you give it and wrap it with the poop emjoi\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [\n \"node\",\n \"npm\"\n ],\n \"author\": \"Dan Vega\",\n \"license\": \"MIT\"\n}\n\n\nIs this OK? (yes) yes\n",[676,44258,44259,44288,44325,44329,44347,44363,44367,44404,44426,44430,44451,44461,44469,44514,44525,44533,44541,44549,44560,44568,44582,44586,44590,44600,44610,44620,44630,44638,44658,44662,44672,44679,44684,44688,44698,44708,44712,44716,44720],{"__ignoreMap":674},[679,44260,44261,44264,44267,44269,44272,44274,44277,44280,44282,44285],{"class":681,"line":682},[679,44262,44263],{"class":880},"This",[679,44265,44266],{"class":689}," utility",[679,44268,22359],{"class":689},[679,44270,44271],{"class":689}," walk",[679,44273,22337],{"class":689},[679,44275,44276],{"class":689}," through",[679,44278,44279],{"class":689}," creating",[679,44281,21697],{"class":689},[679,44283,44284],{"class":689}," package.json",[679,44286,44287],{"class":689}," file.\n",[679,44289,44290,44293,44296,44299,44301,44304,44306,44309,44311,44314,44316,44319,44322],{"class":681,"line":790},[679,44291,44292],{"class":880},"It",[679,44294,44295],{"class":689}," only",[679,44297,44298],{"class":689}," covers",[679,44300,22351],{"class":689},[679,44302,44303],{"class":689}," most",[679,44305,35115],{"class":689},[679,44307,44308],{"class":689}," items,",[679,44310,22346],{"class":689},[679,44312,44313],{"class":689}," tries",[679,44315,21703],{"class":689},[679,44317,44318],{"class":689}," guess",[679,44320,44321],{"class":689}," sensible",[679,44323,44324],{"class":689}," defaults.\n",[679,44326,44327],{"class":681,"line":892},[679,44328,889],{"emptyLinePlaceholder":797},[679,44330,44331,44334,44337,44339,44342,44344],{"class":681,"line":901},[679,44332,44333],{"class":880},"See",[679,44335,44336],{"class":689}," `",[679,44338,24568],{"class":880},[679,44340,44341],{"class":689}," help json`",[679,44343,6818],{"class":685},[679,44345,44346],{"class":693}," definitive documentation on these fields\n",[679,44348,44349,44351,44354,44357,44360],{"class":681,"line":909},[679,44350,40257],{"class":880},[679,44352,44353],{"class":689}," exactly",[679,44355,44356],{"class":689}," what",[679,44358,44359],{"class":689}," they",[679,44361,44362],{"class":689}," do.\n",[679,44364,44365],{"class":681,"line":918},[679,44366,889],{"emptyLinePlaceholder":797},[679,44368,44369,44372,44374,44376,44379,44381,44384,44386,44389,44392,44394,44396,44398,44401],{"class":681,"line":935},[679,44370,44371],{"class":880},"Use",[679,44373,44336],{"class":689},[679,44375,24568],{"class":880},[679,44377,44378],{"class":689}," install ",[679,44380,4505],{"class":685},[679,44382,44383],{"class":689},"pkg",[679,44385,5860],{"class":685},[679,44387,44388],{"class":689},"`",[679,44390,44391],{"class":880}," afterwards",[679,44393,21703],{"class":689},[679,44395,24571],{"class":689},[679,44397,21697],{"class":689},[679,44399,44400],{"class":689}," package",[679,44402,44403],{"class":689}," and\n",[679,44405,44406,44408,44411,44413,44415,44418,44420,44422,44424],{"class":681,"line":944},[679,44407,7629],{"class":880},[679,44409,44410],{"class":689}," it",[679,44412,24745],{"class":689},[679,44414,21697],{"class":689},[679,44416,44417],{"class":689}," dependency",[679,44419,6997],{"class":689},[679,44421,22351],{"class":689},[679,44423,44284],{"class":689},[679,44425,44287],{"class":689},[679,44427,44428],{"class":681,"line":959},[679,44429,889],{"emptyLinePlaceholder":797},[679,44431,44432,44435,44438,44440,44443,44446,44448],{"class":681,"line":964},[679,44433,44434],{"class":880},"Press",[679,44436,44437],{"class":689}," ^C",[679,44439,36759],{"class":689},[679,44441,44442],{"class":689}," any",[679,44444,44445],{"class":689}," time",[679,44447,21703],{"class":689},[679,44449,44450],{"class":689}," quit.\n",[679,44452,44453,44455,44458],{"class":681,"line":977},[679,44454,2543],{"class":880},[679,44456,44457],{"class":689}," name:",[679,44459,44460],{"class":693}," (wrap-with-poop)\n",[679,44462,44463,44466],{"class":681,"line":982},[679,44464,44465],{"class":880},"version:",[679,44467,44468],{"class":693}," (1.0.0) 0.0.1\n",[679,44470,44471,44474,44477,44479,44481,44484,44486,44489,44491,44494,44496,44498,44501,44503,44506,44508,44511],{"class":681,"line":988},[679,44472,44473],{"class":880},"description:",[679,44475,44476],{"class":689}," This",[679,44478,44400],{"class":689},[679,44480,22359],{"class":689},[679,44482,44483],{"class":689}," take",[679,44485,44442],{"class":689},[679,44487,44488],{"class":689}," string",[679,44490,22337],{"class":689},[679,44492,44493],{"class":689}," give",[679,44495,44410],{"class":689},[679,44497,22346],{"class":689},[679,44499,44500],{"class":689}," wrap",[679,44502,44410],{"class":689},[679,44504,44505],{"class":689}," with",[679,44507,22351],{"class":689},[679,44509,44510],{"class":689}," poop",[679,44512,44513],{"class":689}," emjoi\n",[679,44515,44516,44519,44522],{"class":681,"line":993},[679,44517,44518],{"class":880},"entry",[679,44520,44521],{"class":689}," point:",[679,44523,44524],{"class":693}," (index.js)\n",[679,44526,44527,44530],{"class":681,"line":2129},[679,44528,44529],{"class":931},"test",[679,44531,44532],{"class":689}," command:\n",[679,44534,44535,44538],{"class":681,"line":2140},[679,44536,44537],{"class":880},"git",[679,44539,44540],{"class":689}," repository:\n",[679,44542,44543,44546],{"class":681,"line":2145},[679,44544,44545],{"class":880},"keywords:",[679,44547,44548],{"class":689}," node,npm\n",[679,44550,44551,44554,44557],{"class":681,"line":2154},[679,44552,44553],{"class":880},"author:",[679,44555,44556],{"class":689}," Dan",[679,44558,44559],{"class":689}," Vega\n",[679,44561,44562,44565],{"class":681,"line":2159},[679,44563,44564],{"class":880},"license:",[679,44566,44567],{"class":693}," (ISC) MIT\n",[679,44569,44570,44573,44575,44577,44579],{"class":681,"line":2164},[679,44571,44572],{"class":880},"About",[679,44574,21703],{"class":689},[679,44576,21694],{"class":689},[679,44578,21703],{"class":689},[679,44580,44581],{"class":689}," /Users/vega/dev/npm/wrap-with-poop/package.json:\n",[679,44583,44584],{"class":681,"line":3134},[679,44585,889],{"emptyLinePlaceholder":797},[679,44587,44588],{"class":681,"line":3139},[679,44589,28448],{"class":693},[679,44591,44592,44595,44597],{"class":681,"line":3144},[679,44593,44594],{"class":880}," \"name\"",[679,44596,2391],{"class":931},[679,44598,44599],{"class":689}," \"wrap-with-poop\",\n",[679,44601,44602,44605,44607],{"class":681,"line":3149},[679,44603,44604],{"class":880}," \"version\"",[679,44606,2391],{"class":931},[679,44608,44609],{"class":689}," \"0.0.1\",\n",[679,44611,44612,44615,44617],{"class":681,"line":3169},[679,44613,44614],{"class":880}," \"description\"",[679,44616,2391],{"class":931},[679,44618,44619],{"class":689}," \"This package will take any string you give it and wrap it with the poop emjoi\",\n",[679,44621,44622,44625,44627],{"class":681,"line":3185},[679,44623,44624],{"class":880}," \"main\"",[679,44626,2391],{"class":931},[679,44628,44629],{"class":689}," \"index.js\",\n",[679,44631,44632,44634,44636],{"class":681,"line":3194},[679,44633,30870],{"class":880},[679,44635,2391],{"class":931},[679,44637,884],{"class":689},[679,44639,44640,44642,44644,44647,44650,44653,44655],{"class":681,"line":3199},[679,44641,30913],{"class":880},[679,44643,2391],{"class":931},[679,44645,44646],{"class":689}," \"echo ",[679,44648,44649],{"class":931},"\\\"",[679,44651,44652],{"class":689},"Error: no test specified",[679,44654,44649],{"class":931},[679,44656,44657],{"class":689}," && exit 1\"\n",[679,44659,44660],{"class":681,"line":3212},[679,44661,28483],{"class":693},[679,44663,44664,44667,44669],{"class":681,"line":3217},[679,44665,44666],{"class":880}," \"keywords\"",[679,44668,2391],{"class":931},[679,44670,44671],{"class":693}," [\n",[679,44673,44674,44677],{"class":681,"line":3222},[679,44675,44676],{"class":880}," \"node\"",[679,44678,12083],{"class":880},[679,44680,44681],{"class":681,"line":3227},[679,44682,44683],{"class":880}," \"npm\"\n",[679,44685,44686],{"class":681,"line":3232},[679,44687,28705],{"class":693},[679,44689,44690,44693,44695],{"class":681,"line":3499},[679,44691,44692],{"class":880}," \"author\"",[679,44694,2391],{"class":931},[679,44696,44697],{"class":689}," \"Dan Vega\",\n",[679,44699,44700,44703,44705],{"class":681,"line":3509},[679,44701,44702],{"class":880}," \"license\"",[679,44704,2391],{"class":931},[679,44706,44707],{"class":689}," \"MIT\"\n",[679,44709,44710],{"class":681,"line":3516},[679,44711,996],{"class":693},[679,44713,44714],{"class":681,"line":3531},[679,44715,889],{"emptyLinePlaceholder":797},[679,44717,44718],{"class":681,"line":3536},[679,44719,889],{"emptyLinePlaceholder":797},[679,44721,44722,44725,44727,44730],{"class":681,"line":3541},[679,44723,44724],{"class":880},"Is",[679,44726,21353],{"class":689},[679,44728,44729],{"class":689}," OK?",[679,44731,44732],{"class":693}," (yes) yes\n",[4542,44734,44736],{"id":44735},"writing-your-plugin","Writing your plugin",[651,44738,44739],{},"Next open this project up in Visual Studio Code and create index.js. The reason you're creating this file is because in your package.json you said that this was your entry point. In your index.js add the following code:",[669,44741,44743],{"className":25132,"code":44742,"language":25134,"meta":674,"style":674},"module.exports = str => {\n return `💩${str}💩`;\n};\n",[676,44744,44745,44763,44779],{"__ignoreMap":674},[679,44746,44747,44749,44751,44753,44755,44758,44761],{"class":681,"line":682},[679,44748,27515],{"class":931},[679,44750,664],{"class":693},[679,44752,43903],{"class":931},[679,44754,6883],{"class":685},[679,44756,44757],{"class":2099}," str",[679,44759,44760],{"class":685}," =>",[679,44762,884],{"class":693},[679,44764,44765,44768,44771,44774,44777],{"class":681,"line":790},[679,44766,44767],{"class":685}," return",[679,44769,44770],{"class":689}," `💩${",[679,44772,44773],{"class":693},"str",[679,44775,44776],{"class":689},"}💩`",[679,44778,1186],{"class":693},[679,44780,44781],{"class":681,"line":892},[679,44782,44055],{"class":693},[651,44784,44785],{},"The module.exports object allows us to organize some related code and then expose it as a module. This means that when we are done we could import this module into another application. In this case we are assigning an arrow function which means we are exposing a single function that takes an argument called str and returns that string wrapped with the poo emoji. That is all you need to do with this project. It is a pretty simple package but it will help walk through a few things.",[4542,44787,44789],{"id":44788},"npm-local-development","npm local development",[651,44791,44792],{},"Now that you have our package ready to go you need to test it in another project. In the real world you should be writing some unit tests against it but I want to save that for another article & screencast.",[651,44794,44795],{},"Next create a new directory (outside of your package) called wrap-with-poo-testing. You will again need to run npm init but this time you can add the -y argument to skip all of the questions, they are less important this time.",[669,44797,44799],{"className":5851,"code":44798,"language":5853,"meta":674,"style":674},"npm init -y\n\nWrote to /Users/vega/dev/npm/wrap-with-poo/package.json:\n\n{\n \"name\": \"wrap-with-poop\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [],\n \"author\": \"\",\n \"license\": \"ISC\"\n}\n",[676,44800,44801,44810,44814,44824,44828,44832,44840,44849,44858,44866,44874,44890,44894,44903,44911,44920],{"__ignoreMap":674},[679,44802,44803,44805,44807],{"class":681,"line":682},[679,44804,24568],{"class":880},[679,44806,36742],{"class":689},[679,44808,44809],{"class":931}," -y\n",[679,44811,44812],{"class":681,"line":790},[679,44813,889],{"emptyLinePlaceholder":797},[679,44815,44816,44819,44821],{"class":681,"line":892},[679,44817,44818],{"class":880},"Wrote",[679,44820,21703],{"class":689},[679,44822,44823],{"class":689}," /Users/vega/dev/npm/wrap-with-poo/package.json:\n",[679,44825,44826],{"class":681,"line":901},[679,44827,889],{"emptyLinePlaceholder":797},[679,44829,44830],{"class":681,"line":909},[679,44831,28448],{"class":693},[679,44833,44834,44836,44838],{"class":681,"line":918},[679,44835,44594],{"class":880},[679,44837,2391],{"class":931},[679,44839,44599],{"class":689},[679,44841,44842,44844,44846],{"class":681,"line":935},[679,44843,44604],{"class":880},[679,44845,2391],{"class":931},[679,44847,44848],{"class":689}," \"1.0.0\",\n",[679,44850,44851,44853,44855],{"class":681,"line":944},[679,44852,44614],{"class":880},[679,44854,2391],{"class":931},[679,44856,44857],{"class":689}," \"\",\n",[679,44859,44860,44862,44864],{"class":681,"line":959},[679,44861,44624],{"class":880},[679,44863,2391],{"class":931},[679,44865,44629],{"class":689},[679,44867,44868,44870,44872],{"class":681,"line":964},[679,44869,30870],{"class":880},[679,44871,2391],{"class":931},[679,44873,884],{"class":689},[679,44875,44876,44878,44880,44882,44884,44886,44888],{"class":681,"line":977},[679,44877,30913],{"class":880},[679,44879,2391],{"class":931},[679,44881,44646],{"class":689},[679,44883,44649],{"class":931},[679,44885,44652],{"class":689},[679,44887,44649],{"class":931},[679,44889,44657],{"class":689},[679,44891,44892],{"class":681,"line":982},[679,44893,28483],{"class":693},[679,44895,44896,44898,44900],{"class":681,"line":988},[679,44897,44666],{"class":880},[679,44899,2391],{"class":931},[679,44901,44902],{"class":693}," [],\n",[679,44904,44905,44907,44909],{"class":681,"line":993},[679,44906,44692],{"class":880},[679,44908,2391],{"class":931},[679,44910,44857],{"class":689},[679,44912,44913,44915,44917],{"class":681,"line":2129},[679,44914,44702],{"class":880},[679,44916,2391],{"class":931},[679,44918,44919],{"class":689}," \"ISC\"\n",[679,44921,44922],{"class":681,"line":2140},[679,44923,996],{"class":693},[5909,44925,44927],{"id":44926},"npm-install","NPM Install",[651,44929,44930],{},"In this project create a new file called app.js. This is where you are going to use your new wrap-with-poo package. This is normally where you would normally install the npm package you needed by running the following command.",[669,44932,44934],{"className":5851,"code":44933,"language":5853,"meta":674,"style":674},"npm install wrap-with-poo\n",[676,44935,44936],{"__ignoreMap":674},[679,44937,44938,44940,44942],{"class":681,"line":682},[679,44939,24568],{"class":880},[679,44941,24571],{"class":689},[679,44943,44944],{"class":689}," wrap-with-poo\n",[651,44946,44947],{},"The problem with this is that you haven't published your new plugin yet so it isn't in npm. You need a way to reference the package locally while your developing it. You could run npm install with the absolute path to the package.",[669,44949,44951],{"className":5851,"code":44950,"language":5853,"meta":674,"style":674},"npm install /Users/vega/dev/npm/wrap-with-poo\n",[676,44952,44953],{"__ignoreMap":674},[679,44954,44955,44957,44959],{"class":681,"line":682},[679,44956,24568],{"class":880},[679,44958,24571],{"class":689},[679,44960,44961],{"class":689}," /Users/vega/dev/npm/wrap-with-poo\n",[651,44963,44964],{},"Which would update your package.json to look like this",[669,44966,44968],{"className":25132,"code":44967,"language":25134,"meta":674,"style":674},"{\n \"name\": \"npm\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [],\n \"author\": \"\",\n \"license\": \"ISC\",\n \"dependencies\": {\n \"wrap-with-poo\": \"file:../wrap-with-poo\"\n }\n}\n",[676,44969,44970,44974,44985,44996,45006,45017,45023,45040,45044,45050,45060,45071,45078,45088,45092],{"__ignoreMap":674},[679,44971,44972],{"class":681,"line":682},[679,44973,28448],{"class":693},[679,44975,44976,44978,44980,44983],{"class":681,"line":790},[679,44977,44594],{"class":689},[679,44979,4282],{"class":693},[679,44981,44982],{"class":689},"\"npm\"",[679,44984,12083],{"class":693},[679,44986,44987,44989,44991,44994],{"class":681,"line":892},[679,44988,44604],{"class":689},[679,44990,4282],{"class":693},[679,44992,44993],{"class":689},"\"1.0.0\"",[679,44995,12083],{"class":693},[679,44997,44998,45000,45002,45004],{"class":681,"line":901},[679,44999,44614],{"class":689},[679,45001,4282],{"class":693},[679,45003,3579],{"class":689},[679,45005,12083],{"class":693},[679,45007,45008,45010,45012,45015],{"class":681,"line":909},[679,45009,44624],{"class":689},[679,45011,4282],{"class":693},[679,45013,45014],{"class":689},"\"index.js\"",[679,45016,12083],{"class":693},[679,45018,45019,45021],{"class":681,"line":918},[679,45020,30870],{"class":689},[679,45022,28468],{"class":693},[679,45024,45025,45027,45029,45032,45034,45036,45038],{"class":681,"line":935},[679,45026,30913],{"class":689},[679,45028,4282],{"class":693},[679,45030,45031],{"class":689},"\"echo ",[679,45033,44649],{"class":931},[679,45035,44652],{"class":689},[679,45037,44649],{"class":931},[679,45039,44657],{"class":689},[679,45041,45042],{"class":681,"line":944},[679,45043,28483],{"class":693},[679,45045,45046,45048],{"class":681,"line":959},[679,45047,44666],{"class":689},[679,45049,28652],{"class":693},[679,45051,45052,45054,45056,45058],{"class":681,"line":964},[679,45053,44692],{"class":689},[679,45055,4282],{"class":693},[679,45057,3579],{"class":689},[679,45059,12083],{"class":693},[679,45061,45062,45064,45066,45069],{"class":681,"line":977},[679,45063,44702],{"class":689},[679,45065,4282],{"class":693},[679,45067,45068],{"class":689},"\"ISC\"",[679,45070,12083],{"class":693},[679,45072,45073,45076],{"class":681,"line":982},[679,45074,45075],{"class":689}," \"dependencies\"",[679,45077,28468],{"class":693},[679,45079,45080,45083,45085],{"class":681,"line":988},[679,45081,45082],{"class":689}," \"wrap-with-poo\"",[679,45084,4282],{"class":693},[679,45086,45087],{"class":689},"\"file:../wrap-with-poo\"\n",[679,45089,45090],{"class":681,"line":993},[679,45091,21405],{"class":693},[679,45093,45094],{"class":681,"line":2129},[679,45095,996],{"class":693},[651,45097,45098,45099,45104,45105,664],{},"If you need to test out ",[812,45100,45103],{"href":45101,"rel":45102},"https://docs.npmjs.com/misc/scripts",[816],"pre-install/post-install hooks"," in your package then you will want to use this approach. If you don't care about the best way to develop NPM projects locally is by using ",[812,45106,45109],{"href":45107,"rel":45108},"https://docs.npmjs.com/cli/link.html",[816],"npm link",[5909,45111,45113],{"id":45112},"npm-link","NPM Link",[651,45115,45116],{},"npm link is a process that allows you to create a symbolic link between your project and the dependency. First you need to move in to the directory wrap-with-poo and run the following command.",[669,45118,45120],{"className":5851,"code":45119,"language":5853,"meta":674,"style":674},"npm link\n",[676,45121,45122],{"__ignoreMap":674},[679,45123,45124,45126],{"class":681,"line":682},[679,45125,24568],{"class":880},[679,45127,45128],{"class":689}," link\n",[651,45130,45131],{},"This will take your package and create a symbolic link in the npm global folder to it.",[651,45133,45134],{},[2939,45135,45136],{},"/Users/vega/.nvm/versions/node/v10.15.0/lib/node_modules/wrap-with-poo -> /Users/vega/dev/npm/wrap-with-poo",[651,45138,45139],{},"This means that your project can be used in any project with one more simple step. The next thing you need to do is to move into the project wrap-with-poo-testing and run the following command.",[669,45141,45143],{"className":5851,"code":45142,"language":5853,"meta":674,"style":674},"npm link wrap-with-poo\n",[676,45144,45145],{"__ignoreMap":674},[679,45146,45147,45149,45152],{"class":681,"line":682},[679,45148,24568],{"class":880},[679,45150,45151],{"class":689}," link",[679,45153,44944],{"class":689},[651,45155,45156,45157],{},"This will output the following: ",[2939,45158,45159],{},"/Users/vega/dev/npm/wrap-with-poo-testing/node_modules/wrap-with-poo -> /Users/vega/.nvm/versions/node/v10.15.0/lib/node_modules/wra\np-with-poo -> /Users/vega/dev/npm/wrap-with-poo",[651,45161,45162],{},"That is all there is to it, no need to install the dependency. You are ready to begin writing some code to play with you new plugin. Open up the app.js and add the following code.",[669,45164,45166],{"className":25132,"code":45165,"language":25134,"meta":674,"style":674},"const poo = require(\"wrap-with-poo\");\nconst boring = \"This is a boring string\";\nconst fun = poo(boring);\n\nconsole.log(fun);\n",[676,45167,45168,45188,45202,45216,45220],{"__ignoreMap":674},[679,45169,45170,45173,45176,45178,45181,45183,45186],{"class":681,"line":682},[679,45171,45172],{"class":685},"const",[679,45174,45175],{"class":931}," poo",[679,45177,6883],{"class":685},[679,45179,45180],{"class":880}," require",[679,45182,745],{"class":693},[679,45184,45185],{"class":689},"\"wrap-with-poo\"",[679,45187,1208],{"class":693},[679,45189,45190,45192,45195,45197,45200],{"class":681,"line":790},[679,45191,45172],{"class":685},[679,45193,45194],{"class":931}," boring",[679,45196,6883],{"class":685},[679,45198,45199],{"class":689}," \"This is a boring string\"",[679,45201,1186],{"class":693},[679,45203,45204,45206,45209,45211,45213],{"class":681,"line":892},[679,45205,45172],{"class":685},[679,45207,45208],{"class":931}," fun",[679,45210,6883],{"class":685},[679,45212,45175],{"class":880},[679,45214,45215],{"class":693},"(boring);\n",[679,45217,45218],{"class":681,"line":901},[679,45219,889],{"emptyLinePlaceholder":797},[679,45221,45222,45225,45227],{"class":681,"line":909},[679,45223,45224],{"class":693},"console.",[679,45226,21374],{"class":880},[679,45228,45229],{"class":693},"(fun);\n",[651,45231,45232],{},"And run the following command from the integrated terminal",[669,45234,45236],{"className":25132,"code":45235,"language":25134,"meta":674,"style":674},"node app.js\n",[676,45237,45238],{"__ignoreMap":674},[679,45239,45240],{"class":681,"line":682},[679,45241,45235],{"class":693},[651,45243,45244],{},"And you will get the following output",[669,45246,45248],{"className":5851,"code":45247,"language":5853,"meta":674,"style":674},"💩This is a boring string💩\n",[676,45249,45250],{"__ignoreMap":674},[679,45251,45252,45255,45257,45259,45261],{"class":681,"line":682},[679,45253,45254],{"class":880},"💩This",[679,45256,29045],{"class":689},[679,45258,21697],{"class":689},[679,45260,45194],{"class":689},[679,45262,45263],{"class":689}," string💩\n",[4542,45265,45267],{"id":45266},"publish-source-code","Publish Source Code",[651,45269,45270],{},"Now that we know our project works its time to make it public for everyone to use. If you haven't done so yet I would add your project to Github or whatever source code hosting you prefer.",[669,45272,45274],{"className":5851,"code":45273,"language":5853,"meta":674,"style":674},"git init\ngit add .\ngit commit -m \"Initial commit\"\ngit remote add origin https://github.com/cfaddict/wrap-with-poo.git\ngit push -u origin master\n",[676,45275,45276,45282,45291,45304,45319],{"__ignoreMap":674},[679,45277,45278,45280],{"class":681,"line":682},[679,45279,44537],{"class":880},[679,45281,44250],{"class":689},[679,45283,45284,45286,45288],{"class":681,"line":790},[679,45285,44537],{"class":880},[679,45287,8872],{"class":689},[679,45289,45290],{"class":689}," .\n",[679,45292,45293,45295,45298,45301],{"class":681,"line":892},[679,45294,44537],{"class":880},[679,45296,45297],{"class":689}," commit",[679,45299,45300],{"class":931}," -m",[679,45302,45303],{"class":689}," \"Initial commit\"\n",[679,45305,45306,45308,45311,45313,45316],{"class":681,"line":901},[679,45307,44537],{"class":880},[679,45309,45310],{"class":689}," remote",[679,45312,8872],{"class":689},[679,45314,45315],{"class":689}," origin",[679,45317,45318],{"class":689}," https://github.com/cfaddict/wrap-with-poo.git\n",[679,45320,45321,45323,45326,45329,45331],{"class":681,"line":909},[679,45322,44537],{"class":880},[679,45324,45325],{"class":689}," push",[679,45327,45328],{"class":931}," -u",[679,45330,45315],{"class":689},[679,45332,45333],{"class":689}," master\n",[651,45335,45336],{},"Now that it is on Github go back and add an entry to your package.json so everyone knows where to find the source code using the homepage key.",[669,45338,45340],{"className":25132,"code":45339,"language":25134,"meta":674,"style":674},"{\n \"name\": \"wrap-with-poo\",\n \"version\": \"0.0.1\",\n \"description\": \"This package will wrap any string you give it with the poop emoji\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [\n \"node\",\n \"npm\",\n \"poop\"\n ],\n \"author\": \"Dan Vega\",\n \"license\": \"MIT\",\n \"homepage\": \"https://github.com/cfaddict/wrap-with-poo\"\n}\n",[676,45341,45342,45346,45356,45367,45378,45388,45394,45410,45414,45420,45426,45433,45438,45442,45453,45464,45474],{"__ignoreMap":674},[679,45343,45344],{"class":681,"line":682},[679,45345,28448],{"class":693},[679,45347,45348,45350,45352,45354],{"class":681,"line":790},[679,45349,44594],{"class":689},[679,45351,4282],{"class":693},[679,45353,45185],{"class":689},[679,45355,12083],{"class":693},[679,45357,45358,45360,45362,45365],{"class":681,"line":892},[679,45359,44604],{"class":689},[679,45361,4282],{"class":693},[679,45363,45364],{"class":689},"\"0.0.1\"",[679,45366,12083],{"class":693},[679,45368,45369,45371,45373,45376],{"class":681,"line":901},[679,45370,44614],{"class":689},[679,45372,4282],{"class":693},[679,45374,45375],{"class":689},"\"This package will wrap any string you give it with the poop emoji\"",[679,45377,12083],{"class":693},[679,45379,45380,45382,45384,45386],{"class":681,"line":909},[679,45381,44624],{"class":689},[679,45383,4282],{"class":693},[679,45385,45014],{"class":689},[679,45387,12083],{"class":693},[679,45389,45390,45392],{"class":681,"line":918},[679,45391,30870],{"class":689},[679,45393,28468],{"class":693},[679,45395,45396,45398,45400,45402,45404,45406,45408],{"class":681,"line":935},[679,45397,30913],{"class":689},[679,45399,4282],{"class":693},[679,45401,45031],{"class":689},[679,45403,44649],{"class":931},[679,45405,44652],{"class":689},[679,45407,44649],{"class":931},[679,45409,44657],{"class":689},[679,45411,45412],{"class":681,"line":944},[679,45413,28483],{"class":693},[679,45415,45416,45418],{"class":681,"line":959},[679,45417,44666],{"class":689},[679,45419,28491],{"class":693},[679,45421,45422,45424],{"class":681,"line":964},[679,45423,44676],{"class":689},[679,45425,12083],{"class":693},[679,45427,45428,45431],{"class":681,"line":977},[679,45429,45430],{"class":689}," \"npm\"",[679,45432,12083],{"class":693},[679,45434,45435],{"class":681,"line":982},[679,45436,45437],{"class":689}," \"poop\"\n",[679,45439,45440],{"class":681,"line":988},[679,45441,28705],{"class":693},[679,45443,45444,45446,45448,45451],{"class":681,"line":993},[679,45445,44692],{"class":689},[679,45447,4282],{"class":693},[679,45449,45450],{"class":689},"\"Dan Vega\"",[679,45452,12083],{"class":693},[679,45454,45455,45457,45459,45462],{"class":681,"line":2129},[679,45456,44702],{"class":689},[679,45458,4282],{"class":693},[679,45460,45461],{"class":689},"\"MIT\"",[679,45463,12083],{"class":693},[679,45465,45466,45469,45471],{"class":681,"line":2140},[679,45467,45468],{"class":689}," \"homepage\"",[679,45470,4282],{"class":693},[679,45472,45473],{"class":689},"\"https://github.com/cfaddict/wrap-with-poo\"\n",[679,45475,45476],{"class":681,"line":2145},[679,45477,996],{"class":693},[4542,45479,45481],{"id":45480},"publishing-npm-package","Publishing NPM Package",[651,45483,45484],{},"It is now time to publish our project to npm so that anyone can use it. If this is your first time publishing a package open up a terminal in the wrap-with-poo directory and type the following command.",[669,45486,45488],{"className":5851,"code":45487,"language":5853,"meta":674,"style":674},"npm adduser\n",[676,45489,45490],{"__ignoreMap":674},[679,45491,45492,45494],{"class":681,"line":682},[679,45493,24568],{"class":880},[679,45495,45496],{"class":689}," adduser\n",[651,45498,45499],{},"This will ask you for your npm account information such as username, password and email.",[669,45501,45503],{"className":5851,"code":45502,"language":5853,"meta":674,"style":674},"vega wrap-with-poo (master) $ npm adduser\nUsername: therealdanvega\nPassword:\nEmail: (this IS public) danvega@gmail.com\nLogged in as therealdanvega on https://registry.npmjs.org/.\n",[676,45504,45505,45516,45524,45529,45546],{"__ignoreMap":674},[679,45506,45507,45510,45513],{"class":681,"line":682},[679,45508,45509],{"class":880},"vega",[679,45511,45512],{"class":689}," wrap-with-poo",[679,45514,45515],{"class":693}," (master) $ npm adduser\n",[679,45517,45518,45521],{"class":681,"line":790},[679,45519,45520],{"class":880},"Username:",[679,45522,45523],{"class":689}," therealdanvega\n",[679,45525,45526],{"class":681,"line":892},[679,45527,45528],{"class":880},"Password:\n",[679,45530,45531,45534,45537,45540,45543],{"class":681,"line":901},[679,45532,45533],{"class":880},"Email:",[679,45535,45536],{"class":693}," (this ",[679,45538,45539],{"class":689},"IS",[679,45541,45542],{"class":689}," public",[679,45544,45545],{"class":693},") danvega@gmail.com\n",[679,45547,45548,45551,45553,45555,45558,45560],{"class":681,"line":909},[679,45549,45550],{"class":880},"Logged",[679,45552,6997],{"class":689},[679,45554,24745],{"class":689},[679,45556,45557],{"class":689}," therealdanvega",[679,45559,22389],{"class":689},[679,45561,45562],{"class":689}," https://registry.npmjs.org/.\n",[651,45564,45565,45566,45569],{},"Now you're ready to publish but there are a couple of things you need to remember. First every npm package must have a unique name. I would head over to ",[812,45567,24568],{"href":27322,"rel":45568},[816]," and do a quick search for your package. I have already published the package wrap-with-poo so yours will need a new unique name.",[651,45571,45572],{},"The next thing you need to know is that your version number matters. I start with 0.0.1 and work my way up from there. Once you publish a specific version you can't publish the same version again. It is a good idea to build a number of features into a version and then publish that. If you run",[669,45574,45576],{"className":5851,"code":45575,"language":5853,"meta":674,"style":674},"npm version\n",[676,45577,45578],{"__ignoreMap":674},[679,45579,45580,45582],{"class":681,"line":682},[679,45581,24568],{"class":880},[679,45583,45584],{"class":689}," version\n",[651,45586,45587],{},"It will tell you what your current version is.",[669,45589,45591],{"className":25132,"code":45590,"language":25134,"meta":674,"style":674},"{ 'wrap-with-poo': '0.0.1',\n npm: '6.7.0',\n ares: '1.15.0',\n cldr: '33.1',\n http_parser: '2.8.0',\n icu: '62.1',\n modules: '64',\n napi: '3',\n nghttp2: '1.34.0',\n node: '10.15.0',\n openssl: '1.1.0j',\n tz: '2018e',\n unicode: '11.0',\n uv: '1.23.2',\n v8: '6.8.275.32-node.45',\n zlib: '1.2.11' }\n",[676,45592,45593,45608,45620,45632,45644,45656,45668,45680,45692,45704,45716,45728,45740,45752,45764,45776],{"__ignoreMap":674},[679,45594,45595,45598,45601,45603,45606],{"class":681,"line":682},[679,45596,45597],{"class":693},"{ ",[679,45599,45600],{"class":689},"'wrap-with-poo'",[679,45602,4282],{"class":693},[679,45604,45605],{"class":689},"'0.0.1'",[679,45607,12083],{"class":693},[679,45609,45610,45613,45615,45618],{"class":681,"line":790},[679,45611,45612],{"class":880}," npm",[679,45614,4282],{"class":693},[679,45616,45617],{"class":689},"'6.7.0'",[679,45619,12083],{"class":693},[679,45621,45622,45625,45627,45630],{"class":681,"line":892},[679,45623,45624],{"class":880}," ares",[679,45626,4282],{"class":693},[679,45628,45629],{"class":689},"'1.15.0'",[679,45631,12083],{"class":693},[679,45633,45634,45637,45639,45642],{"class":681,"line":901},[679,45635,45636],{"class":880}," cldr",[679,45638,4282],{"class":693},[679,45640,45641],{"class":689},"'33.1'",[679,45643,12083],{"class":693},[679,45645,45646,45649,45651,45654],{"class":681,"line":909},[679,45647,45648],{"class":880}," http_parser",[679,45650,4282],{"class":693},[679,45652,45653],{"class":689},"'2.8.0'",[679,45655,12083],{"class":693},[679,45657,45658,45661,45663,45666],{"class":681,"line":918},[679,45659,45660],{"class":880}," icu",[679,45662,4282],{"class":693},[679,45664,45665],{"class":689},"'62.1'",[679,45667,12083],{"class":693},[679,45669,45670,45673,45675,45678],{"class":681,"line":935},[679,45671,45672],{"class":880}," modules",[679,45674,4282],{"class":693},[679,45676,45677],{"class":689},"'64'",[679,45679,12083],{"class":693},[679,45681,45682,45685,45687,45690],{"class":681,"line":944},[679,45683,45684],{"class":880}," napi",[679,45686,4282],{"class":693},[679,45688,45689],{"class":689},"'3'",[679,45691,12083],{"class":693},[679,45693,45694,45697,45699,45702],{"class":681,"line":959},[679,45695,45696],{"class":880}," nghttp2",[679,45698,4282],{"class":693},[679,45700,45701],{"class":689},"'1.34.0'",[679,45703,12083],{"class":693},[679,45705,45706,45709,45711,45714],{"class":681,"line":964},[679,45707,45708],{"class":880}," node",[679,45710,4282],{"class":693},[679,45712,45713],{"class":689},"'10.15.0'",[679,45715,12083],{"class":693},[679,45717,45718,45721,45723,45726],{"class":681,"line":977},[679,45719,45720],{"class":880}," openssl",[679,45722,4282],{"class":693},[679,45724,45725],{"class":689},"'1.1.0j'",[679,45727,12083],{"class":693},[679,45729,45730,45733,45735,45738],{"class":681,"line":982},[679,45731,45732],{"class":880}," tz",[679,45734,4282],{"class":693},[679,45736,45737],{"class":689},"'2018e'",[679,45739,12083],{"class":693},[679,45741,45742,45745,45747,45750],{"class":681,"line":988},[679,45743,45744],{"class":880}," unicode",[679,45746,4282],{"class":693},[679,45748,45749],{"class":689},"'11.0'",[679,45751,12083],{"class":693},[679,45753,45754,45757,45759,45762],{"class":681,"line":993},[679,45755,45756],{"class":880}," uv",[679,45758,4282],{"class":693},[679,45760,45761],{"class":689},"'1.23.2'",[679,45763,12083],{"class":693},[679,45765,45766,45769,45771,45774],{"class":681,"line":2129},[679,45767,45768],{"class":880}," v8",[679,45770,4282],{"class":693},[679,45772,45773],{"class":689},"'6.8.275.32-node.45'",[679,45775,12083],{"class":693},[679,45777,45778,45781,45783,45786],{"class":681,"line":2140},[679,45779,45780],{"class":880}," zlib",[679,45782,4282],{"class":693},[679,45784,45785],{"class":689},"'1.2.11'",[679,45787,39987],{"class":693},[651,45789,45790],{},"If everything looks good you can publish your new project by running",[669,45792,45794],{"className":5851,"code":45793,"language":5853,"meta":674,"style":674},"npm publish\n",[676,45795,45796],{"__ignoreMap":674},[679,45797,45798,45800],{"class":681,"line":682},[679,45799,24568],{"class":880},[679,45801,45802],{"class":689}," publish\n",[651,45804,45805,45806,664],{},"This might take a few seconds but if everything went ok your package should now be ",[812,45807,45810],{"href":45808,"rel":45809},"https://www.npmjs.com/settings/therealdanvega/packages",[816],"live on npm",[651,45812,45813],{},"Congrats on publishing your first npm package!!!",[651,45815,45816],{},"Now you can go into any project that already has a package.json and type the following",[669,45818,45819],{"className":5851,"code":44933,"language":5853,"meta":674,"style":674},[676,45820,45821],{"__ignoreMap":674},[679,45822,45823,45825,45827],{"class":681,"line":682},[679,45824,24568],{"class":880},[679,45826,24571],{"class":689},[679,45828,44944],{"class":689},[651,45830,45831],{},"And use it just like we did in our testing example above.",[4542,45833,40845],{"id":40844},[651,45835,45836],{},"I know some people say that you should create documentation from the beginning but I am not always sure what my code is going to end up looking like so I usually wait on this. Create a README.md in the root of your project and add some information about your project.",[5316,45838,45839,45842,45845,45848],{},[5332,45840,45841],{},"What does your npm package do?",[5332,45843,45844],{},"Why did you create it.",[5332,45846,45847],{},"How do you install it?",[5332,45849,45850],{},"Are there any configuration options?",[4542,45852,9042],{"id":9041},[651,45854,45855],{},"As I said at the beginning of this article I can't believe I published my first npm package this weekend. I just never really had a need to do so until now but I was really excited to learn how easy it was. If this is your first npm package please leave me some comments or tweet at me when yours is live!",[651,45857,44143,45858,41109],{},[41107,45859],{},[786,45861,45862],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":45864},[45865,45866,45867,45868,45872,45873,45874,45875],{"id":44198,"depth":790,"text":44199},{"id":44227,"depth":790,"text":44228},{"id":44735,"depth":790,"text":44736},{"id":44788,"depth":790,"text":44789,"children":45869},[45870,45871],{"id":44926,"depth":892,"text":44927},{"id":45112,"depth":892,"text":45113},{"id":45266,"depth":790,"text":45267},{"id":45480,"depth":790,"text":45481},{"id":40844,"depth":790,"text":40845},{"id":9041,"depth":790,"text":9042},"How to create your first npm package and publish it.",{"slug":45878,"date":45879,"published":797,"author":798,"tags":45880,"cover":45881},"creating-your-first-npm-package","2019-02-10 11:00:00",[27318,24568,25134],"./npm_cover.png",{"title":537,"description":45876},"blog/2019/02/10/creating-your-first-npm-package","N5yNWPzGVEmrm291QXWV3CcYj9C6QeomhkruGJSHfpw",{"id":45886,"title":534,"body":45887,"description":47742,"extension":793,"meta":47743,"navigation":797,"path":535,"seo":47748,"stem":47749,"__hash__":47750},"content/blog/2019/02/13/html-template-tag.md",{"type":648,"value":45888,"toc":47731},[45889,45896,45910,45919,45926,45929,45932,45940,45943,45947,45955,45961,45965,45968,45971,46518,46521,46528,46701,46704,46796,46805,46869,46872,46964,46967,47010,47013,47342,47346,47349,47355,47358,47426,47470,47473,47596,47600,47603,47607,47610,47709,47712,47723,47725,47728],[651,45890,45891,45892,45895],{},"For the past few months, I have been writing a ton of documentation, tutorials, and exercises around VueJS and Vanilla JavaScript. Just for some context, I am Curriculum Developer at ",[812,45893,41137],{"href":41135,"rel":45894},[816],", a coding bootcamp that teaches students how to code in 14 weeks. With that in mind, all of the content is geared towards beginners but made for everyone.",[651,45897,45898,45899,23212,45904,45909],{},"I was recently working some tutorials and exercises around The Fetch API and I wanted to put together a nice example of how to read some JSON data from a local file and then add it to a page. In a simple example I would just use a ",[812,45900,45903],{"href":45901,"rel":45902},"https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement",[816],"createElement",[812,45905,45908],{"href":45906,"rel":45907},"https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode",[816],"createTextNode"," and append the items to the DOM.",[651,45911,45912,45913,45918],{},"In a more complex example where there is a lot more markup involved it can become very cumbersome to create elements, nodes as well as dealing with attributes and classes. In this instance, a great solution is ",[812,45914,45917],{"href":45915,"rel":45916},"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template",[816],"The Content Element Template",". I also realized that a lot of developers (beginning and vets) might not know what this is or why we would use it.",[651,45920,45921,45922,45925],{},"In this article, I am going to take a look at the ",[676,45923,45924],{},"\u003Ctemplate/>"," tag in HTML & Vanilla JavaScript. When you know why this tag exists it might make more sense why its used in Vue Single File Components.",[4542,45927,45917],{"id":45928},"the-content-element-template",[651,45930,45931],{},"You can think of the template tag in HTML the same way you would think of any other template. A template is a mold or pattern that gives you a starting point to create something else from. The MDN docs define The HTML Content Template as:",[1004,45933,45934,45937],{},[651,45935,45936],{},"The HTML Content Template element is a mechanism for holding client-side content that is not to be rendered when a page is loaded but may subsequently be instantiated during runtime using JavaScript.",[651,45938,45939],{},"Think of a template as a content fragment that is being stored for subsequent use in the document. While the parser does process the contents of the template element while loading the page, it does so only to ensure that those contents are valid; the element's contents are not rendered, however.",[651,45941,45942],{},"That sounds pretty straight forward but if it doesn't make total sense yet don't worry. We are going to take a look at a practical example that will hopefully clear everything up for us.",[4542,45944,45946],{"id":45945},"html-content-template-demo","HTML Content Template Demo",[651,45948,45949,45950,45954],{},"I put together that shows off how to use the template tag in Vanilla JavaScript. If you want to check out the source code for this demo you can find it on ",[812,45951,17458],{"href":45952,"rel":45953},"https://github.com/cfaddict/html-template-article",[816],". We are going to build a page that loads a list of user cards based on some JSON data and it will end up looking like this.",[651,45956,45957],{},[660,45958],{"alt":45959,"src":45960},"Demo","./html-content-demo.png",[5909,45962,45964],{"id":45963},"markup","Markup",[651,45966,45967],{},"As I said before the goal of this project was to read in some user data from a JSON file and then write the user information to the page. This gets really cumbersome when you have to create elements one by one and add them to the page.",[651,45969,45970],{},"A better way to approach this is to build out what the markup and CSS are going to look like and then wrap the markup in a template tag. The following HTML is what I ended up with. When I was done I just add a template tag around the markup and give it an id.",[669,45972,45974],{"className":4496,"code":45973,"language":4498,"meta":674,"style":674},"\u003Ctemplate id=\"user-card-template\">\n \u003Cdiv class=\"user\">\n \u003Cdiv class=\"profile\">\n \u003Cimg src=\"\" class=\"avatar\" />\n \u003Ch2>\u003C/h2>\n \u003Cspan class=\"title\">\u003C/span>\n \u003Cdiv class=\"social\">\n \u003Ca href=\"https://www.github.com\" target=\"_blank\"\n >\u003Ci class=\"fab fa-github fa-2x\" target=\"_blank\">\u003C/i\n >\u003C/a>\n \u003Ca href=\"https://www.reddit.com\" target=\"_blank\"\n >\u003Ci class=\"fab fa-reddit-alien fa-2x\">\u003C/i\n >\u003C/a>\n \u003Ca href=\"https://www.twitter.com\" target=\"_blank\"\n >\u003Ci class=\"fab fa-twitter fa-2x\">\u003C/i\n >\u003C/a>\n \u003Ca href=\"https://www.instagram.com\" target=\"_blank\"\n >\u003Ci class=\"fab fa-instagram fa-2x\">\u003C/i\n >\u003C/a>\n \u003Ca href=\"http://www.facebook.com\" target=\"_blank\"\n >\u003Ci class=\"fab fa-facebook-f fa-2x\">\u003C/i\n >\u003C/a>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"stats\">\n \u003Cdiv class=\"posts\">\n \u003Ch3>\u003C/h3>\n \u003Cspan>Posts\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"likes\">\n \u003Ch3>\u003C/h3>\n \u003Cspan>Likes\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"followers\">\n \u003Ch3>\u003C/h3>\n \u003Cspan>Followers\u003C/span>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n\u003C/template>\n",[676,45975,45976,45992,46007,46022,46044,46056,46074,46089,46109,46135,46144,46163,46180,46188,46207,46224,46232,46251,46268,46276,46295,46312,46320,46329,46337,46352,46366,46378,46390,46398,46413,46425,46438,46446,46461,46473,46486,46494,46502,46510],{"__ignoreMap":674},[679,45977,45978,45980,45983,45985,45987,45990],{"class":681,"line":682},[679,45979,4505],{"class":693},[679,45981,45982],{"class":4508},"template",[679,45984,5578],{"class":880},[679,45986,686],{"class":693},[679,45988,45989],{"class":689},"\"user-card-template\"",[679,45991,4519],{"class":693},[679,45993,45994,45996,45998,46000,46002,46005],{"class":681,"line":790},[679,45995,11738],{"class":693},[679,45997,4509],{"class":4508},[679,45999,4512],{"class":880},[679,46001,686],{"class":693},[679,46003,46004],{"class":689},"\"user\"",[679,46006,4519],{"class":693},[679,46008,46009,46011,46013,46015,46017,46020],{"class":681,"line":892},[679,46010,4524],{"class":693},[679,46012,4509],{"class":4508},[679,46014,4512],{"class":880},[679,46016,686],{"class":693},[679,46018,46019],{"class":689},"\"profile\"",[679,46021,4519],{"class":693},[679,46023,46024,46027,46029,46031,46033,46035,46037,46039,46042],{"class":681,"line":901},[679,46025,46026],{"class":693}," \u003C",[679,46028,660],{"class":4508},[679,46030,5361],{"class":880},[679,46032,686],{"class":693},[679,46034,3579],{"class":689},[679,46036,4512],{"class":880},[679,46038,686],{"class":693},[679,46040,46041],{"class":689},"\"avatar\"",[679,46043,5387],{"class":693},[679,46045,46046,46048,46050,46052,46054],{"class":681,"line":909},[679,46047,46026],{"class":693},[679,46049,4542],{"class":4508},[679,46051,4563],{"class":693},[679,46053,4542],{"class":4508},[679,46055,4519],{"class":693},[679,46057,46058,46060,46062,46064,46066,46068,46070,46072],{"class":681,"line":918},[679,46059,46026],{"class":693},[679,46061,679],{"class":4508},[679,46063,4512],{"class":880},[679,46065,686],{"class":693},[679,46067,5250],{"class":689},[679,46069,4563],{"class":693},[679,46071,679],{"class":4508},[679,46073,4519],{"class":693},[679,46075,46076,46078,46080,46082,46084,46087],{"class":681,"line":935},[679,46077,46026],{"class":693},[679,46079,4509],{"class":4508},[679,46081,4512],{"class":880},[679,46083,686],{"class":693},[679,46085,46086],{"class":689},"\"social\"",[679,46088,4519],{"class":693},[679,46090,46091,46093,46095,46097,46099,46102,46104,46106],{"class":681,"line":944},[679,46092,4904],{"class":693},[679,46094,812],{"class":4508},[679,46096,4550],{"class":880},[679,46098,686],{"class":693},[679,46100,46101],{"class":689},"\"https://www.github.com\"",[679,46103,21827],{"class":880},[679,46105,686],{"class":693},[679,46107,46108],{"class":689},"\"_blank\"\n",[679,46110,46111,46114,46117,46119,46121,46124,46126,46128,46130,46132],{"class":681,"line":959},[679,46112,46113],{"class":693}," >\u003C",[679,46115,46116],{"class":4508},"i",[679,46118,4512],{"class":880},[679,46120,686],{"class":693},[679,46122,46123],{"class":689},"\"fab fa-github fa-2x\"",[679,46125,21827],{"class":880},[679,46127,686],{"class":693},[679,46129,43993],{"class":689},[679,46131,4563],{"class":693},[679,46133,46134],{"class":4508},"i\n",[679,46136,46137,46140,46142],{"class":681,"line":964},[679,46138,46139],{"class":693}," >\u003C/",[679,46141,812],{"class":4508},[679,46143,4519],{"class":693},[679,46145,46146,46148,46150,46152,46154,46157,46159,46161],{"class":681,"line":977},[679,46147,4904],{"class":693},[679,46149,812],{"class":4508},[679,46151,4550],{"class":880},[679,46153,686],{"class":693},[679,46155,46156],{"class":689},"\"https://www.reddit.com\"",[679,46158,21827],{"class":880},[679,46160,686],{"class":693},[679,46162,46108],{"class":689},[679,46164,46165,46167,46169,46171,46173,46176,46178],{"class":681,"line":982},[679,46166,46113],{"class":693},[679,46168,46116],{"class":4508},[679,46170,4512],{"class":880},[679,46172,686],{"class":693},[679,46174,46175],{"class":689},"\"fab fa-reddit-alien fa-2x\"",[679,46177,4563],{"class":693},[679,46179,46134],{"class":4508},[679,46181,46182,46184,46186],{"class":681,"line":988},[679,46183,46139],{"class":693},[679,46185,812],{"class":4508},[679,46187,4519],{"class":693},[679,46189,46190,46192,46194,46196,46198,46201,46203,46205],{"class":681,"line":993},[679,46191,4904],{"class":693},[679,46193,812],{"class":4508},[679,46195,4550],{"class":880},[679,46197,686],{"class":693},[679,46199,46200],{"class":689},"\"https://www.twitter.com\"",[679,46202,21827],{"class":880},[679,46204,686],{"class":693},[679,46206,46108],{"class":689},[679,46208,46209,46211,46213,46215,46217,46220,46222],{"class":681,"line":2129},[679,46210,46113],{"class":693},[679,46212,46116],{"class":4508},[679,46214,4512],{"class":880},[679,46216,686],{"class":693},[679,46218,46219],{"class":689},"\"fab fa-twitter fa-2x\"",[679,46221,4563],{"class":693},[679,46223,46134],{"class":4508},[679,46225,46226,46228,46230],{"class":681,"line":2140},[679,46227,46139],{"class":693},[679,46229,812],{"class":4508},[679,46231,4519],{"class":693},[679,46233,46234,46236,46238,46240,46242,46245,46247,46249],{"class":681,"line":2145},[679,46235,4904],{"class":693},[679,46237,812],{"class":4508},[679,46239,4550],{"class":880},[679,46241,686],{"class":693},[679,46243,46244],{"class":689},"\"https://www.instagram.com\"",[679,46246,21827],{"class":880},[679,46248,686],{"class":693},[679,46250,46108],{"class":689},[679,46252,46253,46255,46257,46259,46261,46264,46266],{"class":681,"line":2154},[679,46254,46113],{"class":693},[679,46256,46116],{"class":4508},[679,46258,4512],{"class":880},[679,46260,686],{"class":693},[679,46262,46263],{"class":689},"\"fab fa-instagram fa-2x\"",[679,46265,4563],{"class":693},[679,46267,46134],{"class":4508},[679,46269,46270,46272,46274],{"class":681,"line":2159},[679,46271,46139],{"class":693},[679,46273,812],{"class":4508},[679,46275,4519],{"class":693},[679,46277,46278,46280,46282,46284,46286,46289,46291,46293],{"class":681,"line":2164},[679,46279,4904],{"class":693},[679,46281,812],{"class":4508},[679,46283,4550],{"class":880},[679,46285,686],{"class":693},[679,46287,46288],{"class":689},"\"http://www.facebook.com\"",[679,46290,21827],{"class":880},[679,46292,686],{"class":693},[679,46294,46108],{"class":689},[679,46296,46297,46299,46301,46303,46305,46308,46310],{"class":681,"line":3134},[679,46298,46113],{"class":693},[679,46300,46116],{"class":4508},[679,46302,4512],{"class":880},[679,46304,686],{"class":693},[679,46306,46307],{"class":689},"\"fab fa-facebook-f fa-2x\"",[679,46309,4563],{"class":693},[679,46311,46134],{"class":4508},[679,46313,46314,46316,46318],{"class":681,"line":3139},[679,46315,46139],{"class":693},[679,46317,812],{"class":4508},[679,46319,4519],{"class":693},[679,46321,46322,46325,46327],{"class":681,"line":3144},[679,46323,46324],{"class":693}," \u003C/",[679,46326,4509],{"class":4508},[679,46328,4519],{"class":693},[679,46330,46331,46333,46335],{"class":681,"line":3149},[679,46332,4577],{"class":693},[679,46334,4509],{"class":4508},[679,46336,4519],{"class":693},[679,46338,46339,46341,46343,46345,46347,46350],{"class":681,"line":3169},[679,46340,4524],{"class":693},[679,46342,4509],{"class":4508},[679,46344,4512],{"class":880},[679,46346,686],{"class":693},[679,46348,46349],{"class":689},"\"stats\"",[679,46351,4519],{"class":693},[679,46353,46354,46356,46358,46360,46362,46364],{"class":681,"line":3185},[679,46355,46026],{"class":693},[679,46357,4509],{"class":4508},[679,46359,4512],{"class":880},[679,46361,686],{"class":693},[679,46363,10069],{"class":689},[679,46365,4519],{"class":693},[679,46367,46368,46370,46372,46374,46376],{"class":681,"line":3194},[679,46369,4904],{"class":693},[679,46371,5909],{"class":4508},[679,46373,4563],{"class":693},[679,46375,5909],{"class":4508},[679,46377,4519],{"class":693},[679,46379,46380,46382,46384,46386,46388],{"class":681,"line":3199},[679,46381,4904],{"class":693},[679,46383,679],{"class":4508},[679,46385,10314],{"class":693},[679,46387,679],{"class":4508},[679,46389,4519],{"class":693},[679,46391,46392,46394,46396],{"class":681,"line":3212},[679,46393,46324],{"class":693},[679,46395,4509],{"class":4508},[679,46397,4519],{"class":693},[679,46399,46400,46402,46404,46406,46408,46411],{"class":681,"line":3217},[679,46401,46026],{"class":693},[679,46403,4509],{"class":4508},[679,46405,4512],{"class":880},[679,46407,686],{"class":693},[679,46409,46410],{"class":689},"\"likes\"",[679,46412,4519],{"class":693},[679,46414,46415,46417,46419,46421,46423],{"class":681,"line":3222},[679,46416,4904],{"class":693},[679,46418,5909],{"class":4508},[679,46420,4563],{"class":693},[679,46422,5909],{"class":4508},[679,46424,4519],{"class":693},[679,46426,46427,46429,46431,46434,46436],{"class":681,"line":3227},[679,46428,4904],{"class":693},[679,46430,679],{"class":4508},[679,46432,46433],{"class":693},">Likes\u003C/",[679,46435,679],{"class":4508},[679,46437,4519],{"class":693},[679,46439,46440,46442,46444],{"class":681,"line":3232},[679,46441,46324],{"class":693},[679,46443,4509],{"class":4508},[679,46445,4519],{"class":693},[679,46447,46448,46450,46452,46454,46456,46459],{"class":681,"line":3499},[679,46449,46026],{"class":693},[679,46451,4509],{"class":4508},[679,46453,4512],{"class":880},[679,46455,686],{"class":693},[679,46457,46458],{"class":689},"\"followers\"",[679,46460,4519],{"class":693},[679,46462,46463,46465,46467,46469,46471],{"class":681,"line":3509},[679,46464,4904],{"class":693},[679,46466,5909],{"class":4508},[679,46468,4563],{"class":693},[679,46470,5909],{"class":4508},[679,46472,4519],{"class":693},[679,46474,46475,46477,46479,46482,46484],{"class":681,"line":3516},[679,46476,4904],{"class":693},[679,46478,679],{"class":4508},[679,46480,46481],{"class":693},">Followers\u003C/",[679,46483,679],{"class":4508},[679,46485,4519],{"class":693},[679,46487,46488,46490,46492],{"class":681,"line":3531},[679,46489,46324],{"class":693},[679,46491,4509],{"class":4508},[679,46493,4519],{"class":693},[679,46495,46496,46498,46500],{"class":681,"line":3536},[679,46497,4577],{"class":693},[679,46499,4509],{"class":4508},[679,46501,4519],{"class":693},[679,46503,46504,46506,46508],{"class":681,"line":3541},[679,46505,11840],{"class":693},[679,46507,4509],{"class":4508},[679,46509,4519],{"class":693},[679,46511,46512,46514,46516],{"class":681,"line":3546},[679,46513,4586],{"class":693},[679,46515,45982],{"class":4508},[679,46517,4519],{"class":693},[5909,46519,46520],{"id":25134},"JavaScript",[651,46522,46523,46524,46527],{},"Now that I have my markup its time we take a look at the JavaScript. I have a JSON file called ",[676,46525,46526],{},"users.json"," that has an array of 9 users that look like this.",[669,46529,46531],{"className":25132,"code":46530,"language":25134,"meta":674,"style":674},"{\n \"id\": 1,\n \"fullname\": \"Jonathan Stark\",\n \"title\": \"Software Developer\",\n \"avatar\": \"img/user_1.png\",\n \"social\": {\n \"github\": \"github_username\",\n \"reddit\": \"reddit_username\",\n \"twitter\": \"twitter_username\",\n \"instagram\": \"instagram_username\",\n \"facebook\": \"facebook_username\"\n },\n \"stats\": {\n \"posts\": \"150\",\n \"likes\": \"680\",\n \"followers\": \"199\"\n }\n}\n",[676,46532,46533,46537,46547,46559,46571,46583,46590,46602,46614,46626,46638,46648,46652,46659,46671,46683,46693,46697],{"__ignoreMap":674},[679,46534,46535],{"class":681,"line":682},[679,46536,28448],{"class":693},[679,46538,46539,46541,46543,46545],{"class":681,"line":790},[679,46540,33276],{"class":689},[679,46542,4282],{"class":693},[679,46544,1557],{"class":931},[679,46546,12083],{"class":693},[679,46548,46549,46552,46554,46557],{"class":681,"line":892},[679,46550,46551],{"class":689}," \"fullname\"",[679,46553,4282],{"class":693},[679,46555,46556],{"class":689},"\"Jonathan Stark\"",[679,46558,12083],{"class":693},[679,46560,46561,46564,46566,46569],{"class":681,"line":901},[679,46562,46563],{"class":689}," \"title\"",[679,46565,4282],{"class":693},[679,46567,46568],{"class":689},"\"Software Developer\"",[679,46570,12083],{"class":693},[679,46572,46573,46576,46578,46581],{"class":681,"line":909},[679,46574,46575],{"class":689}," \"avatar\"",[679,46577,4282],{"class":693},[679,46579,46580],{"class":689},"\"img/user_1.png\"",[679,46582,12083],{"class":693},[679,46584,46585,46588],{"class":681,"line":918},[679,46586,46587],{"class":689}," \"social\"",[679,46589,28468],{"class":693},[679,46591,46592,46595,46597,46600],{"class":681,"line":935},[679,46593,46594],{"class":689}," \"github\"",[679,46596,4282],{"class":693},[679,46598,46599],{"class":689},"\"github_username\"",[679,46601,12083],{"class":693},[679,46603,46604,46607,46609,46612],{"class":681,"line":944},[679,46605,46606],{"class":689}," \"reddit\"",[679,46608,4282],{"class":693},[679,46610,46611],{"class":689},"\"reddit_username\"",[679,46613,12083],{"class":693},[679,46615,46616,46619,46621,46624],{"class":681,"line":959},[679,46617,46618],{"class":689}," \"twitter\"",[679,46620,4282],{"class":693},[679,46622,46623],{"class":689},"\"twitter_username\"",[679,46625,12083],{"class":693},[679,46627,46628,46631,46633,46636],{"class":681,"line":964},[679,46629,46630],{"class":689}," \"instagram\"",[679,46632,4282],{"class":693},[679,46634,46635],{"class":689},"\"instagram_username\"",[679,46637,12083],{"class":693},[679,46639,46640,46643,46645],{"class":681,"line":977},[679,46641,46642],{"class":689}," \"facebook\"",[679,46644,4282],{"class":693},[679,46646,46647],{"class":689},"\"facebook_username\"\n",[679,46649,46650],{"class":681,"line":982},[679,46651,28763],{"class":693},[679,46653,46654,46657],{"class":681,"line":988},[679,46655,46656],{"class":689}," \"stats\"",[679,46658,28468],{"class":693},[679,46660,46661,46664,46666,46669],{"class":681,"line":993},[679,46662,46663],{"class":689}," \"posts\"",[679,46665,4282],{"class":693},[679,46667,46668],{"class":689},"\"150\"",[679,46670,12083],{"class":693},[679,46672,46673,46676,46678,46681],{"class":681,"line":2129},[679,46674,46675],{"class":689}," \"likes\"",[679,46677,4282],{"class":693},[679,46679,46680],{"class":689},"\"680\"",[679,46682,12083],{"class":693},[679,46684,46685,46688,46690],{"class":681,"line":2140},[679,46686,46687],{"class":689}," \"followers\"",[679,46689,4282],{"class":693},[679,46691,46692],{"class":689},"\"199\"\n",[679,46694,46695],{"class":681,"line":2145},[679,46696,985],{"class":693},[679,46698,46699],{"class":681,"line":2154},[679,46700,996],{"class":693},[651,46702,46703],{},"The first step is to read the JSON in and to do so we will use the Fetch API. If you have used fetch before this is nothing new.",[669,46705,46707],{"className":25132,"code":46706,"language":25134,"meta":674,"style":674},"fetch(\"users.json\")\n .then(response => {\n return response.json();\n })\n .then(users => {\n // we have an array of users\n })\n .catch(err => console.error(err));\n",[676,46708,46709,46721,46737,46748,46753,46767,46772,46776],{"__ignoreMap":674},[679,46710,46711,46714,46716,46719],{"class":681,"line":682},[679,46712,46713],{"class":880},"fetch",[679,46715,745],{"class":693},[679,46717,46718],{"class":689},"\"users.json\"",[679,46720,1339],{"class":693},[679,46722,46723,46726,46729,46731,46733,46735],{"class":681,"line":790},[679,46724,46725],{"class":693}," .",[679,46727,46728],{"class":880},"then",[679,46730,745],{"class":693},[679,46732,10447],{"class":2099},[679,46734,44760],{"class":685},[679,46736,884],{"class":693},[679,46738,46739,46741,46744,46746],{"class":681,"line":892},[679,46740,21478],{"class":685},[679,46742,46743],{"class":693}," response.",[679,46745,28441],{"class":880},[679,46747,9317],{"class":693},[679,46749,46750],{"class":681,"line":901},[679,46751,46752],{"class":693}," })\n",[679,46754,46755,46757,46759,46761,46763,46765],{"class":681,"line":909},[679,46756,46725],{"class":693},[679,46758,46728],{"class":880},[679,46760,745],{"class":693},[679,46762,34174],{"class":2099},[679,46764,44760],{"class":685},[679,46766,884],{"class":693},[679,46768,46769],{"class":681,"line":918},[679,46770,46771],{"class":1400}," // we have an array of users\n",[679,46773,46774],{"class":681,"line":935},[679,46775,46752],{"class":693},[679,46777,46778,46780,46782,46784,46787,46789,46791,46793],{"class":681,"line":944},[679,46779,46725],{"class":693},[679,46781,9394],{"class":880},[679,46783,745],{"class":693},[679,46785,46786],{"class":2099},"err",[679,46788,44760],{"class":685},[679,46790,21371],{"class":693},[679,46792,29446],{"class":880},[679,46794,46795],{"class":693},"(err));\n",[651,46797,46798,46799,46804],{},"Now that we have an array of users we can begin to work with our template. First, we need to check to see if the user's browser supports the HTML Content Template tag. As long as you're using a modern browser ",[812,46800,46803],{"href":46801,"rel":46802},"https://caniuse.com/#feat=template",[816],"it should support it"," but it is best practice to make this check.",[669,46806,46808],{"className":25132,"code":46807,"language":25134,"meta":674,"style":674},"if('content' in document.createElement('template')) {\n\n});\n} else {\n console.error('Your browser does not support templates');\n}\n",[676,46809,46810,46834,46838,46843,46851,46865],{"__ignoreMap":674},[679,46811,46812,46814,46816,46819,46821,46824,46826,46828,46831],{"class":681,"line":682},[679,46813,1217],{"class":685},[679,46815,745],{"class":693},[679,46817,46818],{"class":689},"'content'",[679,46820,6997],{"class":685},[679,46822,46823],{"class":693}," document.",[679,46825,45903],{"class":880},[679,46827,745],{"class":693},[679,46829,46830],{"class":689},"'template'",[679,46832,46833],{"class":693},")) {\n",[679,46835,46836],{"class":681,"line":790},[679,46837,889],{"emptyLinePlaceholder":797},[679,46839,46840],{"class":681,"line":892},[679,46841,46842],{"class":693},"});\n",[679,46844,46845,46847,46849],{"class":681,"line":901},[679,46846,2253],{"class":693},[679,46848,2256],{"class":685},[679,46850,884],{"class":693},[679,46852,46853,46856,46858,46860,46863],{"class":681,"line":909},[679,46854,46855],{"class":693}," console.",[679,46857,29446],{"class":880},[679,46859,745],{"class":693},[679,46861,46862],{"class":689},"'Your browser does not support templates'",[679,46864,1208],{"class":693},[679,46866,46867],{"class":681,"line":918},[679,46868,996],{"class":693},[651,46870,46871],{},"Now that we know the browser supports this feature we need to get a reference to the parent container that we will be appending each user card to and in this case it's the element with the id of users. We will then iterate over each element in our array.",[669,46873,46875],{"className":25132,"code":46874,"language":25134,"meta":674,"style":674},"if (\"content\" in document.createElement(\"template\")) {\n const container = document.getElementById(\"users\");\n users.forEach(user => {});\n} else {\n console.error(\"Your browser does not support templates\");\n}\n",[676,46876,46877,46899,46921,46938,46946,46960],{"__ignoreMap":674},[679,46878,46879,46881,46883,46886,46888,46890,46892,46894,46897],{"class":681,"line":682},[679,46880,1217],{"class":685},[679,46882,4193],{"class":693},[679,46884,46885],{"class":689},"\"content\"",[679,46887,6997],{"class":685},[679,46889,46823],{"class":693},[679,46891,45903],{"class":880},[679,46893,745],{"class":693},[679,46895,46896],{"class":689},"\"template\"",[679,46898,46833],{"class":693},[679,46900,46901,46904,46907,46909,46911,46914,46916,46919],{"class":681,"line":790},[679,46902,46903],{"class":685}," const",[679,46905,46906],{"class":931}," container",[679,46908,6883],{"class":685},[679,46910,46823],{"class":693},[679,46912,46913],{"class":880},"getElementById",[679,46915,745],{"class":693},[679,46917,46918],{"class":689},"\"users\"",[679,46920,1208],{"class":693},[679,46922,46923,46926,46929,46931,46933,46935],{"class":681,"line":892},[679,46924,46925],{"class":693}," users.",[679,46927,46928],{"class":880},"forEach",[679,46930,745],{"class":693},[679,46932,9575],{"class":2099},[679,46934,44760],{"class":685},[679,46936,46937],{"class":693}," {});\n",[679,46939,46940,46942,46944],{"class":681,"line":901},[679,46941,2253],{"class":693},[679,46943,2256],{"class":685},[679,46945,884],{"class":693},[679,46947,46948,46951,46953,46955,46958],{"class":681,"line":909},[679,46949,46950],{"class":693}," console.",[679,46952,29446],{"class":880},[679,46954,745],{"class":693},[679,46956,46957],{"class":689},"\"Your browser does not support templates\"",[679,46959,1208],{"class":693},[679,46961,46962],{"class":681,"line":918},[679,46963,996],{"class":693},[651,46965,46966],{},"During each iteration of our user's array, we are going to create a copy (clone) of our template. The way that we do that is by getting a reference to the element, getting the content (what's inside the template tag) and then cloning it. We are passing true to the cloneNode method so that we use a deep clone and grab all of the children with it.",[669,46968,46970],{"className":25132,"code":46969,"language":25134,"meta":674,"style":674},"const tmpl = document\n .getElementById(\"user-card-template\")\n .content.cloneNode(true);\n",[676,46971,46972,46984,46996],{"__ignoreMap":674},[679,46973,46974,46976,46979,46981],{"class":681,"line":682},[679,46975,45172],{"class":685},[679,46977,46978],{"class":931}," tmpl",[679,46980,6883],{"class":685},[679,46982,46983],{"class":693}," document\n",[679,46985,46986,46988,46990,46992,46994],{"class":681,"line":790},[679,46987,46725],{"class":693},[679,46989,46913],{"class":880},[679,46991,745],{"class":693},[679,46993,45989],{"class":689},[679,46995,1339],{"class":693},[679,46997,46998,47001,47004,47006,47008],{"class":681,"line":892},[679,46999,47000],{"class":693}," .content.",[679,47002,47003],{"class":880},"cloneNode",[679,47005,745],{"class":693},[679,47007,3441],{"class":931},[679,47009,1208],{"class":693},[651,47011,47012],{},"From there we can simply query the template for a specific element and set its content to the value we are reading in from the user array. In most cases, I am just setting the inner text of the element. Finally, we use the reference to our container element and append everything that was inside the template tag to our page.",[669,47014,47016],{"className":25132,"code":47015,"language":25134,"meta":674,"style":674},"fetch(\"users.json\")\n .then(response => {\n return response.json();\n })\n .then(users => {\n if (\"content\" in document.createElement(\"template\")) {\n const container = document.getElementById(\"users\");\n users.forEach(user => {\n const tmpl = document\n .getElementById(\"user-card-template\")\n .content.cloneNode(true);\n tmpl.querySelector(\"h2\").innerText = user.fullname;\n tmpl.querySelector(\".title\").innerText = user.title;\n tmpl.querySelector(\"img\").setAttribute(\"src\", user.avatar);\n tmpl.querySelector(\".posts h3\").innerText = user.stats.posts;\n tmpl.querySelector(\".likes h3\").innerText = user.stats.likes;\n tmpl.querySelector(\".followers h3\").innerText = user.stats.followers;\n container.appendChild(tmpl);\n });\n } else {\n console.error(\"Your browser does not support templates\");\n }\n })\n .catch(err => console.error(err));\n",[676,47017,47018,47028,47042,47052,47056,47070,47090,47109,47124,47135,47148,47161,47182,47200,47224,47242,47260,47278,47289,47294,47303,47316,47320,47324],{"__ignoreMap":674},[679,47019,47020,47022,47024,47026],{"class":681,"line":682},[679,47021,46713],{"class":880},[679,47023,745],{"class":693},[679,47025,46718],{"class":689},[679,47027,1339],{"class":693},[679,47029,47030,47032,47034,47036,47038,47040],{"class":681,"line":790},[679,47031,46725],{"class":693},[679,47033,46728],{"class":880},[679,47035,745],{"class":693},[679,47037,10447],{"class":2099},[679,47039,44760],{"class":685},[679,47041,884],{"class":693},[679,47043,47044,47046,47048,47050],{"class":681,"line":892},[679,47045,21478],{"class":685},[679,47047,46743],{"class":693},[679,47049,28441],{"class":880},[679,47051,9317],{"class":693},[679,47053,47054],{"class":681,"line":901},[679,47055,46752],{"class":693},[679,47057,47058,47060,47062,47064,47066,47068],{"class":681,"line":909},[679,47059,46725],{"class":693},[679,47061,46728],{"class":880},[679,47063,745],{"class":693},[679,47065,34174],{"class":2099},[679,47067,44760],{"class":685},[679,47069,884],{"class":693},[679,47071,47072,47074,47076,47078,47080,47082,47084,47086,47088],{"class":681,"line":918},[679,47073,1231],{"class":685},[679,47075,4193],{"class":693},[679,47077,46885],{"class":689},[679,47079,6997],{"class":685},[679,47081,46823],{"class":693},[679,47083,45903],{"class":880},[679,47085,745],{"class":693},[679,47087,46896],{"class":689},[679,47089,46833],{"class":693},[679,47091,47092,47095,47097,47099,47101,47103,47105,47107],{"class":681,"line":935},[679,47093,47094],{"class":685}," const",[679,47096,46906],{"class":931},[679,47098,6883],{"class":685},[679,47100,46823],{"class":693},[679,47102,46913],{"class":880},[679,47104,745],{"class":693},[679,47106,46918],{"class":689},[679,47108,1208],{"class":693},[679,47110,47111,47114,47116,47118,47120,47122],{"class":681,"line":944},[679,47112,47113],{"class":693}," users.",[679,47115,46928],{"class":880},[679,47117,745],{"class":693},[679,47119,9575],{"class":2099},[679,47121,44760],{"class":685},[679,47123,884],{"class":693},[679,47125,47126,47129,47131,47133],{"class":681,"line":959},[679,47127,47128],{"class":685}," const",[679,47130,46978],{"class":931},[679,47132,6883],{"class":685},[679,47134,46983],{"class":693},[679,47136,47137,47140,47142,47144,47146],{"class":681,"line":964},[679,47138,47139],{"class":693}," .",[679,47141,46913],{"class":880},[679,47143,745],{"class":693},[679,47145,45989],{"class":689},[679,47147,1339],{"class":693},[679,47149,47150,47153,47155,47157,47159],{"class":681,"line":977},[679,47151,47152],{"class":693}," .content.",[679,47154,47003],{"class":880},[679,47156,745],{"class":693},[679,47158,3441],{"class":931},[679,47160,1208],{"class":693},[679,47162,47163,47166,47169,47171,47174,47177,47179],{"class":681,"line":982},[679,47164,47165],{"class":693}," tmpl.",[679,47167,47168],{"class":880},"querySelector",[679,47170,745],{"class":693},[679,47172,47173],{"class":689},"\"h2\"",[679,47175,47176],{"class":693},").innerText ",[679,47178,686],{"class":685},[679,47180,47181],{"class":693}," user.fullname;\n",[679,47183,47184,47186,47188,47190,47193,47195,47197],{"class":681,"line":988},[679,47185,47165],{"class":693},[679,47187,47168],{"class":880},[679,47189,745],{"class":693},[679,47191,47192],{"class":689},"\".title\"",[679,47194,47176],{"class":693},[679,47196,686],{"class":685},[679,47198,47199],{"class":693}," user.title;\n",[679,47201,47202,47204,47206,47208,47211,47214,47217,47219,47221],{"class":681,"line":993},[679,47203,47165],{"class":693},[679,47205,47168],{"class":880},[679,47207,745],{"class":693},[679,47209,47210],{"class":689},"\"img\"",[679,47212,47213],{"class":693},").",[679,47215,47216],{"class":880},"setAttribute",[679,47218,745],{"class":693},[679,47220,28506],{"class":689},[679,47222,47223],{"class":693},", user.avatar);\n",[679,47225,47226,47228,47230,47232,47235,47237,47239],{"class":681,"line":2129},[679,47227,47165],{"class":693},[679,47229,47168],{"class":880},[679,47231,745],{"class":693},[679,47233,47234],{"class":689},"\".posts h3\"",[679,47236,47176],{"class":693},[679,47238,686],{"class":685},[679,47240,47241],{"class":693}," user.stats.posts;\n",[679,47243,47244,47246,47248,47250,47253,47255,47257],{"class":681,"line":2140},[679,47245,47165],{"class":693},[679,47247,47168],{"class":880},[679,47249,745],{"class":693},[679,47251,47252],{"class":689},"\".likes h3\"",[679,47254,47176],{"class":693},[679,47256,686],{"class":685},[679,47258,47259],{"class":693}," user.stats.likes;\n",[679,47261,47262,47264,47266,47268,47271,47273,47275],{"class":681,"line":2145},[679,47263,47165],{"class":693},[679,47265,47168],{"class":880},[679,47267,745],{"class":693},[679,47269,47270],{"class":689},"\".followers h3\"",[679,47272,47176],{"class":693},[679,47274,686],{"class":685},[679,47276,47277],{"class":693}," user.stats.followers;\n",[679,47279,47280,47283,47286],{"class":681,"line":2154},[679,47281,47282],{"class":693}," container.",[679,47284,47285],{"class":880},"appendChild",[679,47287,47288],{"class":693},"(tmpl);\n",[679,47290,47291],{"class":681,"line":2159},[679,47292,47293],{"class":693}," });\n",[679,47295,47296,47299,47301],{"class":681,"line":2164},[679,47297,47298],{"class":693}," } ",[679,47300,2256],{"class":685},[679,47302,884],{"class":693},[679,47304,47305,47308,47310,47312,47314],{"class":681,"line":3134},[679,47306,47307],{"class":693}," console.",[679,47309,29446],{"class":880},[679,47311,745],{"class":693},[679,47313,46957],{"class":689},[679,47315,1208],{"class":693},[679,47317,47318],{"class":681,"line":3139},[679,47319,985],{"class":693},[679,47321,47322],{"class":681,"line":3144},[679,47323,46752],{"class":693},[679,47325,47326,47328,47330,47332,47334,47336,47338,47340],{"class":681,"line":3149},[679,47327,46725],{"class":693},[679,47329,9394],{"class":880},[679,47331,745],{"class":693},[679,47333,46786],{"class":2099},[679,47335,44760],{"class":685},[679,47337,21371],{"class":693},[679,47339,29446],{"class":880},[679,47341,46795],{"class":693},[5909,47343,47345],{"id":47344},"conditionals","Conditionals",[651,47347,47348],{},"After writing this article my friend Todd asked me a really good question.",[651,47350,47351],{},[812,47352,47353],{"href":47353,"rel":47354},"https://twitter.com/recursivecodes/status/1089184943673090048",[816],[651,47356,47357],{},"What we have done here is cloned the markup that is inside of the template tag. Because this is normal markup we can do whatever we want with it. So let's say that some users have an account on all of the social networks and some don't.",[669,47359,47361],{"className":25132,"code":47360,"language":25134,"meta":674,"style":674},"\"social\": {\n \"github\": \"github_username\",\n \"reddit\": \"reddit_username\",\n \"twitter\": \"twitter_username\",\n \"instagram\": \"instagram_username\",\n \"facebook\": \"facebook_username\"\n}\n",[676,47362,47363,47369,47380,47391,47402,47413,47422],{"__ignoreMap":674},[679,47364,47365,47367],{"class":681,"line":682},[679,47366,46086],{"class":689},[679,47368,28468],{"class":693},[679,47370,47371,47374,47376,47378],{"class":681,"line":790},[679,47372,47373],{"class":689}," \"github\"",[679,47375,4282],{"class":693},[679,47377,46599],{"class":689},[679,47379,12083],{"class":693},[679,47381,47382,47385,47387,47389],{"class":681,"line":892},[679,47383,47384],{"class":689}," \"reddit\"",[679,47386,4282],{"class":693},[679,47388,46611],{"class":689},[679,47390,12083],{"class":693},[679,47392,47393,47396,47398,47400],{"class":681,"line":901},[679,47394,47395],{"class":689}," \"twitter\"",[679,47397,4282],{"class":693},[679,47399,46623],{"class":689},[679,47401,12083],{"class":693},[679,47403,47404,47407,47409,47411],{"class":681,"line":909},[679,47405,47406],{"class":689}," \"instagram\"",[679,47408,4282],{"class":693},[679,47410,46635],{"class":689},[679,47412,12083],{"class":693},[679,47414,47415,47418,47420],{"class":681,"line":918},[679,47416,47417],{"class":689}," \"facebook\"",[679,47419,4282],{"class":693},[679,47421,46647],{"class":689},[679,47423,47424],{"class":681,"line":935},[679,47425,996],{"class":693},[669,47427,47429],{"className":25132,"code":47428,"language":25134,"meta":674,"style":674},"\"social\": {\n \"github\": \"github_username\",\n \"reddit\": \"reddit_username\",\n \"twitter\": \"twitter_username\"\n}\n",[676,47430,47431,47437,47447,47457,47466],{"__ignoreMap":674},[679,47432,47433,47435],{"class":681,"line":682},[679,47434,46086],{"class":689},[679,47436,28468],{"class":693},[679,47438,47439,47441,47443,47445],{"class":681,"line":790},[679,47440,47373],{"class":689},[679,47442,4282],{"class":693},[679,47444,46599],{"class":689},[679,47446,12083],{"class":693},[679,47448,47449,47451,47453,47455],{"class":681,"line":892},[679,47450,47384],{"class":689},[679,47452,4282],{"class":693},[679,47454,46611],{"class":689},[679,47456,12083],{"class":693},[679,47458,47459,47461,47463],{"class":681,"line":901},[679,47460,47395],{"class":689},[679,47462,4282],{"class":693},[679,47464,47465],{"class":689},"\"twitter_username\"\n",[679,47467,47468],{"class":681,"line":909},[679,47469,996],{"class":693},[651,47471,47472],{},"What we could do here is iterate over all the know social networks that we support and if the users.social object doesn't have that key then we will remove that element from the DOM. Again we are working with normal elements here so we can do things like set the visibility to hidden or remove them completely. In this case, we want to remove it because if just hide it then we will have this empty space in certain cases and that doesn't look good.",[669,47474,47476],{"className":25132,"code":47475,"language":25134,"meta":674,"style":674},"// this is a list of social networks we display under a users profile\nconst socialLinks = [\"github\", \"reddit\", \"twitter\", \"instagram\", \"facebook\"];\n// iterate over that list and check to see if they have an account on that network\nsocialLinks.forEach(social => {\n // if they don't have a link in the JSON data hide that link & icon\n if (!user.social.hasOwnProperty(social)) {\n tmpl.querySelector(`.${social}`).remove();\n }\n});\n",[676,47477,47478,47483,47520,47525,47541,47546,47564,47588,47592],{"__ignoreMap":674},[679,47479,47480],{"class":681,"line":682},[679,47481,47482],{"class":1400},"// this is a list of social networks we display under a users profile\n",[679,47484,47485,47487,47490,47492,47494,47497,47499,47502,47504,47507,47509,47512,47514,47517],{"class":681,"line":790},[679,47486,45172],{"class":685},[679,47488,47489],{"class":931}," socialLinks",[679,47491,6883],{"class":685},[679,47493,4270],{"class":693},[679,47495,47496],{"class":689},"\"github\"",[679,47498,2797],{"class":693},[679,47500,47501],{"class":689},"\"reddit\"",[679,47503,2797],{"class":693},[679,47505,47506],{"class":689},"\"twitter\"",[679,47508,2797],{"class":693},[679,47510,47511],{"class":689},"\"instagram\"",[679,47513,2797],{"class":693},[679,47515,47516],{"class":689},"\"facebook\"",[679,47518,47519],{"class":693},"];\n",[679,47521,47522],{"class":681,"line":892},[679,47523,47524],{"class":1400},"// iterate over that list and check to see if they have an account on that network\n",[679,47526,47527,47530,47532,47534,47537,47539],{"class":681,"line":901},[679,47528,47529],{"class":693},"socialLinks.",[679,47531,46928],{"class":880},[679,47533,745],{"class":693},[679,47535,47536],{"class":2099},"social",[679,47538,44760],{"class":685},[679,47540,884],{"class":693},[679,47542,47543],{"class":681,"line":909},[679,47544,47545],{"class":1400}," // if they don't have a link in the JSON data hide that link & icon\n",[679,47547,47548,47551,47553,47555,47558,47561],{"class":681,"line":918},[679,47549,47550],{"class":685}," if",[679,47552,4193],{"class":693},[679,47554,1223],{"class":685},[679,47556,47557],{"class":693},"user.social.",[679,47559,47560],{"class":880},"hasOwnProperty",[679,47562,47563],{"class":693},"(social)) {\n",[679,47565,47566,47569,47571,47573,47576,47578,47581,47583,47586],{"class":681,"line":935},[679,47567,47568],{"class":693}," tmpl.",[679,47570,47168],{"class":880},[679,47572,745],{"class":693},[679,47574,47575],{"class":689},"`.${",[679,47577,47536],{"class":693},[679,47579,47580],{"class":689},"}`",[679,47582,47213],{"class":693},[679,47584,47585],{"class":880},"remove",[679,47587,9317],{"class":693},[679,47589,47590],{"class":681,"line":944},[679,47591,21405],{"class":693},[679,47593,47594],{"class":681,"line":959},[679,47595,46842],{"class":693},[5909,47597,47599],{"id":47598},"html-template-in-vanilla-javascript-wrapup","HTML Template in Vanilla JavaScript Wrapup",[651,47601,47602],{},"And that is really all you need to create a template in markup, clone it and add data to it. I will mention this because it is important to know but if you take this approach and view source you will only see the template code. This means that if you have data that needs to be written to the page that needs to be search engine friendly this probably isn't a good solution.",[4542,47604,47606],{"id":47605},"template-tag-in-vue","Template Tag in Vue",[651,47608,47609],{},"Now that we know what the template tag is it should make a little more sense what Vue is using this for. If you create a new Single File Component in Vue you will have some code that looks like this.",[669,47611,47613],{"className":4496,"code":47612,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"users\">\n \u003C!-- markup here -->\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\n // js here\n\u003C/script>\n\n\u003Cstyle>\n /* css here */\n\u003C/style>\n",[676,47614,47615,47623,47637,47642,47650,47658,47662,47671,47676,47684,47688,47696,47701],{"__ignoreMap":674},[679,47616,47617,47619,47621],{"class":681,"line":682},[679,47618,4505],{"class":693},[679,47620,45982],{"class":4508},[679,47622,4519],{"class":693},[679,47624,47625,47627,47629,47631,47633,47635],{"class":681,"line":790},[679,47626,11738],{"class":693},[679,47628,4509],{"class":4508},[679,47630,5578],{"class":880},[679,47632,686],{"class":693},[679,47634,46918],{"class":689},[679,47636,4519],{"class":693},[679,47638,47639],{"class":681,"line":892},[679,47640,47641],{"class":1400}," \u003C!-- markup here -->\n",[679,47643,47644,47646,47648],{"class":681,"line":901},[679,47645,11840],{"class":693},[679,47647,4509],{"class":4508},[679,47649,4519],{"class":693},[679,47651,47652,47654,47656],{"class":681,"line":909},[679,47653,4586],{"class":693},[679,47655,45982],{"class":4508},[679,47657,4519],{"class":693},[679,47659,47660],{"class":681,"line":918},[679,47661,889],{"emptyLinePlaceholder":797},[679,47663,47664,47666,47669],{"class":681,"line":935},[679,47665,4505],{"class":693},[679,47667,47668],{"class":4508},"script",[679,47670,4519],{"class":693},[679,47672,47673],{"class":681,"line":944},[679,47674,47675],{"class":1400}," // js here\n",[679,47677,47678,47680,47682],{"class":681,"line":959},[679,47679,4586],{"class":693},[679,47681,47668],{"class":4508},[679,47683,4519],{"class":693},[679,47685,47686],{"class":681,"line":964},[679,47687,889],{"emptyLinePlaceholder":797},[679,47689,47690,47692,47694],{"class":681,"line":977},[679,47691,4505],{"class":693},[679,47693,786],{"class":4508},[679,47695,4519],{"class":693},[679,47697,47698],{"class":681,"line":982},[679,47699,47700],{"class":1400}," /* css here */\n",[679,47702,47703,47705,47707],{"class":681,"line":988},[679,47704,4586],{"class":693},[679,47706,786],{"class":4508},[679,47708,4519],{"class":693},[651,47710,47711],{},"It should be really clear why we need a top-level element inside of the template tag now. Vue is going to compile everything inside of the template tag into the Virtual DOM. The Vue documentation describes the template syntax as:",[1004,47713,47714,47717,47720],{},[651,47715,47716],{},"Vue.js uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying Vue instance’s data. All Vue.js templates are valid HTML that can be parsed by spec-compliant browsers and HTML parsers.",[651,47718,47719],{},"Under the hood, Vue compiles the templates into Virtual DOM render functions. Combined with the reactivity system, Vue is able to intelligently figure out the minimal number of components to re-render and apply the minimal amount of DOM manipulations when the app state changes.",[651,47721,47722],{},"If you are familiar with Virtual DOM concepts and prefer the raw power of JavaScript, you can also directly write render functions instead of templates, with optional JSX support.",[4542,47724,9042],{"id":9041},[651,47726,47727],{},"If you have never used the template tag before I hope you learned something new today. Please feel free to ask any questions about this article or the demo I built.",[786,47729,47730],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":47732},[47733,47734,47740,47741],{"id":45928,"depth":790,"text":45917},{"id":45945,"depth":790,"text":45946,"children":47735},[47736,47737,47738,47739],{"id":45963,"depth":892,"text":45964},{"id":25134,"depth":892,"text":46520},{"id":47344,"depth":892,"text":47345},{"id":47598,"depth":892,"text":47599},{"id":47605,"depth":790,"text":47606},{"id":9041,"depth":790,"text":9042},"A look at what the HTML template tag is and how it can be used in Vanilla JavaScript as well as what its role in Vue is.",{"slug":47744,"date":47745,"published":797,"author":798,"tags":47746,"cover":47747},"html-template-tag","2019-02-13 22:30:00",[4498,25134,44169],"./html-template-cover.png",{"title":534,"description":47742},"blog/2019/02/13/html-template-tag","ArvJMtFP92GrJGn5iue2gL5G_SB1Te4sbrF5EbUm0Zs",{"id":47752,"title":531,"body":47753,"description":48374,"extension":793,"meta":48375,"navigation":797,"path":532,"seo":48380,"stem":48381,"__hash__":48382},"content/blog/2019/02/18/twitter-cards-meta-tags.md",{"type":648,"value":47754,"toc":48364},[47755,47762,47765,47769,47772,47778,47781,47784,47790,47793,47797,47800,47803,47880,47883,47926,47930,47933,48053,48056,48061,48064,48091,48094,48098,48101,48128,48131,48159,48165,48168,48333,48337,48344,48348,48351,48356,48358,48361],[651,47756,47757,47758,47761],{},"For those of you who haven't been reading my blog lately I am in the middle of converting my website from WordPress over to a static site generator called ",[812,47759,43848],{"href":43854,"rel":47760},[816]," that is built on VueJS. There are a lot of things that I took for granted when using a proven blogging platform like WordPress. In WordPress when I created a new blog post and then shared it on Twitter I got this nice display of my blog post as apposed to just a link.",[651,47763,47764],{},"This might not seem like a big thing to most but why write something if nobody reads it? Engagement is the name of game on social media platforms like Twitter and Twitter Cards (and images in general) are proven to increase engagement. In this article I will tell you what a Twitter Card is and how to add how to add Twitter Card meta tags to your blog.",[4542,47766,47768],{"id":47767},"what-is-a-twitter-card","What is a Twitter Card",[651,47770,47771],{},"Before we dive into the technical details on how to add Twitter cards to your blog it might make sense to understand what a Twitter Card actually is. I currently don't have any Twitter Cards setup for the home page of my website. After sitting down to write this article I realize that I should fix that. If I share out a link of my homepage on Twitter it looks like this.",[651,47773,47774],{},[660,47775],{"alt":47776,"src":47777},"Link with no Twitter Card","./twitter-link-no-card.png",[651,47779,47780],{},"Pretty boring right? No matter what my message is you aren't going to be very compelled to click that link.",[651,47782,47783],{},"Here is a recent blog post that I just posted a link to on Twitter.",[651,47785,47786],{},[660,47787],{"alt":47788,"src":47789},"Link with Twitter Card","./twitter-link-with-card.png",[651,47791,47792],{},"In this case you can see a nicely formatted card with a summary and an image. Now I will admit the image could use a little work as some text is running up against the side, but you get the idea. I think this is a real nice addition that you can add to your blog today and with just a few lines of code.",[4542,47794,47796],{"id":47795},"what-are-meta-tags","What are meta tags",[651,47798,47799],{},"If you're familiar with HTML chances are you have come across meta tags before. Metadata is data (information) about data. This means that the meta tag provides metadata about the HTML document. Metadata will not be displayed on the page, but will be machine parsable. This just means that it will not be displayed but it will exist in your pages source code.",[651,47801,47802],{},"Meta tags are defined in the head of an HTML document and contain the following attributes:",[1031,47804,47805,47816],{},[1034,47806,47807],{},[1037,47808,47809,47812,47814],{},[1040,47810,47811],{},"Attribute",[1040,47813,31784],{},[1040,47815,12285],{},[1050,47817,47818,47829,47839,47856],{},[1037,47819,47820,47823,47826],{},[1055,47821,47822],{},"charset",[1055,47824,47825],{},"character_set",[1055,47827,47828],{},"Specifies the character encoding for the HTML document",[1037,47830,47831,47834,47836],{},[1055,47832,47833],{},"content",[1055,47835,11464],{},[1055,47837,47838],{},"Gives the value associated with the http-equiv or name attribute",[1037,47840,47841,47844,47853],{},[1055,47842,47843],{},"http-equiv",[1055,47845,47846,47847,47849,47850,47852],{},"content-type",[41107,47848],{},"default-style",[41107,47851],{},"refresh",[1055,47854,47855],{},"Provides an HTTP header for the information/value of the content attribute",[1037,47857,47858,47860,47877],{},[1055,47859,16334],{},[1055,47861,47862,47863,6526,47865,47867,47868,47870,47871,47873,47874,47876],{},"application-name",[41107,47864],{},[41107,47866],{},"description",[41107,47869],{},"generator",[41107,47872],{},"keywords",[41107,47875],{},"viewport",[1055,47878,47879],{},"Specifies a name for the metadata",[651,47881,47882],{},"This is an example of what a meta tag looks like.",[669,47884,47886],{"className":4496,"code":47885,"language":4498,"meta":674,"style":674},"\u003Chead>\n \u003Cmeta name=\"description\" content=\"Welcome to my website!\" />\n\u003C/head>\n",[676,47887,47888,47896,47918],{"__ignoreMap":674},[679,47889,47890,47892,47894],{"class":681,"line":682},[679,47891,4505],{"class":693},[679,47893,11741],{"class":4508},[679,47895,4519],{"class":693},[679,47897,47898,47900,47902,47904,47906,47909,47911,47913,47916],{"class":681,"line":790},[679,47899,11738],{"class":693},[679,47901,11968],{"class":4508},[679,47903,5283],{"class":880},[679,47905,686],{"class":693},[679,47907,47908],{"class":689},"\"description\"",[679,47910,11995],{"class":880},[679,47912,686],{"class":693},[679,47914,47915],{"class":689},"\"Welcome to my website!\"",[679,47917,5387],{"class":693},[679,47919,47920,47922,47924],{"class":681,"line":892},[679,47921,4586],{"class":693},[679,47923,11741],{"class":4508},[679,47925,4519],{"class":693},[5909,47927,47929],{"id":47928},"meta-tag-use-cases","Meta Tag Use Cases",[651,47931,47932],{},"Now that you know what a meta tag it might be good to understand what they are useful for. Here are some of examples of meta tags that you might have come across already.",[669,47934,47936],{"className":4496,"code":47935,"language":4498,"meta":674,"style":674},"\u003Chead>\n \u003Cmeta charset=\"UTF-8\" />\n \u003Cmeta name=\"description\" content=\"Welcome to my website!\" />\n \u003Cmeta name=\"keywords\" content=\"HTML,CSS,JavaScript\" />\n \u003Cmeta name=\"author\" content=\"Dan Vega\" />\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\u003C/head>\n",[676,47937,47938,47946,47961,47981,48003,48024,48045],{"__ignoreMap":674},[679,47939,47940,47942,47944],{"class":681,"line":682},[679,47941,4505],{"class":693},[679,47943,11741],{"class":4508},[679,47945,4519],{"class":693},[679,47947,47948,47950,47952,47954,47956,47959],{"class":681,"line":790},[679,47949,11738],{"class":693},[679,47951,11968],{"class":4508},[679,47953,11971],{"class":880},[679,47955,686],{"class":693},[679,47957,47958],{"class":689},"\"UTF-8\"",[679,47960,5387],{"class":693},[679,47962,47963,47965,47967,47969,47971,47973,47975,47977,47979],{"class":681,"line":892},[679,47964,11738],{"class":693},[679,47966,11968],{"class":4508},[679,47968,5283],{"class":880},[679,47970,686],{"class":693},[679,47972,47908],{"class":689},[679,47974,11995],{"class":880},[679,47976,686],{"class":693},[679,47978,47915],{"class":689},[679,47980,5387],{"class":693},[679,47982,47983,47985,47987,47989,47991,47994,47996,47998,48001],{"class":681,"line":901},[679,47984,11738],{"class":693},[679,47986,11968],{"class":4508},[679,47988,5283],{"class":880},[679,47990,686],{"class":693},[679,47992,47993],{"class":689},"\"keywords\"",[679,47995,11995],{"class":880},[679,47997,686],{"class":693},[679,47999,48000],{"class":689},"\"HTML,CSS,JavaScript\"",[679,48002,5387],{"class":693},[679,48004,48005,48007,48009,48011,48013,48016,48018,48020,48022],{"class":681,"line":909},[679,48006,11738],{"class":693},[679,48008,11968],{"class":4508},[679,48010,5283],{"class":880},[679,48012,686],{"class":693},[679,48014,48015],{"class":689},"\"author\"",[679,48017,11995],{"class":880},[679,48019,686],{"class":693},[679,48021,45450],{"class":689},[679,48023,5387],{"class":693},[679,48025,48026,48028,48030,48032,48034,48036,48038,48040,48043],{"class":681,"line":918},[679,48027,11738],{"class":693},[679,48029,11968],{"class":4508},[679,48031,5283],{"class":880},[679,48033,686],{"class":693},[679,48035,12015],{"class":689},[679,48037,11995],{"class":880},[679,48039,686],{"class":693},[679,48041,48042],{"class":689},"\"width=device-width, initial-scale=1.0\"",[679,48044,5387],{"class":693},[679,48046,48047,48049,48051],{"class":681,"line":935},[679,48048,4586],{"class":693},[679,48050,11741],{"class":4508},[679,48052,4519],{"class":693},[651,48054,48055],{},"If you done any kind of Search Engine Optimization (SEO) you have probably come across the meta description.",[1004,48057,48058],{},[651,48059,48060],{},"The meta description is a snippet of up to about 155 characters – a tag in HTML – which summarizes a page's content. Search engines show the meta description in search results mostly when the searched-for phrase is within the description, so optimizing the meta description is crucial for on-page SEO .",[651,48062,48063],{},"Most meta tags are nothing more than name and content, in fact you could create your own if you wanted to.",[669,48065,48067],{"className":4496,"code":48066,"language":4498,"meta":674,"style":674},"\u003Cmeta name=\"msg\" content=\"hello meta tags\" />\n",[676,48068,48069],{"__ignoreMap":674},[679,48070,48071,48073,48075,48077,48079,48082,48084,48086,48089],{"class":681,"line":682},[679,48072,4505],{"class":693},[679,48074,11968],{"class":4508},[679,48076,5283],{"class":880},[679,48078,686],{"class":693},[679,48080,48081],{"class":689},"\"msg\"",[679,48083,11995],{"class":880},[679,48085,686],{"class":693},[679,48087,48088],{"class":689},"\"hello meta tags\"",[679,48090,5387],{"class":693},[651,48092,48093],{},"While this is valid syntax it really doesn't do anything. If you were to write some program that scanned html looking for this specific meta tag than it would make sense to include, otherwise its useless.",[4542,48095,48097],{"id":48096},"adding-twitter-cards-to-your-blog-or-website","Adding Twitter Cards to your blog or website",[651,48099,48100],{},"No that you know a little bit more about meta tags lets take that knowledge and apply it to Twitter Cards. The first meta tag you need to set is the twitter:card meta tag.",[669,48102,48104],{"className":4496,"code":48103,"language":4498,"meta":674,"style":674},"\u003Cmeta name=\"twitter:card\" content=\"summary_large_image\" />\n",[676,48105,48106],{"__ignoreMap":674},[679,48107,48108,48110,48112,48114,48116,48119,48121,48123,48126],{"class":681,"line":682},[679,48109,4505],{"class":693},[679,48111,11968],{"class":4508},[679,48113,5283],{"class":880},[679,48115,686],{"class":693},[679,48117,48118],{"class":689},"\"twitter:card\"",[679,48120,11995],{"class":880},[679,48122,686],{"class":693},[679,48124,48125],{"class":689},"\"summary_large_image\"",[679,48127,5387],{"class":693},[651,48129,48130],{},"This tag can have one of the following values",[5316,48132,48133,48140,48146,48153],{},[5332,48134,48135],{},[812,48136,48139],{"href":48137,"rel":48138},"https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary",[816],"summary",[5332,48141,48142],{},[812,48143,48145],{"href":48137,"rel":48144},[816],"summary_large_image",[5332,48147,48148],{},[812,48149,48152],{"href":48150,"rel":48151},"https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card",[816],"player",[5332,48154,48155],{},[812,48156,29014],{"href":48157,"rel":48158},"https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/app-card",[816],[651,48160,48161,48162,48164],{},"In my case and the example I showed you earlier of the blog post I am using ",[2939,48163,48145],{},". The other tags are pretty self explinatory if you compare them to the example card. The one thing I will say is that make sure you give the twitter:image tag the full path to the image you want to use.",[651,48166,48167],{},"Here are all of the tags I used for that blog post and what it looks like.",[669,48169,48171],{"className":4496,"code":48170,"language":4498,"meta":674,"style":674},"\u003Chead>\n \u003Cmeta name=\"twitter:card\" content=\"summary_large_image\" />\n \u003Cmeta\n name=\"twitter:description\"\n content=\"How to create your first npm package and publish it.\"\n />\n \u003Cmeta name=\"twitter:title\" content=\"Creating your first npm package\" />\n \u003Cmeta name=\"twitter:site\" content=\"@therealdanvega\" />\n \u003Cmeta\n name=\"twitter:image\"\n content=\"https://www.danvega.me/assets/static/npm_cover.bd64798.eced3da.png\"\n />\n \u003Cmeta name=\"twitter:creator\" content=\"@therealdanvega\" />\n\u003C/head>\n",[676,48172,48173,48181,48201,48208,48217,48227,48232,48254,48276,48282,48291,48300,48304,48325],{"__ignoreMap":674},[679,48174,48175,48177,48179],{"class":681,"line":682},[679,48176,4505],{"class":693},[679,48178,11741],{"class":4508},[679,48180,4519],{"class":693},[679,48182,48183,48185,48187,48189,48191,48193,48195,48197,48199],{"class":681,"line":790},[679,48184,11738],{"class":693},[679,48186,11968],{"class":4508},[679,48188,5283],{"class":880},[679,48190,686],{"class":693},[679,48192,48118],{"class":689},[679,48194,11995],{"class":880},[679,48196,686],{"class":693},[679,48198,48125],{"class":689},[679,48200,5387],{"class":693},[679,48202,48203,48205],{"class":681,"line":892},[679,48204,11738],{"class":693},[679,48206,48207],{"class":4508},"meta\n",[679,48209,48210,48212,48214],{"class":681,"line":901},[679,48211,35427],{"class":880},[679,48213,686],{"class":693},[679,48215,48216],{"class":689},"\"twitter:description\"\n",[679,48218,48219,48222,48224],{"class":681,"line":909},[679,48220,48221],{"class":880}," content",[679,48223,686],{"class":693},[679,48225,48226],{"class":689},"\"How to create your first npm package and publish it.\"\n",[679,48228,48229],{"class":681,"line":918},[679,48230,48231],{"class":693}," />\n",[679,48233,48234,48236,48238,48240,48242,48245,48247,48249,48252],{"class":681,"line":935},[679,48235,11738],{"class":693},[679,48237,11968],{"class":4508},[679,48239,5283],{"class":880},[679,48241,686],{"class":693},[679,48243,48244],{"class":689},"\"twitter:title\"",[679,48246,11995],{"class":880},[679,48248,686],{"class":693},[679,48250,48251],{"class":689},"\"Creating your first npm package\"",[679,48253,5387],{"class":693},[679,48255,48256,48258,48260,48262,48264,48267,48269,48271,48274],{"class":681,"line":944},[679,48257,11738],{"class":693},[679,48259,11968],{"class":4508},[679,48261,5283],{"class":880},[679,48263,686],{"class":693},[679,48265,48266],{"class":689},"\"twitter:site\"",[679,48268,11995],{"class":880},[679,48270,686],{"class":693},[679,48272,48273],{"class":689},"\"@therealdanvega\"",[679,48275,5387],{"class":693},[679,48277,48278,48280],{"class":681,"line":959},[679,48279,11738],{"class":693},[679,48281,48207],{"class":4508},[679,48283,48284,48286,48288],{"class":681,"line":964},[679,48285,35427],{"class":880},[679,48287,686],{"class":693},[679,48289,48290],{"class":689},"\"twitter:image\"\n",[679,48292,48293,48295,48297],{"class":681,"line":977},[679,48294,48221],{"class":880},[679,48296,686],{"class":693},[679,48298,48299],{"class":689},"\"https://www.danvega.me/assets/static/npm_cover.bd64798.eced3da.png\"\n",[679,48301,48302],{"class":681,"line":982},[679,48303,48231],{"class":693},[679,48305,48306,48308,48310,48312,48314,48317,48319,48321,48323],{"class":681,"line":988},[679,48307,11738],{"class":693},[679,48309,11968],{"class":4508},[679,48311,5283],{"class":880},[679,48313,686],{"class":693},[679,48315,48316],{"class":689},"\"twitter:creator\"",[679,48318,11995],{"class":880},[679,48320,686],{"class":693},[679,48322,48273],{"class":689},[679,48324,5387],{"class":693},[679,48326,48327,48329,48331],{"class":681,"line":993},[679,48328,4586],{"class":693},[679,48330,11741],{"class":4508},[679,48332,4519],{"class":693},[651,48334,48335],{},[660,48336],{"alt":47788,"src":47789},[651,48338,48339,48340,664],{},"If you want to learn more about the different types of cards you can create and all of the options please read through the ",[812,48341,40844],{"href":48342,"rel":48343},"https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards",[816],[5909,48345,48347],{"id":48346},"twitter-card-validator","Twitter Card Validator",[651,48349,48350],{},"If you ever have problems with your Twitter Cards not displaying correctly there is a way to test them. You can use the Twitter Card Validator, plug in the url you want to test and see what the outcome is along with some debugging information. Here I have entered another blog post and I get a preview of what my twitter card is going to look like.",[651,48352,48353],{},[660,48354],{"alt":48347,"src":48355},"./twitter-card-validator.png",[4542,48357,9042],{"id":9041},[651,48359,48360],{},"I mentioned this at the start of this article that I was converting my site from WordPress to Gridsome. If you're interested in how I added Twitter cards to this blog let me know. If you're also interested in how to add similar meta tags for other social networks you can do some research on open graph or just let me know that you would like to see another article on that.",[786,48362,48363],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":48365},[48366,48367,48370,48373],{"id":47767,"depth":790,"text":47768},{"id":47795,"depth":790,"text":47796,"children":48368},[48369],{"id":47928,"depth":892,"text":47929},{"id":48096,"depth":790,"text":48097,"children":48371},[48372],{"id":48346,"depth":892,"text":48347},{"id":9041,"depth":790,"text":9042},"In this tutorial you will learn what a Twitter Card is along with step by step instructions how to add them to your blog and validate that they are working.",{"slug":48376,"date":48377,"published":797,"author":798,"tags":48378,"cover":48379},"twitter-cards-meta-tags","2019-02-18 22:00:00",[4498],"./twitter-cards.png",{"title":531,"description":48374},"blog/2019/02/18/twitter-cards-meta-tags","HfxRAf8gwItKdh9bHmwDBg4pqSKhKvQGiIzSbaW-U3s",{"id":48384,"title":528,"body":48385,"description":49434,"extension":793,"meta":49435,"navigation":797,"path":529,"seo":49440,"stem":49441,"__hash__":49442},"content/blog/2019/02/21/node-recursive-directories.md",{"type":648,"value":48386,"toc":49429},[48387,48390,48393,48411,48420,48424,48431,48442,48448,48451,48495,48504,48514,48522,48692,48695,48701,48704,48707,48863,48867,48882,48917,48926,48949,48960,48976,48979,49162,49165,49171,49190,49195,49198,49417,49419,49422,49426],[651,48388,48389],{},"This is going to be a quick tutorial but I think it's one that I want to share. I am in the middle of migrating close to 1,000 blog posts from WordPress to Gridsome, a static site generator.",[651,48391,48392],{},"As any good (lazy) developer would, I did some searching around for a migration script. With Gridsome being a fairly new project I knew that my chances of finding a script would be pretty slim. After spending about 10 minutes looking around I found that my assumptions were true.",[651,48394,48395,48396,2797,48401,48406,48407,48410],{},"Not to worry because I wasn't really doing anything that was Gridsome specific. What I wanted to do was convert a bunch of WordPress posts into Markdown. With all of the great blogs written on top of static site generators like ",[812,48397,48400],{"href":48398,"rel":48399},"https://gohugo.io/",[816],"Hugo",[812,48402,48405],{"href":48403,"rel":48404},"https://jekyllrb.com/",[816],"Jekyll",", and ",[812,48408,43821],{"href":43819,"rel":48409},[816]," I was sure that I could find something close to what I was looking for.",[651,48412,48413,48414,48419],{},"Sure enough, I came across a this awesome ",[812,48415,48418],{"href":48416,"rel":48417},"https://github.com/konsalex/gatsby-wordpress-migrate",[816],"Gatsby to WordPress"," migration script by Costa Alexoglou. This script will take your WordPress posts (exported out as XML) and convert them to Markdown. This was a good start for me but one of the things I needed to do was put the markdown files into a specific folder format.",[4542,48421,48423],{"id":48422},"directory-format","Directory Format",[651,48425,48426,48427,48430],{},"I needed to stick to the url format that my existing posts were in which was ",[2939,48428,48429],{},"/blog/:year/:month/:day/:slug",". I also needed to make sure that parts of the date were formatted in the following format:",[5316,48432,48433,48436,48439],{},[5332,48434,48435],{},"Year: 4 digits",[5332,48437,48438],{},"Month: 2 digits",[5332,48440,48441],{},"Day: 2 digits",[651,48443,48444],{},[660,48445],{"alt":48446,"src":48447},"Calendar","./undraw_calendar_dutt.png",[651,48449,48450],{},"Before I could even begin to worry about creating a new directory (or directories) I needed to get the 3 parts I needed from the date for that blog post in the format I needed them. The first thing I did was to create a new date called createdOn from the post date.",[669,48452,48454],{"className":25132,"code":48453,"language":25134,"meta":674,"style":674},"const header = {\n date: \"2019-02-21 08:00:00\"\n};\nconst createdOn = new Date(header.date);\n",[676,48455,48456,48467,48475,48479],{"__ignoreMap":674},[679,48457,48458,48460,48463,48465],{"class":681,"line":682},[679,48459,45172],{"class":685},[679,48461,48462],{"class":931}," header",[679,48464,6883],{"class":685},[679,48466,884],{"class":693},[679,48468,48469,48472],{"class":681,"line":790},[679,48470,48471],{"class":693}," date: ",[679,48473,48474],{"class":689},"\"2019-02-21 08:00:00\"\n",[679,48476,48477],{"class":681,"line":892},[679,48478,44055],{"class":693},[679,48480,48481,48483,48486,48488,48490,48492],{"class":681,"line":901},[679,48482,45172],{"class":685},[679,48484,48485],{"class":931}," createdOn",[679,48487,6883],{"class":685},[679,48489,2054],{"class":685},[679,48491,17511],{"class":880},[679,48493,48494],{"class":693},"(header.date);\n",[651,48496,48497,48498,48503],{},"Now that I had a ",[812,48499,48502],{"href":48500,"rel":48501},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date",[816],"Date"," object I could use the API to get the different parts that I wanted. The year was the easiest while I had to do some work to get the month and day in the format I wanted them in.",[651,48505,48506,48507,27319,48510,48513],{},"The month and day were not as easy. First off both ",[676,48508,48509],{},"getMonth()",[676,48511,48512],{},"getDate()"," of them return to you 1,2,3... and I needed them in 2 digit format 01,02,03. For both of those I used a ternary operator to pad it with a 0 if the number was less than 10.",[651,48515,48516,48517,48519,48520,664],{},"The other gotcha here is that ",[676,48518,48509],{}," returns the month as a zero based value so January is 0. Knowing that I will need to add 1 to each value returned from ",[676,48521,48509],{},[669,48523,48525],{"className":25132,"code":48524,"language":25134,"meta":674,"style":674},"const header = {\n date: \"2019-02-21 08:00:00\"\n};\nconst createdOn = new Date(header.date);\nconst year = createdOn.getFullYear();\nconst month = `${\n createdOn.getMonth() + 1 \u003C 10 ? \"0\" : \"\"\n}${createdOn.getMonth() + 1}`;\nconst day = `${createdOn.getDate() \u003C 10 ? \"0\" : \"\"}${createdOn.getDate()}`;\n",[676,48526,48527,48537,48543,48547,48561,48578,48590,48623,48645],{"__ignoreMap":674},[679,48528,48529,48531,48533,48535],{"class":681,"line":682},[679,48530,45172],{"class":685},[679,48532,48462],{"class":931},[679,48534,6883],{"class":685},[679,48536,884],{"class":693},[679,48538,48539,48541],{"class":681,"line":790},[679,48540,48471],{"class":693},[679,48542,48474],{"class":689},[679,48544,48545],{"class":681,"line":892},[679,48546,44055],{"class":693},[679,48548,48549,48551,48553,48555,48557,48559],{"class":681,"line":901},[679,48550,45172],{"class":685},[679,48552,48485],{"class":931},[679,48554,6883],{"class":685},[679,48556,2054],{"class":685},[679,48558,17511],{"class":880},[679,48560,48494],{"class":693},[679,48562,48563,48565,48568,48570,48573,48576],{"class":681,"line":909},[679,48564,45172],{"class":685},[679,48566,48567],{"class":931}," year",[679,48569,6883],{"class":685},[679,48571,48572],{"class":693}," createdOn.",[679,48574,48575],{"class":880},"getFullYear",[679,48577,9317],{"class":693},[679,48579,48580,48582,48585,48587],{"class":681,"line":918},[679,48581,45172],{"class":685},[679,48583,48584],{"class":931}," month",[679,48586,6883],{"class":685},[679,48588,48589],{"class":689}," `${\n",[679,48591,48592,48595,48597,48600,48602,48604,48607,48610,48613,48615,48618,48620],{"class":681,"line":935},[679,48593,48594],{"class":693}," createdOn",[679,48596,664],{"class":689},[679,48598,48599],{"class":880},"getMonth",[679,48601,6700],{"class":689},[679,48603,3065],{"class":685},[679,48605,48606],{"class":931}," 1",[679,48608,48609],{"class":685}," \u003C",[679,48611,48612],{"class":931}," 10",[679,48614,14615],{"class":685},[679,48616,48617],{"class":689}," \"0\"",[679,48619,3033],{"class":685},[679,48621,48622],{"class":689}," \"\"\n",[679,48624,48625,48628,48631,48633,48635,48637,48639,48641,48643],{"class":681,"line":944},[679,48626,48627],{"class":689},"}${",[679,48629,48630],{"class":693},"createdOn",[679,48632,664],{"class":689},[679,48634,48599],{"class":880},[679,48636,6700],{"class":689},[679,48638,3065],{"class":685},[679,48640,48606],{"class":931},[679,48642,47580],{"class":689},[679,48644,1186],{"class":693},[679,48646,48647,48649,48652,48654,48657,48659,48661,48664,48666,48668,48670,48672,48674,48676,48679,48681,48683,48685,48688,48690],{"class":681,"line":959},[679,48648,45172],{"class":685},[679,48650,48651],{"class":931}," day",[679,48653,6883],{"class":685},[679,48655,48656],{"class":689}," `${",[679,48658,48630],{"class":693},[679,48660,664],{"class":689},[679,48662,48663],{"class":880},"getDate",[679,48665,6700],{"class":689},[679,48667,4505],{"class":685},[679,48669,48612],{"class":931},[679,48671,14615],{"class":685},[679,48673,48617],{"class":689},[679,48675,3033],{"class":685},[679,48677,48678],{"class":689}," \"\"}${",[679,48680,48630],{"class":693},[679,48682,664],{"class":689},[679,48684,48663],{"class":880},[679,48686,48687],{"class":689},"()",[679,48689,47580],{"class":689},[679,48691,1186],{"class":693},[651,48693,48694],{},"While this wasn't super hard it isn't the easiest or most elegant solution. Dates just always seem to be a pain point in every language and they all have their quirks. I thought I would take the Twitter and see if anyone could help me out.",[651,48696,48697],{},[812,48698,48699],{"href":48699,"rel":48700},"https://twitter.com/therealdanvega/status/1098667133112934401",[816],[651,48702,48703],{},"I didn't get any real good answers so for now that is what I am going to stick with. It was also pointed out to me that IE11 won't support creating a Date from a string. You should look into browser compatibility when you have to worry about it but in my case this is just a local script that I am running so I'm not worried.",[651,48705,48706],{},"So if you're following along so far you should have something that looks like this.",[669,48708,48710],{"className":25132,"code":48709,"language":25134,"meta":674,"style":674},"const header = {\n date: \"2019-02-20 08:00:00\"\n};\n\nconst createdOn = new Date(header.date);\nconst year = createdOn.getFullYear();\nconst month = `${\n createdOn.getMonth() + 1 \u003C 10 ? \"0\" : \"\"\n}${createdOn.getMonth() + 1}`;\nconst day = `${createdOn.getDate() \u003C 10 ? \"0\" : \"\"}${createdOn.getDate()}`;\n",[676,48711,48712,48722,48729,48733,48737,48751,48765,48775,48801,48821],{"__ignoreMap":674},[679,48713,48714,48716,48718,48720],{"class":681,"line":682},[679,48715,45172],{"class":685},[679,48717,48462],{"class":931},[679,48719,6883],{"class":685},[679,48721,884],{"class":693},[679,48723,48724,48726],{"class":681,"line":790},[679,48725,48471],{"class":693},[679,48727,48728],{"class":689},"\"2019-02-20 08:00:00\"\n",[679,48730,48731],{"class":681,"line":892},[679,48732,44055],{"class":693},[679,48734,48735],{"class":681,"line":901},[679,48736,889],{"emptyLinePlaceholder":797},[679,48738,48739,48741,48743,48745,48747,48749],{"class":681,"line":909},[679,48740,45172],{"class":685},[679,48742,48485],{"class":931},[679,48744,6883],{"class":685},[679,48746,2054],{"class":685},[679,48748,17511],{"class":880},[679,48750,48494],{"class":693},[679,48752,48753,48755,48757,48759,48761,48763],{"class":681,"line":918},[679,48754,45172],{"class":685},[679,48756,48567],{"class":931},[679,48758,6883],{"class":685},[679,48760,48572],{"class":693},[679,48762,48575],{"class":880},[679,48764,9317],{"class":693},[679,48766,48767,48769,48771,48773],{"class":681,"line":935},[679,48768,45172],{"class":685},[679,48770,48584],{"class":931},[679,48772,6883],{"class":685},[679,48774,48589],{"class":689},[679,48776,48777,48779,48781,48783,48785,48787,48789,48791,48793,48795,48797,48799],{"class":681,"line":944},[679,48778,48594],{"class":693},[679,48780,664],{"class":689},[679,48782,48599],{"class":880},[679,48784,6700],{"class":689},[679,48786,3065],{"class":685},[679,48788,48606],{"class":931},[679,48790,48609],{"class":685},[679,48792,48612],{"class":931},[679,48794,14615],{"class":685},[679,48796,48617],{"class":689},[679,48798,3033],{"class":685},[679,48800,48622],{"class":689},[679,48802,48803,48805,48807,48809,48811,48813,48815,48817,48819],{"class":681,"line":959},[679,48804,48627],{"class":689},[679,48806,48630],{"class":693},[679,48808,664],{"class":689},[679,48810,48599],{"class":880},[679,48812,6700],{"class":689},[679,48814,3065],{"class":685},[679,48816,48606],{"class":931},[679,48818,47580],{"class":689},[679,48820,1186],{"class":693},[679,48822,48823,48825,48827,48829,48831,48833,48835,48837,48839,48841,48843,48845,48847,48849,48851,48853,48855,48857,48859,48861],{"class":681,"line":964},[679,48824,45172],{"class":685},[679,48826,48651],{"class":931},[679,48828,6883],{"class":685},[679,48830,48656],{"class":689},[679,48832,48630],{"class":693},[679,48834,664],{"class":689},[679,48836,48663],{"class":880},[679,48838,6700],{"class":689},[679,48840,4505],{"class":685},[679,48842,48612],{"class":931},[679,48844,14615],{"class":685},[679,48846,48617],{"class":689},[679,48848,3033],{"class":685},[679,48850,48678],{"class":689},[679,48852,48630],{"class":693},[679,48854,664],{"class":689},[679,48856,48663],{"class":880},[679,48858,48687],{"class":689},[679,48860,47580],{"class":689},[679,48862,1186],{"class":693},[4542,48864,48866],{"id":48865},"using-node-to-write-directories","Using node to write directories",[651,48868,48869,48870,48873,48874,48877,48878,48881],{},"Now that we have the parts we need for the directory lets create the full blog post directory. I am storing everything in a relative folder to this script in the form of ",[676,48871,48872],{},"/blog/:year/:month/:day"," and then the name of the file would be ",[676,48875,48876],{},":slug.md",". So I am going to start by creating a variable called ",[676,48879,48880],{},"blogPostFolder"," and I will create the path using a template literal.",[669,48883,48885],{"className":25132,"code":48884,"language":25134,"meta":674,"style":674},"const blogPostFolder = `./blog/${year}/${month}/${day}`;\n",[676,48886,48887],{"__ignoreMap":674},[679,48888,48889,48891,48894,48896,48899,48902,48905,48908,48910,48913,48915],{"class":681,"line":682},[679,48890,45172],{"class":685},[679,48892,48893],{"class":931}," blogPostFolder",[679,48895,6883],{"class":685},[679,48897,48898],{"class":689}," `./blog/${",[679,48900,48901],{"class":693},"year",[679,48903,48904],{"class":689},"}/${",[679,48906,48907],{"class":693},"month",[679,48909,48904],{"class":689},[679,48911,48912],{"class":693},"day",[679,48914,47580],{"class":689},[679,48916,1186],{"class":693},[651,48918,48919,48920,48925],{},"Next we are going to tap into ",[812,48921,48924],{"href":48922,"rel":48923},"https://nodejs.org/api/fs.html",[816],"Node's Files System API"," to actually create the directory. To use the file system module",[669,48927,48929],{"className":25132,"code":48928,"language":25134,"meta":674,"style":674},"const fs = require(\"fs\");\n",[676,48930,48931],{"__ignoreMap":674},[679,48932,48933,48935,48938,48940,48942,48944,48947],{"class":681,"line":682},[679,48934,45172],{"class":685},[679,48936,48937],{"class":931}," fs",[679,48939,6883],{"class":685},[679,48941,45180],{"class":880},[679,48943,745],{"class":693},[679,48945,48946],{"class":689},"\"fs\"",[679,48948,1208],{"class":693},[651,48950,48951,48952,48959],{},"There is a method in the file system API to make a directory. The default method is asynchronous but for our case we will actually want this to be synchronous so we will use ",[812,48953,48956],{"href":48954,"rel":48955},"https://nodejs.org/api/fs.html#fs_fs_mkdirsync_path_options",[816],[676,48957,48958],{},"mkdirSync()",". The first argument to this method is the path for the directory that you want to create. If you try and run this method using the blog post folder path:",[669,48961,48963],{"className":25132,"code":48962,"language":25134,"meta":674,"style":674},"fs.mkdirSync(blogPostFolder);\n",[676,48964,48965],{"__ignoreMap":674},[679,48966,48967,48970,48973],{"class":681,"line":682},[679,48968,48969],{"class":693},"fs.",[679,48971,48972],{"class":880},"mkdirSync",[679,48974,48975],{"class":693},"(blogPostFolder);\n",[651,48977,48978],{},"You will get the following error:",[669,48980,48982],{"className":5851,"code":48981,"language":5853,"meta":674,"style":674},"vega recursive-dirs $ node app.js\n./blog/2019/02/21\nfs.js:115\n throw err;\n ^\n\nError: ENOENT: no such file or directory, mkdir './blog/2019/02/21'\n at Object.mkdirSync (fs.js:753:3)\n at Object.\u003Canonymous> (/Users/vega/dev/node/recursive-dirs/app.js:16:4)\n at Module._compile (internal/modules/cjs/loader.js:689:30)\n at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)\n at Module.load (internal/modules/cjs/loader.js:599:32)\n at tryModuleLoad (internal/modules/cjs/loader.js:538:12)\n at Function.Module._load (internal/modules/cjs/loader.js:530:3)\n at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)\n at startup (internal/bootstrap/node.js:283:19)\n at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)\n",[676,48983,48984,48999,49004,49009,49019,49024,49028,49054,49064,49083,49093,49103,49113,49123,49133,49143,49152],{"__ignoreMap":674},[679,48985,48986,48988,48991,48994,48996],{"class":681,"line":682},[679,48987,45509],{"class":880},[679,48989,48990],{"class":689}," recursive-dirs",[679,48992,48993],{"class":693}," $ ",[679,48995,27318],{"class":689},[679,48997,48998],{"class":689}," app.js\n",[679,49000,49001],{"class":681,"line":790},[679,49002,49003],{"class":880},"./blog/2019/02/21\n",[679,49005,49006],{"class":681,"line":892},[679,49007,49008],{"class":880},"fs.js:115\n",[679,49010,49011,49014,49017],{"class":681,"line":901},[679,49012,49013],{"class":880}," throw",[679,49015,49016],{"class":689}," err",[679,49018,1186],{"class":693},[679,49020,49021],{"class":681,"line":909},[679,49022,49023],{"class":880}," ^\n",[679,49025,49026],{"class":681,"line":918},[679,49027,889],{"emptyLinePlaceholder":797},[679,49029,49030,49033,49036,49039,49041,49043,49045,49048,49051],{"class":681,"line":935},[679,49031,49032],{"class":880},"Error:",[679,49034,49035],{"class":689}," ENOENT:",[679,49037,49038],{"class":689}," no",[679,49040,24742],{"class":689},[679,49042,29025],{"class":689},[679,49044,6991],{"class":689},[679,49046,49047],{"class":689}," directory,",[679,49049,49050],{"class":689}," mkdir",[679,49052,49053],{"class":689}," './blog/2019/02/21'\n",[679,49055,49056,49058,49061],{"class":681,"line":944},[679,49057,8024],{"class":880},[679,49059,49060],{"class":689}," Object.mkdirSync",[679,49062,49063],{"class":693}," (fs.js:753:3)\n",[679,49065,49066,49068,49071,49073,49076,49078,49080],{"class":681,"line":959},[679,49067,8024],{"class":880},[679,49069,49070],{"class":689}," Object.",[679,49072,4505],{"class":685},[679,49074,49075],{"class":689},"anonymou",[679,49077,20413],{"class":693},[679,49079,5860],{"class":685},[679,49081,49082],{"class":693}," (/Users/vega/dev/node/recursive-dirs/app.js:16:4)\n",[679,49084,49085,49087,49090],{"class":681,"line":964},[679,49086,8024],{"class":880},[679,49088,49089],{"class":689}," Module._compile",[679,49091,49092],{"class":693}," (internal/modules/cjs/loader.js:689:30)\n",[679,49094,49095,49097,49100],{"class":681,"line":977},[679,49096,8024],{"class":880},[679,49098,49099],{"class":689}," Object.Module._extensions..js",[679,49101,49102],{"class":693}," (internal/modules/cjs/loader.js:700:10)\n",[679,49104,49105,49107,49110],{"class":681,"line":982},[679,49106,8024],{"class":880},[679,49108,49109],{"class":689}," Module.load",[679,49111,49112],{"class":693}," (internal/modules/cjs/loader.js:599:32)\n",[679,49114,49115,49117,49120],{"class":681,"line":988},[679,49116,8024],{"class":880},[679,49118,49119],{"class":689}," tryModuleLoad",[679,49121,49122],{"class":693}," (internal/modules/cjs/loader.js:538:12)\n",[679,49124,49125,49127,49130],{"class":681,"line":993},[679,49126,8024],{"class":880},[679,49128,49129],{"class":689}," Function.Module._load",[679,49131,49132],{"class":693}," (internal/modules/cjs/loader.js:530:3)\n",[679,49134,49135,49137,49140],{"class":681,"line":2129},[679,49136,8024],{"class":880},[679,49138,49139],{"class":689}," Function.Module.runMain",[679,49141,49142],{"class":693}," (internal/modules/cjs/loader.js:742:12)\n",[679,49144,49145,49147,49149],{"class":681,"line":2140},[679,49146,8024],{"class":880},[679,49148,34608],{"class":689},[679,49150,49151],{"class":693}," (internal/bootstrap/node.js:283:19)\n",[679,49153,49154,49156,49159],{"class":681,"line":2145},[679,49155,8024],{"class":880},[679,49157,49158],{"class":689}," bootstrapNodeJSCore",[679,49160,49161],{"class":693}," (internal/bootstrap/node.js:743:3)\n",[651,49163,49164],{},"This is because there is no blog folder yet and if there is no parent folder how is it going to create sub folders. If you create a blog folder you will have the same problem because there is no 2019 folder.",[651,49166,49167,49168,49170],{},"The solution to this is to recursively create directories but by default this isn't the case. The 2nd argument to the ",[676,49169,48958],{}," method is an options object that contains a property called recursive. If you set this true and run your script again everything should work just fine.",[669,49172,49174],{"className":25132,"code":49173,"language":25134,"meta":674,"style":674},"fs.mkdirSync(blogPostFolder, { recursive: true });\n",[676,49175,49176],{"__ignoreMap":674},[679,49177,49178,49180,49182,49185,49187],{"class":681,"line":682},[679,49179,48969],{"class":693},[679,49181,48972],{"class":880},[679,49183,49184],{"class":693},"(blogPostFolder, { recursive: ",[679,49186,3441],{"class":931},[679,49188,49189],{"class":693}," });\n",[1004,49191,49192],{},[651,49193,49194],{},"I am not 100% sure on this but it appears this option is working as of v10.15.1",[651,49196,49197],{},"If you have been following along you should end up with something like this",[669,49199,49201],{"className":25132,"code":49200,"language":25134,"meta":674,"style":674},"const fs = require(\"fs\");\n\nconst header = {\n date: \"2019-02-20 08:00:00\"\n};\n\nconst createdOn = new Date(header.date);\nconst year = createdOn.getFullYear();\nconst month = `${\n createdOn.getMonth() + 1 \u003C 10 ? \"0\" : \"\"\n}${createdOn.getMonth() + 1}`;\nconst day = `${createdOn.getDate() \u003C 10 ? \"0\" : \"\"}${createdOn.getDate()}`;\n\nconst blogPostFolder = `./blog/${year}/${month}/${day}`;\n\nfs.mkdirSync(blogPostFolder, { recursive: true });\n",[676,49202,49203,49219,49223,49233,49239,49243,49247,49261,49275,49285,49311,49331,49373,49377,49401,49405],{"__ignoreMap":674},[679,49204,49205,49207,49209,49211,49213,49215,49217],{"class":681,"line":682},[679,49206,45172],{"class":685},[679,49208,48937],{"class":931},[679,49210,6883],{"class":685},[679,49212,45180],{"class":880},[679,49214,745],{"class":693},[679,49216,48946],{"class":689},[679,49218,1208],{"class":693},[679,49220,49221],{"class":681,"line":790},[679,49222,889],{"emptyLinePlaceholder":797},[679,49224,49225,49227,49229,49231],{"class":681,"line":892},[679,49226,45172],{"class":685},[679,49228,48462],{"class":931},[679,49230,6883],{"class":685},[679,49232,884],{"class":693},[679,49234,49235,49237],{"class":681,"line":901},[679,49236,48471],{"class":693},[679,49238,48728],{"class":689},[679,49240,49241],{"class":681,"line":909},[679,49242,44055],{"class":693},[679,49244,49245],{"class":681,"line":918},[679,49246,889],{"emptyLinePlaceholder":797},[679,49248,49249,49251,49253,49255,49257,49259],{"class":681,"line":935},[679,49250,45172],{"class":685},[679,49252,48485],{"class":931},[679,49254,6883],{"class":685},[679,49256,2054],{"class":685},[679,49258,17511],{"class":880},[679,49260,48494],{"class":693},[679,49262,49263,49265,49267,49269,49271,49273],{"class":681,"line":944},[679,49264,45172],{"class":685},[679,49266,48567],{"class":931},[679,49268,6883],{"class":685},[679,49270,48572],{"class":693},[679,49272,48575],{"class":880},[679,49274,9317],{"class":693},[679,49276,49277,49279,49281,49283],{"class":681,"line":959},[679,49278,45172],{"class":685},[679,49280,48584],{"class":931},[679,49282,6883],{"class":685},[679,49284,48589],{"class":689},[679,49286,49287,49289,49291,49293,49295,49297,49299,49301,49303,49305,49307,49309],{"class":681,"line":964},[679,49288,48594],{"class":693},[679,49290,664],{"class":689},[679,49292,48599],{"class":880},[679,49294,6700],{"class":689},[679,49296,3065],{"class":685},[679,49298,48606],{"class":931},[679,49300,48609],{"class":685},[679,49302,48612],{"class":931},[679,49304,14615],{"class":685},[679,49306,48617],{"class":689},[679,49308,3033],{"class":685},[679,49310,48622],{"class":689},[679,49312,49313,49315,49317,49319,49321,49323,49325,49327,49329],{"class":681,"line":977},[679,49314,48627],{"class":689},[679,49316,48630],{"class":693},[679,49318,664],{"class":689},[679,49320,48599],{"class":880},[679,49322,6700],{"class":689},[679,49324,3065],{"class":685},[679,49326,48606],{"class":931},[679,49328,47580],{"class":689},[679,49330,1186],{"class":693},[679,49332,49333,49335,49337,49339,49341,49343,49345,49347,49349,49351,49353,49355,49357,49359,49361,49363,49365,49367,49369,49371],{"class":681,"line":982},[679,49334,45172],{"class":685},[679,49336,48651],{"class":931},[679,49338,6883],{"class":685},[679,49340,48656],{"class":689},[679,49342,48630],{"class":693},[679,49344,664],{"class":689},[679,49346,48663],{"class":880},[679,49348,6700],{"class":689},[679,49350,4505],{"class":685},[679,49352,48612],{"class":931},[679,49354,14615],{"class":685},[679,49356,48617],{"class":689},[679,49358,3033],{"class":685},[679,49360,48678],{"class":689},[679,49362,48630],{"class":693},[679,49364,664],{"class":689},[679,49366,48663],{"class":880},[679,49368,48687],{"class":689},[679,49370,47580],{"class":689},[679,49372,1186],{"class":693},[679,49374,49375],{"class":681,"line":988},[679,49376,889],{"emptyLinePlaceholder":797},[679,49378,49379,49381,49383,49385,49387,49389,49391,49393,49395,49397,49399],{"class":681,"line":993},[679,49380,45172],{"class":685},[679,49382,48893],{"class":931},[679,49384,6883],{"class":685},[679,49386,48898],{"class":689},[679,49388,48901],{"class":693},[679,49390,48904],{"class":689},[679,49392,48907],{"class":693},[679,49394,48904],{"class":689},[679,49396,48912],{"class":693},[679,49398,47580],{"class":689},[679,49400,1186],{"class":693},[679,49402,49403],{"class":681,"line":2129},[679,49404,889],{"emptyLinePlaceholder":797},[679,49406,49407,49409,49411,49413,49415],{"class":681,"line":2140},[679,49408,48969],{"class":693},[679,49410,48972],{"class":880},[679,49412,49184],{"class":693},[679,49414,3441],{"class":931},[679,49416,49189],{"class":693},[4542,49418,9042],{"id":9041},[651,49420,49421],{},"This was just a small problem that came up during the migration. If anyone is interested in hearing specific on the migration script please let me know. I wanted to keep this post focused on the problem and I hope it helps someone out.",[651,49423,41105,49424,41109],{},[41107,49425],{},[786,49427,49428],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":49430},[49431,49432,49433],{"id":48422,"depth":790,"text":48423},{"id":48865,"depth":790,"text":48866},{"id":9041,"depth":790,"text":9042},"A quick tutorial on how to recursively create directories in node.",{"slug":49436,"date":49437,"published":797,"author":798,"tags":49438,"cover":49439},"node-recursive-directories","2019-02-21 08:00:00",[27318,25134],"node-multiple-directories.png",{"title":528,"description":49434},"blog/2019/02/21/node-recursive-directories","CBSVny0iXKeGdwSyMMtzGBdzbHs__SzRpDbKSJutR-w",{"id":49444,"title":525,"body":49445,"description":51317,"extension":793,"meta":51318,"navigation":797,"path":526,"seo":51323,"stem":51324,"__hash__":51325},"content/blog/2019/03/04/vue-event-arguments.md",{"type":648,"value":49446,"toc":51304},[49447,49450,49454,49457,49472,49483,49591,49594,49793,49797,49804,49813,49929,49938,49941,49945,49948,50017,50020,50148,50152,50155,50286,50289,50295,50569,50572,50576,50583,50855,50858,50862,50865,50875,51144,51148,51155,51292,51294,51297,51301],[651,49448,49449],{},"A fundamental skill in learning JavaScript and Vue is the ability to listen for events and handle them. Events are notifications to code that something interesting has taken place like a user clicking on a button or pressing the enter key. Vue makes event handling incredibly easy and gives us some great features when it comes to event modifiers.",[4542,49451,49453],{"id":49452},"event-handler-project","Event Handler Project",[651,49455,49456],{},"To get started you are going to setup a new Vue project by running the following command:",[669,49458,49460],{"className":5851,"code":49459,"language":5853,"meta":674,"style":674},"vue create event-handlers\n",[676,49461,49462],{"__ignoreMap":674},[679,49463,49464,49466,49469],{"class":681,"line":682},[679,49465,44169],{"class":880},[679,49467,49468],{"class":689}," create",[679,49470,49471],{"class":689}," event-handlers\n",[651,49473,49474,49475,49478,49479,49482],{},"Feel free to accept the defaults while creating this project. The first thing you're going to do is open up ",[676,49476,49477],{},"App.vue"," and remove the default content in between the ",[676,49480,49481],{},"\u003Ctemplate>\u003C/template>"," tags. With a empty page you're going to add 2 buttons that we will use as the base for our demo.",[669,49484,49486],{"className":4496,"code":49485,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\">Increase\u003C/a>\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\">Decrease\u003C/a>\n \u003C/div>\n\u003C/template>\n",[676,49487,49488,49496,49510,49543,49575,49583],{"__ignoreMap":674},[679,49489,49490,49492,49494],{"class":681,"line":682},[679,49491,4505],{"class":693},[679,49493,45982],{"class":4508},[679,49495,4519],{"class":693},[679,49497,49498,49500,49502,49504,49506,49508],{"class":681,"line":790},[679,49499,11738],{"class":693},[679,49501,4509],{"class":4508},[679,49503,5578],{"class":880},[679,49505,686],{"class":693},[679,49507,28626],{"class":689},[679,49509,4519],{"class":693},[679,49511,49512,49514,49516,49518,49520,49522,49524,49526,49529,49531,49533,49536,49539,49541],{"class":681,"line":892},[679,49513,4524],{"class":693},[679,49515,812],{"class":4508},[679,49517,4550],{"class":880},[679,49519,686],{"class":693},[679,49521,5420],{"class":689},[679,49523,5578],{"class":880},[679,49525,686],{"class":693},[679,49527,49528],{"class":689},"\"increase\"",[679,49530,4512],{"class":880},[679,49532,686],{"class":693},[679,49534,49535],{"class":689},"\"btn\"",[679,49537,49538],{"class":693},">Increase\u003C/",[679,49540,812],{"class":4508},[679,49542,4519],{"class":693},[679,49544,49545,49547,49549,49551,49553,49555,49557,49559,49562,49564,49566,49568,49571,49573],{"class":681,"line":901},[679,49546,4524],{"class":693},[679,49548,812],{"class":4508},[679,49550,4550],{"class":880},[679,49552,686],{"class":693},[679,49554,5420],{"class":689},[679,49556,5578],{"class":880},[679,49558,686],{"class":693},[679,49560,49561],{"class":689},"\"decrease\"",[679,49563,4512],{"class":880},[679,49565,686],{"class":693},[679,49567,49535],{"class":689},[679,49569,49570],{"class":693},">Decrease\u003C/",[679,49572,812],{"class":4508},[679,49574,4519],{"class":693},[679,49576,49577,49579,49581],{"class":681,"line":909},[679,49578,11840],{"class":693},[679,49580,4509],{"class":4508},[679,49582,4519],{"class":693},[679,49584,49585,49587,49589],{"class":681,"line":918},[679,49586,4586],{"class":693},[679,49588,45982],{"class":4508},[679,49590,4519],{"class":693},[651,49592,49593],{},"To give our buttons some style add the following css:",[669,49595,49597],{"className":4496,"code":49596,"language":4498,"meta":674,"style":674},"\u003Cstyle>\n body {\n margin: 10px;\n }\n a.btn {\n display: inline-block;\n margin: 10px;\n padding: 12px;\n font-size: 13px;\n font-weight: 700;\n background-color: rgb(63, 63, 219);\n color: white;\n text-decoration: none;\n text-transform: uppercase;\n border-radius: 4px;\n }\n\u003C/style>\n",[676,49598,49599,49607,49614,49627,49631,49641,49653,49665,49679,49693,49705,49731,49743,49755,49767,49781,49785],{"__ignoreMap":674},[679,49600,49601,49603,49605],{"class":681,"line":682},[679,49602,4505],{"class":693},[679,49604,786],{"class":4508},[679,49606,4519],{"class":693},[679,49608,49609,49612],{"class":681,"line":790},[679,49610,49611],{"class":4508}," body",[679,49613,884],{"class":693},[679,49615,49616,49619,49621,49623,49625],{"class":681,"line":892},[679,49617,49618],{"class":931}," margin",[679,49620,4282],{"class":693},[679,49622,1205],{"class":931},[679,49624,11797],{"class":685},[679,49626,1186],{"class":693},[679,49628,49629],{"class":681,"line":901},[679,49630,21405],{"class":693},[679,49632,49633,49636,49639],{"class":681,"line":909},[679,49634,49635],{"class":4508}," a",[679,49637,49638],{"class":880},".btn",[679,49640,884],{"class":693},[679,49642,49643,49646,49648,49651],{"class":681,"line":918},[679,49644,49645],{"class":931}," display",[679,49647,4282],{"class":693},[679,49649,49650],{"class":931},"inline-block",[679,49652,1186],{"class":693},[679,49654,49655,49657,49659,49661,49663],{"class":681,"line":935},[679,49656,49618],{"class":931},[679,49658,4282],{"class":693},[679,49660,1205],{"class":931},[679,49662,11797],{"class":685},[679,49664,1186],{"class":693},[679,49666,49667,49670,49672,49675,49677],{"class":681,"line":944},[679,49668,49669],{"class":931}," padding",[679,49671,4282],{"class":693},[679,49673,49674],{"class":931},"12",[679,49676,11797],{"class":685},[679,49678,1186],{"class":693},[679,49680,49681,49684,49686,49689,49691],{"class":681,"line":959},[679,49682,49683],{"class":931}," font-size",[679,49685,4282],{"class":693},[679,49687,49688],{"class":931},"13",[679,49690,11797],{"class":685},[679,49692,1186],{"class":693},[679,49694,49695,49698,49700,49703],{"class":681,"line":964},[679,49696,49697],{"class":931}," font-weight",[679,49699,4282],{"class":693},[679,49701,49702],{"class":931},"700",[679,49704,1186],{"class":693},[679,49706,49707,49710,49712,49715,49717,49720,49722,49724,49726,49729],{"class":681,"line":977},[679,49708,49709],{"class":931}," background-color",[679,49711,4282],{"class":693},[679,49713,49714],{"class":931},"rgb",[679,49716,745],{"class":693},[679,49718,49719],{"class":931},"63",[679,49721,2797],{"class":693},[679,49723,49719],{"class":931},[679,49725,2797],{"class":693},[679,49727,49728],{"class":931},"219",[679,49730,1208],{"class":693},[679,49732,49733,49736,49738,49741],{"class":681,"line":982},[679,49734,49735],{"class":931}," color",[679,49737,4282],{"class":693},[679,49739,49740],{"class":931},"white",[679,49742,1186],{"class":693},[679,49744,49745,49748,49750,49753],{"class":681,"line":988},[679,49746,49747],{"class":931}," text-decoration",[679,49749,4282],{"class":693},[679,49751,49752],{"class":931},"none",[679,49754,1186],{"class":693},[679,49756,49757,49760,49762,49765],{"class":681,"line":993},[679,49758,49759],{"class":931}," text-transform",[679,49761,4282],{"class":693},[679,49763,49764],{"class":931},"uppercase",[679,49766,1186],{"class":693},[679,49768,49769,49772,49774,49777,49779],{"class":681,"line":2129},[679,49770,49771],{"class":931}," border-radius",[679,49773,4282],{"class":693},[679,49775,49776],{"class":931},"4",[679,49778,11797],{"class":685},[679,49780,1186],{"class":693},[679,49782,49783],{"class":681,"line":2140},[679,49784,21405],{"class":693},[679,49786,49787,49789,49791],{"class":681,"line":2145},[679,49788,4586],{"class":693},[679,49790,786],{"class":4508},[679,49792,4519],{"class":693},[4542,49794,49796],{"id":49795},"listening-to-events","Listening to Events",[651,49798,49799,49800,49803],{},"With your buttons in place we can add an event listener to each. In vanilla JavaScript you would have to get a reference to the element and then add an event listener. In Vue you can use the ",[676,49801,49802],{},"v-on"," directive to listen to DOM events and run some JavaScript when they're triggered.",[651,49805,40060,49806,49808,49809,49812],{},[676,49807,49802],{}," directive is followed by a colon and then the DOM event that you want to listen for. In the case of the example you want to listen for the ",[676,49810,49811],{},"click"," event so your code would now look like this.",[669,49814,49816],{"className":4496,"code":49815,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\" v-on:click=\"\">Increase\u003C/a>\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\" v-on:click=\"\">Decrease\u003C/a>\n \u003C/div>\n\u003C/template>\n",[676,49817,49818,49826,49840,49877,49913,49921],{"__ignoreMap":674},[679,49819,49820,49822,49824],{"class":681,"line":682},[679,49821,4505],{"class":693},[679,49823,45982],{"class":4508},[679,49825,4519],{"class":693},[679,49827,49828,49830,49832,49834,49836,49838],{"class":681,"line":790},[679,49829,11738],{"class":693},[679,49831,4509],{"class":4508},[679,49833,5578],{"class":880},[679,49835,686],{"class":693},[679,49837,28626],{"class":689},[679,49839,4519],{"class":693},[679,49841,49842,49844,49846,49848,49850,49852,49854,49856,49858,49860,49862,49864,49867,49869,49871,49873,49875],{"class":681,"line":892},[679,49843,4524],{"class":693},[679,49845,812],{"class":4508},[679,49847,4550],{"class":880},[679,49849,686],{"class":693},[679,49851,5420],{"class":689},[679,49853,5578],{"class":880},[679,49855,686],{"class":693},[679,49857,49528],{"class":689},[679,49859,4512],{"class":880},[679,49861,686],{"class":693},[679,49863,49535],{"class":689},[679,49865,49866],{"class":880}," v-on:click",[679,49868,686],{"class":693},[679,49870,3579],{"class":689},[679,49872,49538],{"class":693},[679,49874,812],{"class":4508},[679,49876,4519],{"class":693},[679,49878,49879,49881,49883,49885,49887,49889,49891,49893,49895,49897,49899,49901,49903,49905,49907,49909,49911],{"class":681,"line":901},[679,49880,4524],{"class":693},[679,49882,812],{"class":4508},[679,49884,4550],{"class":880},[679,49886,686],{"class":693},[679,49888,5420],{"class":689},[679,49890,5578],{"class":880},[679,49892,686],{"class":693},[679,49894,49561],{"class":689},[679,49896,4512],{"class":880},[679,49898,686],{"class":693},[679,49900,49535],{"class":689},[679,49902,49866],{"class":880},[679,49904,686],{"class":693},[679,49906,3579],{"class":689},[679,49908,49570],{"class":693},[679,49910,812],{"class":4508},[679,49912,4519],{"class":693},[679,49914,49915,49917,49919],{"class":681,"line":909},[679,49916,11840],{"class":693},[679,49918,4509],{"class":4508},[679,49920,4519],{"class":693},[679,49922,49923,49925,49927],{"class":681,"line":918},[679,49924,4586],{"class":693},[679,49926,45982],{"class":4508},[679,49928,4519],{"class":693},[1004,49930,49931],{},[651,49932,49933,49934,49937],{},"There is a shorthand for the v-on: directive which is to just use the @ symbol followed by the name of the event. In this case ",[676,49935,49936],{},"@click=\"\""," does the exact same thing as the longer format. For the rest of this demo I will be using the long version.",[651,49939,49940],{},"The code that you place inside of the parenthesis is the code that you will run when that event is fired.",[4542,49942,49944],{"id":49943},"method-event-handlers","Method Event Handlers",[651,49946,49947],{},"The first thing you need to do is setup some initial data. In the script block create an instance variable called counter and set it to zero.",[669,49949,49951],{"className":25132,"code":49950,"language":25134,"meta":674,"style":674},"\u003Cscript>\nexport default {\n name: \"app\",\n data() {\n return {\n counter: 0\n };\n }\n};\n\u003C/script>\n",[676,49952,49953,49961,49966,49975,49982,49987,49996,50001,50005,50009],{"__ignoreMap":674},[679,49954,49955,49957,49959],{"class":681,"line":682},[679,49956,4505],{"class":693},[679,49958,47668],{"class":4508},[679,49960,4519],{"class":693},[679,49962,49963],{"class":681,"line":790},[679,49964,49965],{"class":693},"export default {\n",[679,49967,49968,49971,49973],{"class":681,"line":892},[679,49969,49970],{"class":693}," name: ",[679,49972,28626],{"class":689},[679,49974,12083],{"class":693},[679,49976,49977,49980],{"class":681,"line":901},[679,49978,49979],{"class":880}," data",[679,49981,2667],{"class":693},[679,49983,49984],{"class":681,"line":909},[679,49985,49986],{"class":693}," return {\n",[679,49988,49989,49992,49994],{"class":681,"line":918},[679,49990,49991],{"class":880}," counter",[679,49993,4282],{"class":693},[679,49995,6562],{"class":931},[679,49997,49998],{"class":681,"line":935},[679,49999,50000],{"class":693}," };\n",[679,50002,50003],{"class":681,"line":944},[679,50004,21405],{"class":693},[679,50006,50007],{"class":681,"line":959},[679,50008,44055],{"class":693},[679,50010,50011,50013,50015],{"class":681,"line":964},[679,50012,4586],{"class":693},[679,50014,47668],{"class":4508},[679,50016,4519],{"class":693},[651,50018,50019],{},"In the template you are going to add some text and using data binding you are going to display the value of counter.",[669,50021,50023],{"className":4496,"code":50022,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\" v-on:click=\"\">Increase\u003C/a>\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\" v-on:click=\"\">Decrease\u003C/a>\n \u003Cp>The button was clicked {{ counter }} times\u003C/p>\n \u003C/div>\n\u003C/template>\n",[676,50024,50025,50033,50047,50083,50119,50132,50140],{"__ignoreMap":674},[679,50026,50027,50029,50031],{"class":681,"line":682},[679,50028,4505],{"class":693},[679,50030,45982],{"class":4508},[679,50032,4519],{"class":693},[679,50034,50035,50037,50039,50041,50043,50045],{"class":681,"line":790},[679,50036,11738],{"class":693},[679,50038,4509],{"class":4508},[679,50040,5578],{"class":880},[679,50042,686],{"class":693},[679,50044,28626],{"class":689},[679,50046,4519],{"class":693},[679,50048,50049,50051,50053,50055,50057,50059,50061,50063,50065,50067,50069,50071,50073,50075,50077,50079,50081],{"class":681,"line":892},[679,50050,4524],{"class":693},[679,50052,812],{"class":4508},[679,50054,4550],{"class":880},[679,50056,686],{"class":693},[679,50058,5420],{"class":689},[679,50060,5578],{"class":880},[679,50062,686],{"class":693},[679,50064,49528],{"class":689},[679,50066,4512],{"class":880},[679,50068,686],{"class":693},[679,50070,49535],{"class":689},[679,50072,49866],{"class":880},[679,50074,686],{"class":693},[679,50076,3579],{"class":689},[679,50078,49538],{"class":693},[679,50080,812],{"class":4508},[679,50082,4519],{"class":693},[679,50084,50085,50087,50089,50091,50093,50095,50097,50099,50101,50103,50105,50107,50109,50111,50113,50115,50117],{"class":681,"line":901},[679,50086,4524],{"class":693},[679,50088,812],{"class":4508},[679,50090,4550],{"class":880},[679,50092,686],{"class":693},[679,50094,5420],{"class":689},[679,50096,5578],{"class":880},[679,50098,686],{"class":693},[679,50100,49561],{"class":689},[679,50102,4512],{"class":880},[679,50104,686],{"class":693},[679,50106,49535],{"class":689},[679,50108,49866],{"class":880},[679,50110,686],{"class":693},[679,50112,3579],{"class":689},[679,50114,49570],{"class":693},[679,50116,812],{"class":4508},[679,50118,4519],{"class":693},[679,50120,50121,50123,50125,50128,50130],{"class":681,"line":909},[679,50122,4524],{"class":693},[679,50124,651],{"class":4508},[679,50126,50127],{"class":693},">The button was clicked {{ counter }} times\u003C/",[679,50129,651],{"class":4508},[679,50131,4519],{"class":693},[679,50133,50134,50136,50138],{"class":681,"line":918},[679,50135,11840],{"class":693},[679,50137,4509],{"class":4508},[679,50139,4519],{"class":693},[679,50141,50142,50144,50146],{"class":681,"line":935},[679,50143,4586],{"class":693},[679,50145,45982],{"class":4508},[679,50147,4519],{"class":693},[5909,50149,50151],{"id":50150},"inline-event-handlers","Inline Event Handlers",[651,50153,50154],{},"Now that you know how to declare an event handler you need to write some code that will execute when that event is triggered. You can write this code inside of the parenthesis (inline) or you can declare a function to handle it. For basic operations writing inline code will work and here all you want to do is increase or decrease the value of the variable counter.",[669,50156,50158],{"className":4496,"code":50157,"language":4498,"meta":674,"style":674},"\u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\" v-on:click=\"counter += 1\">\n Increase\n \u003C/a>\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\" v-on:click=\"counter -= 1\">\n Decrease\n \u003C/a>\n \u003Cp>The button was clicked {{ counter }} times\u003C/p>\n\u003C/div>\n",[676,50159,50160,50174,50207,50212,50220,50253,50258,50266,50278],{"__ignoreMap":674},[679,50161,50162,50164,50166,50168,50170,50172],{"class":681,"line":682},[679,50163,4505],{"class":693},[679,50165,4509],{"class":4508},[679,50167,5578],{"class":880},[679,50169,686],{"class":693},[679,50171,28626],{"class":689},[679,50173,4519],{"class":693},[679,50175,50176,50178,50180,50182,50184,50186,50188,50190,50192,50194,50196,50198,50200,50202,50205],{"class":681,"line":790},[679,50177,11738],{"class":693},[679,50179,812],{"class":4508},[679,50181,4550],{"class":880},[679,50183,686],{"class":693},[679,50185,5420],{"class":689},[679,50187,5578],{"class":880},[679,50189,686],{"class":693},[679,50191,49528],{"class":689},[679,50193,4512],{"class":880},[679,50195,686],{"class":693},[679,50197,49535],{"class":689},[679,50199,49866],{"class":880},[679,50201,686],{"class":693},[679,50203,50204],{"class":689},"\"counter += 1\"",[679,50206,4519],{"class":693},[679,50208,50209],{"class":681,"line":892},[679,50210,50211],{"class":693}," Increase\n",[679,50213,50214,50216,50218],{"class":681,"line":901},[679,50215,11840],{"class":693},[679,50217,812],{"class":4508},[679,50219,4519],{"class":693},[679,50221,50222,50224,50226,50228,50230,50232,50234,50236,50238,50240,50242,50244,50246,50248,50251],{"class":681,"line":909},[679,50223,11738],{"class":693},[679,50225,812],{"class":4508},[679,50227,4550],{"class":880},[679,50229,686],{"class":693},[679,50231,5420],{"class":689},[679,50233,5578],{"class":880},[679,50235,686],{"class":693},[679,50237,49561],{"class":689},[679,50239,4512],{"class":880},[679,50241,686],{"class":693},[679,50243,49535],{"class":689},[679,50245,49866],{"class":880},[679,50247,686],{"class":693},[679,50249,50250],{"class":689},"\"counter -= 1\"",[679,50252,4519],{"class":693},[679,50254,50255],{"class":681,"line":918},[679,50256,50257],{"class":693}," Decrease\n",[679,50259,50260,50262,50264],{"class":681,"line":935},[679,50261,11840],{"class":693},[679,50263,812],{"class":4508},[679,50265,4519],{"class":693},[679,50267,50268,50270,50272,50274,50276],{"class":681,"line":944},[679,50269,11738],{"class":693},[679,50271,651],{"class":4508},[679,50273,50127],{"class":693},[679,50275,651],{"class":4508},[679,50277,4519],{"class":693},[679,50279,50280,50282,50284],{"class":681,"line":959},[679,50281,4586],{"class":693},[679,50283,4509],{"class":4508},[679,50285,4519],{"class":693},[5909,50287,49944],{"id":50288},"method-event-handlers-1",[651,50290,50291,50292,50294],{},"You will quickly find out that logic for most of your event handlers is more complex and for those cases we can call a method. The ",[676,50293,49802],{}," directive will take the name of a method that will be called when the event is fired. In the followed code you are moving the logic from inline to a method.",[669,50296,50299],{"className":50297,"code":50298,"language":44169,"meta":674,"style":674},"language-vue shiki shiki-themes github-light github-dark github-light","\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\" v-on:click=\"increase\">Increase\u003C/a>\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\" v-on:click=\"decrease\">Decrease\u003C/a>\n \u003Cp>The button was clicked {{ counter }} times\u003C/p>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: \"app\",\n data() {\n return {\n counter: 0\n };\n },\n methods: {\n increase() {\n this.counter += 1;\n },\n decrease() {\n this.counter -= 1;\n }\n }\n};\n\u003C/script>\n",[676,50300,50301,50309,50323,50369,50414,50426,50434,50442,50446,50454,50463,50471,50477,50483,50490,50494,50498,50503,50510,50525,50529,50536,50549,50553,50557,50561],{"__ignoreMap":674},[679,50302,50303,50305,50307],{"class":681,"line":682},[679,50304,4505],{"class":693},[679,50306,45982],{"class":4508},[679,50308,4519],{"class":693},[679,50310,50311,50313,50315,50317,50319,50321],{"class":681,"line":790},[679,50312,11738],{"class":693},[679,50314,4509],{"class":4508},[679,50316,5578],{"class":880},[679,50318,686],{"class":693},[679,50320,28626],{"class":689},[679,50322,4519],{"class":693},[679,50324,50325,50327,50329,50331,50333,50335,50337,50339,50341,50343,50345,50347,50350,50352,50354,50356,50358,50361,50363,50365,50367],{"class":681,"line":892},[679,50326,4524],{"class":693},[679,50328,812],{"class":4508},[679,50330,4550],{"class":880},[679,50332,686],{"class":693},[679,50334,5420],{"class":689},[679,50336,5578],{"class":880},[679,50338,686],{"class":693},[679,50340,49528],{"class":689},[679,50342,4512],{"class":880},[679,50344,686],{"class":693},[679,50346,49535],{"class":689},[679,50348,50349],{"class":880}," v-on",[679,50351,2391],{"class":693},[679,50353,49811],{"class":880},[679,50355,686],{"class":693},[679,50357,4857],{"class":689},[679,50359,50360],{"class":693},"increase",[679,50362,4857],{"class":689},[679,50364,49538],{"class":693},[679,50366,812],{"class":4508},[679,50368,4519],{"class":693},[679,50370,50371,50373,50375,50377,50379,50381,50383,50385,50387,50389,50391,50393,50395,50397,50399,50401,50403,50406,50408,50410,50412],{"class":681,"line":901},[679,50372,4524],{"class":693},[679,50374,812],{"class":4508},[679,50376,4550],{"class":880},[679,50378,686],{"class":693},[679,50380,5420],{"class":689},[679,50382,5578],{"class":880},[679,50384,686],{"class":693},[679,50386,49561],{"class":689},[679,50388,4512],{"class":880},[679,50390,686],{"class":693},[679,50392,49535],{"class":689},[679,50394,50349],{"class":880},[679,50396,2391],{"class":693},[679,50398,49811],{"class":880},[679,50400,686],{"class":693},[679,50402,4857],{"class":689},[679,50404,50405],{"class":693},"decrease",[679,50407,4857],{"class":689},[679,50409,49570],{"class":693},[679,50411,812],{"class":4508},[679,50413,4519],{"class":693},[679,50415,50416,50418,50420,50422,50424],{"class":681,"line":909},[679,50417,4524],{"class":693},[679,50419,651],{"class":4508},[679,50421,50127],{"class":693},[679,50423,651],{"class":4508},[679,50425,4519],{"class":693},[679,50427,50428,50430,50432],{"class":681,"line":918},[679,50429,11840],{"class":693},[679,50431,4509],{"class":4508},[679,50433,4519],{"class":693},[679,50435,50436,50438,50440],{"class":681,"line":935},[679,50437,4586],{"class":693},[679,50439,45982],{"class":4508},[679,50441,4519],{"class":693},[679,50443,50444],{"class":681,"line":944},[679,50445,889],{"emptyLinePlaceholder":797},[679,50447,50448,50450,50452],{"class":681,"line":959},[679,50449,4505],{"class":693},[679,50451,47668],{"class":4508},[679,50453,4519],{"class":693},[679,50455,50456,50458,50461],{"class":681,"line":964},[679,50457,29245],{"class":685},[679,50459,50460],{"class":685}," default",[679,50462,884],{"class":693},[679,50464,50465,50467,50469],{"class":681,"line":977},[679,50466,49970],{"class":693},[679,50468,28626],{"class":689},[679,50470,12083],{"class":693},[679,50472,50473,50475],{"class":681,"line":982},[679,50474,49979],{"class":880},[679,50476,2667],{"class":693},[679,50478,50479,50481],{"class":681,"line":988},[679,50480,21478],{"class":685},[679,50482,884],{"class":693},[679,50484,50485,50488],{"class":681,"line":993},[679,50486,50487],{"class":693}," counter: ",[679,50489,6562],{"class":931},[679,50491,50492],{"class":681,"line":2129},[679,50493,50000],{"class":693},[679,50495,50496],{"class":681,"line":2140},[679,50497,28483],{"class":693},[679,50499,50500],{"class":681,"line":2145},[679,50501,50502],{"class":693}," methods: {\n",[679,50504,50505,50508],{"class":681,"line":2154},[679,50506,50507],{"class":880}," increase",[679,50509,2667],{"class":693},[679,50511,50512,50515,50518,50521,50523],{"class":681,"line":2159},[679,50513,50514],{"class":931}," this",[679,50516,50517],{"class":693},".counter ",[679,50519,50520],{"class":685},"+=",[679,50522,48606],{"class":931},[679,50524,1186],{"class":693},[679,50526,50527],{"class":681,"line":2164},[679,50528,28763],{"class":693},[679,50530,50531,50534],{"class":681,"line":3134},[679,50532,50533],{"class":880}," decrease",[679,50535,2667],{"class":693},[679,50537,50538,50540,50542,50545,50547],{"class":681,"line":3139},[679,50539,50514],{"class":931},[679,50541,50517],{"class":693},[679,50543,50544],{"class":685},"-=",[679,50546,48606],{"class":931},[679,50548,1186],{"class":693},[679,50550,50551],{"class":681,"line":3144},[679,50552,985],{"class":693},[679,50554,50555],{"class":681,"line":3149},[679,50556,21405],{"class":693},[679,50558,50559],{"class":681,"line":3169},[679,50560,44055],{"class":693},[679,50562,50563,50565,50567],{"class":681,"line":3185},[679,50564,4586],{"class":693},[679,50566,47668],{"class":4508},[679,50568,4519],{"class":693},[651,50570,50571],{},"The program will function the same but now you have extracted it into a program where it could contain more complex operations if needed.",[4542,50573,50575],{"id":50574},"event-handler-arguments","Event Handler Arguments",[651,50577,50578,50579,50582],{},"While this program works just fine it seems like we can simplify it. All the increase or decrease methods do is change the variable counter. You could write a single method to handle the logic for this method. Just like any other method in JavaScript you can pass arguments to it. Here you are calling ",[676,50580,50581],{},"updateCounter()"," for both buttons but passing the value of 1 for increase and -1 for decrease.",[669,50584,50586],{"className":50297,"code":50585,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\" v-on:click=\"updateCounter(1)\"\n >Increase\u003C/a\n >\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\" v-on:click=\"updateCounter(-1)\"\n >Decrease\u003C/a\n >\n \u003Cp>The button was clicked {{ counter }} times\u003C/p>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: \"app\",\n data() {\n return {\n counter: 0\n };\n },\n methods: {\n updateCounter(operand) {\n this.counter += operand;\n }\n }\n};\n\u003C/script>\n",[676,50587,50588,50596,50610,50656,50664,50669,50715,50722,50726,50738,50746,50754,50758,50766,50774,50782,50788,50794,50800,50804,50808,50812,50824,50835,50839,50843,50847],{"__ignoreMap":674},[679,50589,50590,50592,50594],{"class":681,"line":682},[679,50591,4505],{"class":693},[679,50593,45982],{"class":4508},[679,50595,4519],{"class":693},[679,50597,50598,50600,50602,50604,50606,50608],{"class":681,"line":790},[679,50599,11738],{"class":693},[679,50601,4509],{"class":4508},[679,50603,5578],{"class":880},[679,50605,686],{"class":693},[679,50607,28626],{"class":689},[679,50609,4519],{"class":693},[679,50611,50612,50614,50616,50618,50620,50622,50624,50626,50628,50630,50632,50634,50636,50638,50640,50642,50644,50647,50649,50651,50654],{"class":681,"line":892},[679,50613,4524],{"class":693},[679,50615,812],{"class":4508},[679,50617,4550],{"class":880},[679,50619,686],{"class":693},[679,50621,5420],{"class":689},[679,50623,5578],{"class":880},[679,50625,686],{"class":693},[679,50627,49528],{"class":689},[679,50629,4512],{"class":880},[679,50631,686],{"class":693},[679,50633,49535],{"class":689},[679,50635,50349],{"class":880},[679,50637,2391],{"class":693},[679,50639,49811],{"class":880},[679,50641,686],{"class":693},[679,50643,4857],{"class":689},[679,50645,50646],{"class":880},"updateCounter",[679,50648,745],{"class":693},[679,50650,1557],{"class":931},[679,50652,50653],{"class":693},")",[679,50655,2878],{"class":689},[679,50657,50658,50661],{"class":681,"line":901},[679,50659,50660],{"class":693}," >Increase\u003C/",[679,50662,50663],{"class":4508},"a\n",[679,50665,50666],{"class":681,"line":909},[679,50667,50668],{"class":693}," >\n",[679,50670,50671,50673,50675,50677,50679,50681,50683,50685,50687,50689,50691,50693,50695,50697,50699,50701,50703,50705,50707,50709,50711,50713],{"class":681,"line":918},[679,50672,4524],{"class":693},[679,50674,812],{"class":4508},[679,50676,4550],{"class":880},[679,50678,686],{"class":693},[679,50680,5420],{"class":689},[679,50682,5578],{"class":880},[679,50684,686],{"class":693},[679,50686,49561],{"class":689},[679,50688,4512],{"class":880},[679,50690,686],{"class":693},[679,50692,49535],{"class":689},[679,50694,50349],{"class":880},[679,50696,2391],{"class":693},[679,50698,49811],{"class":880},[679,50700,686],{"class":693},[679,50702,4857],{"class":689},[679,50704,50646],{"class":880},[679,50706,745],{"class":693},[679,50708,6453],{"class":685},[679,50710,1557],{"class":931},[679,50712,50653],{"class":693},[679,50714,2878],{"class":689},[679,50716,50717,50720],{"class":681,"line":935},[679,50718,50719],{"class":693}," >Decrease\u003C/",[679,50721,50663],{"class":4508},[679,50723,50724],{"class":681,"line":944},[679,50725,50668],{"class":693},[679,50727,50728,50730,50732,50734,50736],{"class":681,"line":959},[679,50729,4524],{"class":693},[679,50731,651],{"class":4508},[679,50733,50127],{"class":693},[679,50735,651],{"class":4508},[679,50737,4519],{"class":693},[679,50739,50740,50742,50744],{"class":681,"line":964},[679,50741,11840],{"class":693},[679,50743,4509],{"class":4508},[679,50745,4519],{"class":693},[679,50747,50748,50750,50752],{"class":681,"line":977},[679,50749,4586],{"class":693},[679,50751,45982],{"class":4508},[679,50753,4519],{"class":693},[679,50755,50756],{"class":681,"line":982},[679,50757,889],{"emptyLinePlaceholder":797},[679,50759,50760,50762,50764],{"class":681,"line":988},[679,50761,4505],{"class":693},[679,50763,47668],{"class":4508},[679,50765,4519],{"class":693},[679,50767,50768,50770,50772],{"class":681,"line":993},[679,50769,29245],{"class":685},[679,50771,50460],{"class":685},[679,50773,884],{"class":693},[679,50775,50776,50778,50780],{"class":681,"line":2129},[679,50777,49970],{"class":693},[679,50779,28626],{"class":689},[679,50781,12083],{"class":693},[679,50783,50784,50786],{"class":681,"line":2140},[679,50785,49979],{"class":880},[679,50787,2667],{"class":693},[679,50789,50790,50792],{"class":681,"line":2145},[679,50791,21478],{"class":685},[679,50793,884],{"class":693},[679,50795,50796,50798],{"class":681,"line":2154},[679,50797,50487],{"class":693},[679,50799,6562],{"class":931},[679,50801,50802],{"class":681,"line":2159},[679,50803,50000],{"class":693},[679,50805,50806],{"class":681,"line":2164},[679,50807,28483],{"class":693},[679,50809,50810],{"class":681,"line":3134},[679,50811,50502],{"class":693},[679,50813,50814,50817,50819,50822],{"class":681,"line":3139},[679,50815,50816],{"class":880}," updateCounter",[679,50818,745],{"class":693},[679,50820,50821],{"class":2099},"operand",[679,50823,4390],{"class":693},[679,50825,50826,50828,50830,50832],{"class":681,"line":3144},[679,50827,50514],{"class":931},[679,50829,50517],{"class":693},[679,50831,50520],{"class":685},[679,50833,50834],{"class":693}," operand;\n",[679,50836,50837],{"class":681,"line":3149},[679,50838,985],{"class":693},[679,50840,50841],{"class":681,"line":3169},[679,50842,21405],{"class":693},[679,50844,50845],{"class":681,"line":3185},[679,50846,44055],{"class":693},[679,50848,50849,50851,50853],{"class":681,"line":3194},[679,50850,4586],{"class":693},[679,50852,47668],{"class":4508},[679,50854,4519],{"class":693},[651,50856,50857],{},"Just like any other method you can pass whatever arguments are needed to this method.",[5909,50859,50861],{"id":50860},"implicit-event-argument","Implicit Event Argument",[651,50863,50864],{},"Looking at this method I can't help but think we are be a little explicit here. You know that the increase button will increase the counter by 1 and the decrease button will decrease the counter by 1. If that is the case why do you need to pass that variable to the method.",[651,50866,50867,50868,50870,50871,50874],{},"In vanilla JavaScript you have access to the original DOM event. From there you can determine where the event was originated from. In Vue the original DOM event is implicitly passed if there are no arguments to the method. This means that in our ",[676,50869,50581],{}," method you can declare an argument called event (or whatever you want for that matter) and the event will be passed in. With the original event we can get the id of the button using ",[676,50872,50873],{},"event.target.id"," and determine if we are increasing or decreasing the value of counter.",[669,50876,50878],{"className":50297,"code":50877,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\" v-on:click=\"updateCounter\"\n >Increase\u003C/a\n >\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\" v-on:click=\"updateCounter\"\n >Decrease\u003C/a\n >\n \u003Cp>The button was clicked {{ counter }} times\u003C/p>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: \"app\",\n data() {\n return {\n counter: 0\n };\n },\n methods: {\n updateCounter(event) {\n this.counter += event.target.id === \"increase\" ? 1 : -1;\n }\n }\n};\n\u003C/script>\n",[676,50879,50880,50888,50902,50940,50946,50950,50988,50994,50998,51010,51018,51026,51030,51038,51046,51054,51060,51066,51072,51076,51080,51084,51095,51124,51128,51132,51136],{"__ignoreMap":674},[679,50881,50882,50884,50886],{"class":681,"line":682},[679,50883,4505],{"class":693},[679,50885,45982],{"class":4508},[679,50887,4519],{"class":693},[679,50889,50890,50892,50894,50896,50898,50900],{"class":681,"line":790},[679,50891,11738],{"class":693},[679,50893,4509],{"class":4508},[679,50895,5578],{"class":880},[679,50897,686],{"class":693},[679,50899,28626],{"class":689},[679,50901,4519],{"class":693},[679,50903,50904,50906,50908,50910,50912,50914,50916,50918,50920,50922,50924,50926,50928,50930,50932,50934,50936,50938],{"class":681,"line":892},[679,50905,4524],{"class":693},[679,50907,812],{"class":4508},[679,50909,4550],{"class":880},[679,50911,686],{"class":693},[679,50913,5420],{"class":689},[679,50915,5578],{"class":880},[679,50917,686],{"class":693},[679,50919,49528],{"class":689},[679,50921,4512],{"class":880},[679,50923,686],{"class":693},[679,50925,49535],{"class":689},[679,50927,50349],{"class":880},[679,50929,2391],{"class":693},[679,50931,49811],{"class":880},[679,50933,686],{"class":693},[679,50935,4857],{"class":689},[679,50937,50646],{"class":693},[679,50939,2878],{"class":689},[679,50941,50942,50944],{"class":681,"line":901},[679,50943,50660],{"class":693},[679,50945,50663],{"class":4508},[679,50947,50948],{"class":681,"line":909},[679,50949,50668],{"class":693},[679,50951,50952,50954,50956,50958,50960,50962,50964,50966,50968,50970,50972,50974,50976,50978,50980,50982,50984,50986],{"class":681,"line":918},[679,50953,4524],{"class":693},[679,50955,812],{"class":4508},[679,50957,4550],{"class":880},[679,50959,686],{"class":693},[679,50961,5420],{"class":689},[679,50963,5578],{"class":880},[679,50965,686],{"class":693},[679,50967,49561],{"class":689},[679,50969,4512],{"class":880},[679,50971,686],{"class":693},[679,50973,49535],{"class":689},[679,50975,50349],{"class":880},[679,50977,2391],{"class":693},[679,50979,49811],{"class":880},[679,50981,686],{"class":693},[679,50983,4857],{"class":689},[679,50985,50646],{"class":693},[679,50987,2878],{"class":689},[679,50989,50990,50992],{"class":681,"line":935},[679,50991,50719],{"class":693},[679,50993,50663],{"class":4508},[679,50995,50996],{"class":681,"line":944},[679,50997,50668],{"class":693},[679,50999,51000,51002,51004,51006,51008],{"class":681,"line":959},[679,51001,4524],{"class":693},[679,51003,651],{"class":4508},[679,51005,50127],{"class":693},[679,51007,651],{"class":4508},[679,51009,4519],{"class":693},[679,51011,51012,51014,51016],{"class":681,"line":964},[679,51013,11840],{"class":693},[679,51015,4509],{"class":4508},[679,51017,4519],{"class":693},[679,51019,51020,51022,51024],{"class":681,"line":977},[679,51021,4586],{"class":693},[679,51023,45982],{"class":4508},[679,51025,4519],{"class":693},[679,51027,51028],{"class":681,"line":982},[679,51029,889],{"emptyLinePlaceholder":797},[679,51031,51032,51034,51036],{"class":681,"line":988},[679,51033,4505],{"class":693},[679,51035,47668],{"class":4508},[679,51037,4519],{"class":693},[679,51039,51040,51042,51044],{"class":681,"line":993},[679,51041,29245],{"class":685},[679,51043,50460],{"class":685},[679,51045,884],{"class":693},[679,51047,51048,51050,51052],{"class":681,"line":2129},[679,51049,49970],{"class":693},[679,51051,28626],{"class":689},[679,51053,12083],{"class":693},[679,51055,51056,51058],{"class":681,"line":2140},[679,51057,49979],{"class":880},[679,51059,2667],{"class":693},[679,51061,51062,51064],{"class":681,"line":2145},[679,51063,21478],{"class":685},[679,51065,884],{"class":693},[679,51067,51068,51070],{"class":681,"line":2154},[679,51069,50487],{"class":693},[679,51071,6562],{"class":931},[679,51073,51074],{"class":681,"line":2159},[679,51075,50000],{"class":693},[679,51077,51078],{"class":681,"line":2164},[679,51079,28483],{"class":693},[679,51081,51082],{"class":681,"line":3134},[679,51083,50502],{"class":693},[679,51085,51086,51088,51090,51093],{"class":681,"line":3139},[679,51087,50816],{"class":880},[679,51089,745],{"class":693},[679,51091,51092],{"class":2099},"event",[679,51094,4390],{"class":693},[679,51096,51097,51099,51101,51103,51106,51109,51112,51114,51116,51118,51120,51122],{"class":681,"line":3144},[679,51098,50514],{"class":931},[679,51100,50517],{"class":693},[679,51102,50520],{"class":685},[679,51104,51105],{"class":693}," event.target.id ",[679,51107,51108],{"class":685},"===",[679,51110,51111],{"class":689}," \"increase\"",[679,51113,14615],{"class":685},[679,51115,48606],{"class":931},[679,51117,3033],{"class":685},[679,51119,35355],{"class":685},[679,51121,1557],{"class":931},[679,51123,1186],{"class":693},[679,51125,51126],{"class":681,"line":3149},[679,51127,985],{"class":693},[679,51129,51130],{"class":681,"line":3169},[679,51131,21405],{"class":693},[679,51133,51134],{"class":681,"line":3185},[679,51135,44055],{"class":693},[679,51137,51138,51140,51142],{"class":681,"line":3194},[679,51139,4586],{"class":693},[679,51141,47668],{"class":4508},[679,51143,4519],{"class":693},[5909,51145,51147],{"id":51146},"explicit-event-argument","Explicit Event Argument",[651,51149,51150,51151,51154],{},"What happens when you have arguments that you need to pass to your method but you also need access to the original DOM event? In that case there is a special variable ",[676,51152,51153],{},"$event"," that you can pass.",[669,51156,51158],{"className":4496,"code":51157,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ca href=\"#\" id=\"increase\" class=\"btn\" v-on:click=\"updateCounter(1,$event)\"\n >Increase\u003C/a\n >\n \u003Ca href=\"#\" id=\"decrease\" class=\"btn\" v-on:click=\"updateCounter(-1,$event)\"\n >Decrease\u003C/a\n >\n \u003Cp>The button was clicked {{ counter }} times\u003C/p>\n \u003C/div>\n\u003C/template>\n",[676,51159,51160,51168,51182,51213,51219,51223,51254,51260,51264,51276,51284],{"__ignoreMap":674},[679,51161,51162,51164,51166],{"class":681,"line":682},[679,51163,4505],{"class":693},[679,51165,45982],{"class":4508},[679,51167,4519],{"class":693},[679,51169,51170,51172,51174,51176,51178,51180],{"class":681,"line":790},[679,51171,11738],{"class":693},[679,51173,4509],{"class":4508},[679,51175,5578],{"class":880},[679,51177,686],{"class":693},[679,51179,28626],{"class":689},[679,51181,4519],{"class":693},[679,51183,51184,51186,51188,51190,51192,51194,51196,51198,51200,51202,51204,51206,51208,51210],{"class":681,"line":892},[679,51185,4524],{"class":693},[679,51187,812],{"class":4508},[679,51189,4550],{"class":880},[679,51191,686],{"class":693},[679,51193,5420],{"class":689},[679,51195,5578],{"class":880},[679,51197,686],{"class":693},[679,51199,49528],{"class":689},[679,51201,4512],{"class":880},[679,51203,686],{"class":693},[679,51205,49535],{"class":689},[679,51207,49866],{"class":880},[679,51209,686],{"class":693},[679,51211,51212],{"class":689},"\"updateCounter(1,$event)\"\n",[679,51214,51215,51217],{"class":681,"line":901},[679,51216,50660],{"class":693},[679,51218,50663],{"class":4508},[679,51220,51221],{"class":681,"line":909},[679,51222,50668],{"class":693},[679,51224,51225,51227,51229,51231,51233,51235,51237,51239,51241,51243,51245,51247,51249,51251],{"class":681,"line":918},[679,51226,4524],{"class":693},[679,51228,812],{"class":4508},[679,51230,4550],{"class":880},[679,51232,686],{"class":693},[679,51234,5420],{"class":689},[679,51236,5578],{"class":880},[679,51238,686],{"class":693},[679,51240,49561],{"class":689},[679,51242,4512],{"class":880},[679,51244,686],{"class":693},[679,51246,49535],{"class":689},[679,51248,49866],{"class":880},[679,51250,686],{"class":693},[679,51252,51253],{"class":689},"\"updateCounter(-1,$event)\"\n",[679,51255,51256,51258],{"class":681,"line":935},[679,51257,50719],{"class":693},[679,51259,50663],{"class":4508},[679,51261,51262],{"class":681,"line":944},[679,51263,50668],{"class":693},[679,51265,51266,51268,51270,51272,51274],{"class":681,"line":959},[679,51267,4524],{"class":693},[679,51269,651],{"class":4508},[679,51271,50127],{"class":693},[679,51273,651],{"class":4508},[679,51275,4519],{"class":693},[679,51277,51278,51280,51282],{"class":681,"line":964},[679,51279,11840],{"class":693},[679,51281,4509],{"class":4508},[679,51283,4519],{"class":693},[679,51285,51286,51288,51290],{"class":681,"line":977},[679,51287,4586],{"class":693},[679,51289,45982],{"class":4508},[679,51291,4519],{"class":693},[4542,51293,9042],{"id":9041},[651,51295,51296],{},"I realize that most or all of this was pretty basic but I have found that a lot of people didn't know about the implicit event argument. This comes in really handy in many situations so its good to know how to access the original DOM event. If you have any questions about this article and or anything related to Vue please let me know and I will try and answer them, until then...",[651,51298,41105,51299,41109],{},[41107,51300],{},[786,51302,51303],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":51305},[51306,51307,51308,51312,51316],{"id":49452,"depth":790,"text":49453},{"id":49795,"depth":790,"text":49796},{"id":49943,"depth":790,"text":49944,"children":51309},[51310,51311],{"id":50150,"depth":892,"text":50151},{"id":50288,"depth":892,"text":49944},{"id":50574,"depth":790,"text":50575,"children":51313},[51314,51315],{"id":50860,"depth":892,"text":50861},{"id":51146,"depth":892,"text":51147},{"id":9041,"depth":790,"text":9042},"In this article you are going to learn how to pass arguments to your Vue event handlers as well as how get access to the original DOM event.",{"slug":51319,"date":51320,"published":797,"author":798,"tags":51321,"cover":51322},"vue-event-arguments","2019-03-04 10:00:00",[44169,25134],"./vue-event-arguments.png",{"title":525,"description":51317},"blog/2019/03/04/vue-event-arguments","66O3uiXakRWGKA5x8e5ofkCakd_rOXCfGo4rZ2oqZbw",{"id":51327,"title":522,"body":51328,"description":51487,"extension":793,"meta":51488,"navigation":797,"path":523,"seo":51493,"stem":51494,"__hash__":51495},"content/blog/2019/03/07/spring-initializr-fresh-look.md",{"type":648,"value":51329,"toc":51480},[51330,51343,51349,51353,51361,51364,51367,51373,51377,51397,51400,51431,51435,51438,51441,51447,51450,51456,51459,51462,51464,51467,51476],[651,51331,51332,51333,51338,51339,51342],{},"The Spring Team ",[812,51334,51337],{"href":51335,"rel":51336},"https://spring.io/blog/2019/03/05/spring-initializr-new-ui",[816],"just announced"," that the Spring Initializr has been updated with a brand new UI and it is now available at ",[812,51340,30440],{"href":30440,"rel":51341},[816],". You can see a screen shot of it below but I have say my first impressions were all positive.",[651,51344,51345],{},[660,51346],{"alt":51347,"src":51348},"Spring Initializr New Look","./spring-init-new-look.png",[4542,51350,51352],{"id":51351},"what-is-the-spring-initializr","What is the Spring Initializr",[651,51354,51355,51356,51360],{},"Before I get into my thoughts on the redesign it might be a good idea to discuss what the Spring Initializr is for those of you who might be new to it. The ",[812,51357,7117],{"href":51358,"rel":51359},"https://start.spring.io",[816]," provides a web UI that will allow you the ability to quickly create a new Spring Boot project customized just the way you want it.",[651,51362,51363],{},"When I am describing this to students I often make the analogy of going to the grocery store when you're planning on preparing a big meal. You know what you need to make and you know what the ingredients are and now you need to gather them all up.",[651,51365,51366],{},"When you're building a new application you have a similar story. You know what you're building, even if you don't know all the details. If you find out later on you need to add new features you can certainly do so, the same way you inevitably will need to run out on Thanksgiving for those cranberries.",[651,51368,51369],{},[660,51370],{"alt":51371,"src":51372},"Shopping List","./shopping-list.png",[5909,51374,51376],{"id":51375},"spring-initializr-features","Spring Initializr Features",[651,51378,51379,51380,51383,51384,51389,51390,51393,51394,664],{},"It is worth mentioning that the Spring Intitializr at ",[812,51381,51358],{"href":51358,"rel":51382},[816]," isn't the only way to create a new project. IDE's like Eclips, IntelliJ IDEA Ultimate and NetBeans have this built in and make it really easy to select your dependencies. You can also kick start a project running a similar wizard from Visual Studio Code which is really handy. Finally if the command line is your thing you can create a new project using the ",[812,51385,51388],{"href":51386,"rel":51387},"https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#cli-init",[816],"Spring Boot CLI"," or by using ",[676,51391,51392],{},"cURL"," or ",[676,51395,51396],{},"HTTPie",[651,51398,51399],{},"The Spring Initalizr let's you customize the following options in your prjoject.",[5316,51401,51402,51411,51422,51425,51428],{},[5332,51403,51404,51405],{},"Build System\n",[5316,51406,51407,51409],{},[5332,51408,38919],{},[5332,51410,18326],{},[5332,51412,51413,51414],{},"Language\n",[5316,51415,51416,51418,51420],{},[5332,51417,36422],{},[5332,51419,38943],{},[5332,51421,18628],{},[5332,51423,51424],{},"Spring Boot Version",[5332,51426,51427],{},"Project Meta Data",[5332,51429,51430],{},"Dependencies",[4542,51432,51434],{"id":51433},"the-new-look-feel","The new look & feel",[651,51436,51437],{},"Now that you know a little bit about what the Spring Boot Initilaizr is let's talk about the new look and feel. As I said earlier in the article my first impressions were all great. I am someone who has always enjoyed a nice clean interface so this appealed to me. At a quick glance I was able to fill out everything I needed for my project but there is one small problem and to some folks its a big issue.",[651,51439,51440],{},"I didn't even notice this at first but I was scrolling through the comments on Damien's announcement I noticed a theme.",[651,51442,51443],{},[812,51444,51445],{"href":51445,"rel":51446},"https://twitter.com/oodamien/status/1102875897290473472",[816],[651,51448,51449],{},"The problem is that in the previous version of the Spring Initializr you could switch to a full version and see all of the available starters.",[651,51451,51452],{},[660,51453],{"alt":51454,"src":51455},"Spring Initializr Full Version","./spring-init-full-version.jpg",[651,51457,51458],{},"This was very useful for beginners and but I don't think it was just geared towards them. There are a ton of available dependencies and it's really hard to remember what is available. The team addressed this in their announcement post and I am hoping this is something they address in the future.",[651,51460,51461],{},"The important thing to note is that you will still get the full list if you're in an IDE like IntelliJ so this is only affecting the web UI.",[4542,51463,9042],{"id":9041},[651,51465,51466],{},"I really like the fresh and clean design but I think the lack of a full list of dependencies is a bit of a downer. Personally it doesn't affect me as much because I am the proud owner of IntelliJ Ultimate. It does hurt beginners though like my students who might be using the community edition and rely on the web UI to kick start their project.",[651,51468,51469,51470,51475],{},"I would love to hear your thoughts, find me on ",[812,51471,51474],{"href":51472,"rel":51473},"https://twitter.com/therealdanvega",[816],"Twitter"," and let me know what you think! Until then...",[651,51477,41105,51478,41109],{},[41107,51479],{},{"title":674,"searchDepth":790,"depth":790,"links":51481},[51482,51485,51486],{"id":51351,"depth":790,"text":51352,"children":51483},[51484],{"id":51375,"depth":892,"text":51376},{"id":51433,"depth":790,"text":51434},{"id":9041,"depth":790,"text":9042},"In this article I will tell you what the Spring Initializr is and give you my thoughts on the new redesign.",{"slug":51489,"date":51490,"published":797,"author":798,"tags":51491,"cover":51492},"spring-initializr-fresh-look","2019-03-07 17:00:00",[7055],"./spring-init-cover.png",{"title":522,"description":51487},"blog/2019/03/07/spring-initializr-fresh-look","GAPHEynW_FCiCeNl9p12bJvlKB5b6xN7s-J3QQsExkE",{"id":51497,"title":519,"body":51498,"description":52267,"extension":793,"meta":52268,"navigation":797,"path":520,"seo":52273,"stem":52274,"__hash__":52275},"content/blog/2019/03/14/find-max-array-objects-javascript.md",{"type":648,"value":51499,"toc":52258},[51500,51503,51506,51520,51524,51533,51556,51565,51777,51780,51783,51786,51866,51869,51872,51885,51973,51976,52050,52053,52063,52143,52150,52154,52162,52172,52210,52215,52218,52222,52232,52246,52248,52251,52255],[651,51501,51502],{},"I was recently putting together a tutorial for some students and I came across an issue that I am sure we have all come across at one time or another. I had this array of objects and in each object I had an id. I wanted a quick and efficient way to look at all the ids and find the max id.",[651,51504,51505],{},"In this article I will walk you through a 4 different solutions.",[5316,51507,51508,51511,51514,51517],{},[5332,51509,51510],{},"Array.forEach",[5332,51512,51513],{},"Array.map",[5332,51515,51516],{},"Array.reduce",[5332,51518,51519],{},"Math.max",[4542,51521,51523],{"id":51522},"setting-up-your-project","Setting up your project",[651,51525,51526,51527,51532],{},"I am using Node to run this example but you can just as easily use JavaScript and drop this into a web page. The first thing that I am going to do is to require the ",[812,51528,51531],{"href":51529,"rel":51530},"https://nodejs.org/api/assert.html",[816],"assert module"," from Node which gives us the ability to provide a simple set of assertion tests.",[669,51534,51536],{"className":25132,"code":51535,"language":25134,"meta":674,"style":674},"const assert = require(\"assert\");\n",[676,51537,51538],{"__ignoreMap":674},[679,51539,51540,51542,51545,51547,51549,51551,51554],{"class":681,"line":682},[679,51541,45172],{"class":685},[679,51543,51544],{"class":931}," assert",[679,51546,6883],{"class":685},[679,51548,45180],{"class":880},[679,51550,745],{"class":693},[679,51552,51553],{"class":689},"\"assert\"",[679,51555,1208],{"class":693},[651,51557,51558,51559,51564],{},"Next you are going to create an array of characters. I recently read the book ",[812,51560,51563],{"href":51561,"rel":51562},"https://amzn.to/2TFwvIC",[816],"Bad Blood"," so I decided to use some of the characters from that book. Side note: If you haven't read that book yet, I highly recommend it. Each character in the array is going to be an object that contains an id, first name and last name.",[669,51566,51568],{"className":25132,"code":51567,"language":25134,"meta":674,"style":674},"const characters = [\n { id: 1, first: \"Tim\", last: \"Draper\" },\n { id: 17, first: \"David\", last: \"Boies\" },\n { id: 199, first: \"Tim\", last: \"Kemp\" },\n { id: 75, first: \"Henry\", last: \"Mosley\" },\n { id: 444, first: \"Elizabeth\", last: \"Holmes\" },\n { id: 95, first: \"Donald\", last: \"Lucas\" },\n { id: 186, first: \"Larry\", last: \"Ellison\" },\n { id: 364, first: \"Channing\", last: \"Robertson\" },\n { id: 285, first: \"Charles\", last: \"Fleischmann\" },\n { id: 33, first: \"John\", last: \"Howard\" }\n];\n",[676,51569,51570,51581,51603,51622,51640,51659,51678,51697,51716,51735,51754,51773],{"__ignoreMap":674},[679,51571,51572,51574,51577,51579],{"class":681,"line":682},[679,51573,45172],{"class":685},[679,51575,51576],{"class":931}," characters",[679,51578,6883],{"class":685},[679,51580,44671],{"class":693},[679,51582,51583,51586,51588,51591,51594,51597,51600],{"class":681,"line":790},[679,51584,51585],{"class":693}," { id: ",[679,51587,1557],{"class":931},[679,51589,51590],{"class":693},", first: ",[679,51592,51593],{"class":689},"\"Tim\"",[679,51595,51596],{"class":693},", last: ",[679,51598,51599],{"class":689},"\"Draper\"",[679,51601,51602],{"class":693}," },\n",[679,51604,51605,51607,51610,51612,51615,51617,51620],{"class":681,"line":892},[679,51606,51585],{"class":693},[679,51608,51609],{"class":931},"17",[679,51611,51590],{"class":693},[679,51613,51614],{"class":689},"\"David\"",[679,51616,51596],{"class":693},[679,51618,51619],{"class":689},"\"Boies\"",[679,51621,51602],{"class":693},[679,51623,51624,51626,51629,51631,51633,51635,51638],{"class":681,"line":901},[679,51625,51585],{"class":693},[679,51627,51628],{"class":931},"199",[679,51630,51590],{"class":693},[679,51632,51593],{"class":689},[679,51634,51596],{"class":693},[679,51636,51637],{"class":689},"\"Kemp\"",[679,51639,51602],{"class":693},[679,51641,51642,51644,51647,51649,51652,51654,51657],{"class":681,"line":909},[679,51643,51585],{"class":693},[679,51645,51646],{"class":931},"75",[679,51648,51590],{"class":693},[679,51650,51651],{"class":689},"\"Henry\"",[679,51653,51596],{"class":693},[679,51655,51656],{"class":689},"\"Mosley\"",[679,51658,51602],{"class":693},[679,51660,51661,51663,51666,51668,51671,51673,51676],{"class":681,"line":918},[679,51662,51585],{"class":693},[679,51664,51665],{"class":931},"444",[679,51667,51590],{"class":693},[679,51669,51670],{"class":689},"\"Elizabeth\"",[679,51672,51596],{"class":693},[679,51674,51675],{"class":689},"\"Holmes\"",[679,51677,51602],{"class":693},[679,51679,51680,51682,51685,51687,51690,51692,51695],{"class":681,"line":935},[679,51681,51585],{"class":693},[679,51683,51684],{"class":931},"95",[679,51686,51590],{"class":693},[679,51688,51689],{"class":689},"\"Donald\"",[679,51691,51596],{"class":693},[679,51693,51694],{"class":689},"\"Lucas\"",[679,51696,51602],{"class":693},[679,51698,51699,51701,51704,51706,51709,51711,51714],{"class":681,"line":944},[679,51700,51585],{"class":693},[679,51702,51703],{"class":931},"186",[679,51705,51590],{"class":693},[679,51707,51708],{"class":689},"\"Larry\"",[679,51710,51596],{"class":693},[679,51712,51713],{"class":689},"\"Ellison\"",[679,51715,51602],{"class":693},[679,51717,51718,51720,51723,51725,51728,51730,51733],{"class":681,"line":959},[679,51719,51585],{"class":693},[679,51721,51722],{"class":931},"364",[679,51724,51590],{"class":693},[679,51726,51727],{"class":689},"\"Channing\"",[679,51729,51596],{"class":693},[679,51731,51732],{"class":689},"\"Robertson\"",[679,51734,51602],{"class":693},[679,51736,51737,51739,51742,51744,51747,51749,51752],{"class":681,"line":964},[679,51738,51585],{"class":693},[679,51740,51741],{"class":931},"285",[679,51743,51590],{"class":693},[679,51745,51746],{"class":689},"\"Charles\"",[679,51748,51596],{"class":693},[679,51750,51751],{"class":689},"\"Fleischmann\"",[679,51753,51602],{"class":693},[679,51755,51756,51758,51761,51763,51766,51768,51771],{"class":681,"line":977},[679,51757,51585],{"class":693},[679,51759,51760],{"class":931},"33",[679,51762,51590],{"class":693},[679,51764,51765],{"class":689},"\"John\"",[679,51767,51596],{"class":693},[679,51769,51770],{"class":689},"\"Howard\"",[679,51772,39987],{"class":693},[679,51774,51775],{"class":681,"line":982},[679,51776,47519],{"class":693},[651,51778,51779],{},"In each possible solution you want to write some logic that will return the largest id in the array.",[4542,51781,51510],{"id":51782},"arrayforeach",[651,51784,51785],{},"Your first thought might be to iterate over the array using some type of loop and why not, its what you have been taught to do since you started writing code. You can start off by declaring a max of zero, iterate over each character and if its id is larger than the max, update the max.",[669,51787,51789],{"className":25132,"code":51788,"language":25134,"meta":674,"style":674},"let max = 0;\ncharacters.forEach(character => {\n if (character.id > max) {\n max = character.id;\n }\n});\nassert(max === 444);\n",[676,51790,51791,51805,51821,51833,51843,51847,51851],{"__ignoreMap":674},[679,51792,51793,51796,51799,51801,51803],{"class":681,"line":682},[679,51794,51795],{"class":685},"let",[679,51797,51798],{"class":693}," max ",[679,51800,686],{"class":685},[679,51802,14987],{"class":931},[679,51804,1186],{"class":693},[679,51806,51807,51810,51812,51814,51817,51819],{"class":681,"line":790},[679,51808,51809],{"class":693},"characters.",[679,51811,46928],{"class":880},[679,51813,745],{"class":693},[679,51815,51816],{"class":2099},"character",[679,51818,44760],{"class":685},[679,51820,884],{"class":693},[679,51822,51823,51825,51828,51830],{"class":681,"line":892},[679,51824,47550],{"class":685},[679,51826,51827],{"class":693}," (character.id ",[679,51829,5860],{"class":685},[679,51831,51832],{"class":693}," max) {\n",[679,51834,51835,51838,51840],{"class":681,"line":901},[679,51836,51837],{"class":693}," max ",[679,51839,686],{"class":685},[679,51841,51842],{"class":693}," character.id;\n",[679,51844,51845],{"class":681,"line":909},[679,51846,21405],{"class":693},[679,51848,51849],{"class":681,"line":918},[679,51850,46842],{"class":693},[679,51852,51853,51856,51859,51861,51864],{"class":681,"line":935},[679,51854,51855],{"class":880},"assert",[679,51857,51858],{"class":693},"(max ",[679,51860,51108],{"class":685},[679,51862,51863],{"class":931}," 444",[679,51865,1208],{"class":693},[651,51867,51868],{},"My main objective every time I write code is to get something to work first and then improve upon it later. If you were to run this code it certainly works but it just doesn't seem right to me. What if you have one thousand or a million objects in the array? Any time that I start iterating over an array using some type of loop to perform som calculation that is a huge red flag for me.",[4542,51870,51513],{"id":51871},"arraymap",[651,51873,51874,51875,51880,51881,51884],{},"Whenever that red flag of iterating over an array comes up I immediately ask myself is this something that map/filter/reduce can solve. So if you were going to take that approach here you can start with the ",[812,51876,51879],{"href":51877,"rel":51878},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map",[816],"map method",". The ",[676,51882,51883],{},"map()"," method creates a new array with the results of calling a provided function on every element in the calling array. You will use the map method to create a new array that contains just the id's so you're no longer working with objects. At this point you can just use the normal sort method on the array, grab the last element in the list and that is your max id.",[669,51886,51888],{"className":25132,"code":51887,"language":25134,"meta":674,"style":674},"const ids = characters.map(user => user.id);\nconst sorted = ids.sort((a, b) => a - b);\nassert(sorted[sorted.length - 1] === 444);\n",[676,51889,51890,51914,51950],{"__ignoreMap":674},[679,51891,51892,51894,51897,51899,51902,51905,51907,51909,51911],{"class":681,"line":682},[679,51893,45172],{"class":685},[679,51895,51896],{"class":931}," ids",[679,51898,6883],{"class":685},[679,51900,51901],{"class":693}," characters.",[679,51903,51904],{"class":880},"map",[679,51906,745],{"class":693},[679,51908,9575],{"class":2099},[679,51910,44760],{"class":685},[679,51912,51913],{"class":693}," user.id);\n",[679,51915,51916,51918,51921,51923,51926,51929,51932,51934,51936,51938,51940,51942,51945,51947],{"class":681,"line":790},[679,51917,45172],{"class":685},[679,51919,51920],{"class":931}," sorted",[679,51922,6883],{"class":685},[679,51924,51925],{"class":693}," ids.",[679,51927,51928],{"class":880},"sort",[679,51930,51931],{"class":693},"((",[679,51933,812],{"class":2099},[679,51935,2797],{"class":693},[679,51937,14294],{"class":2099},[679,51939,2378],{"class":693},[679,51941,21350],{"class":685},[679,51943,51944],{"class":693}," a ",[679,51946,6453],{"class":685},[679,51948,51949],{"class":693}," b);\n",[679,51951,51952,51954,51957,51960,51962,51964,51967,51969,51971],{"class":681,"line":892},[679,51953,51855],{"class":880},[679,51955,51956],{"class":693},"(sorted[sorted.",[679,51958,51959],{"class":931},"length",[679,51961,35355],{"class":685},[679,51963,48606],{"class":931},[679,51965,51966],{"class":693},"] ",[679,51968,51108],{"class":685},[679,51970,51863],{"class":931},[679,51972,1208],{"class":693},[651,51974,51975],{},"If you wanted to get really fancy you can do all of that in a single statement.",[669,51977,51979],{"className":25132,"code":51978,"language":25134,"meta":674,"style":674},"assert(\n characters.map(user => user.id).sort((a, b) => a - b)[\n characters.length - 1\n ] === 444\n);\n",[676,51980,51981,51987,52024,52036,52046],{"__ignoreMap":674},[679,51982,51983,51985],{"class":681,"line":682},[679,51984,51855],{"class":880},[679,51986,21337],{"class":693},[679,51988,51989,51992,51994,51996,51998,52000,52003,52005,52007,52009,52011,52013,52015,52017,52019,52021],{"class":681,"line":790},[679,51990,51991],{"class":693}," characters.",[679,51993,51904],{"class":880},[679,51995,745],{"class":693},[679,51997,9575],{"class":2099},[679,51999,44760],{"class":685},[679,52001,52002],{"class":693}," user.id).",[679,52004,51928],{"class":880},[679,52006,51931],{"class":693},[679,52008,812],{"class":2099},[679,52010,2797],{"class":693},[679,52012,14294],{"class":2099},[679,52014,2378],{"class":693},[679,52016,21350],{"class":685},[679,52018,51944],{"class":693},[679,52020,6453],{"class":685},[679,52022,52023],{"class":693}," b)[\n",[679,52025,52026,52029,52031,52033],{"class":681,"line":892},[679,52027,52028],{"class":693}," characters.",[679,52030,51959],{"class":931},[679,52032,35355],{"class":685},[679,52034,52035],{"class":931}," 1\n",[679,52037,52038,52041,52043],{"class":681,"line":901},[679,52039,52040],{"class":693}," ] ",[679,52042,51108],{"class":685},[679,52044,52045],{"class":931}," 444\n",[679,52047,52048],{"class":681,"line":909},[679,52049,1208],{"class":693},[4542,52051,51516],{"id":52052},"arrayreduce",[651,52054,40060,52055,52058,52059,52062],{},[676,52056,52057],{},"reduce()"," method executes a reducer function (that you provide) on each member of the array resulting in a single output value. Here you are going to call reduce on the characters array. You will define a accumulator which will accumulate the callback's return value. Every time the callback function is called it will return a value and store it in max. The callback method is looking at the character id and if it's greater than max it returns that, if not it returns max. Finally it needs to set a default value for max and that is what ",[676,52060,52061],{},"characters[0].id"," is doing.",[669,52064,52066],{"className":25132,"code":52065,"language":25134,"meta":674,"style":674},"const maxId = characters.reduce(\n (max, character) => (character.id > max ? character.id : max),\n characters[0].id\n);\nassert(maxId === 444);\n",[676,52067,52068,52084,52116,52126,52130],{"__ignoreMap":674},[679,52069,52070,52072,52075,52077,52079,52082],{"class":681,"line":682},[679,52071,45172],{"class":685},[679,52073,52074],{"class":931}," maxId",[679,52076,6883],{"class":685},[679,52078,51901],{"class":693},[679,52080,52081],{"class":880},"reduce",[679,52083,21337],{"class":693},[679,52085,52086,52089,52092,52094,52096,52098,52100,52102,52104,52106,52108,52111,52113],{"class":681,"line":790},[679,52087,52088],{"class":693}," (",[679,52090,52091],{"class":2099},"max",[679,52093,2797],{"class":693},[679,52095,51816],{"class":2099},[679,52097,2378],{"class":693},[679,52099,21350],{"class":685},[679,52101,51827],{"class":693},[679,52103,5860],{"class":685},[679,52105,51798],{"class":693},[679,52107,2381],{"class":685},[679,52109,52110],{"class":693}," character.id ",[679,52112,2391],{"class":685},[679,52114,52115],{"class":693}," max),\n",[679,52117,52118,52121,52123],{"class":681,"line":892},[679,52119,52120],{"class":693}," characters[",[679,52122,1060],{"class":931},[679,52124,52125],{"class":693},"].id\n",[679,52127,52128],{"class":681,"line":901},[679,52129,1208],{"class":693},[679,52131,52132,52134,52137,52139,52141],{"class":681,"line":909},[679,52133,51855],{"class":880},[679,52135,52136],{"class":693},"(maxId ",[679,52138,51108],{"class":685},[679,52140,51863],{"class":931},[679,52142,1208],{"class":693},[651,52144,52145,52146,52149],{},"If you notice this is a similar approach to us using the forEach method, iterating over each element and comparing it to max. The difference here is using reduce is much faster. You can also use a combination of reduce and the next solution ",[676,52147,52148],{},"Math.max()"," if you want to.",[4542,52151,52153],{"id":52152},"the-spread-operator","The Spread Operator",[651,52155,52156,52157,52161],{},"While I like the map and reduce approach much better than iterating over the array it still doesn't feel great to me. If you dig into ",[812,52158,51519],{"href":52159,"rel":52160},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max",[816]," you will find out that it can take a list of numbers or an array of numbers.",[651,52163,52164,52165,52168,52169,52171],{},"This means that all you have to do is get an array of id's and you can pass that into the ",[676,52166,52167],{},"max()"," function. If you were paying attention you already did that earlier using map. This means that you can use the map function to get an array of id's and then pass it into the ",[676,52170,52167],{}," function using the spread operator.",[669,52173,52175],{"className":25132,"code":52174,"language":25134,"meta":674,"style":674},"assert(Math.max(...characters.map(user => user.id)) === 444);\n",[676,52176,52177],{"__ignoreMap":674},[679,52178,52179,52181,52184,52186,52188,52191,52193,52195,52197,52199,52201,52204,52206,52208],{"class":681,"line":682},[679,52180,51855],{"class":880},[679,52182,52183],{"class":693},"(Math.",[679,52185,52091],{"class":880},[679,52187,745],{"class":693},[679,52189,52190],{"class":685},"...",[679,52192,51809],{"class":693},[679,52194,51904],{"class":880},[679,52196,745],{"class":693},[679,52198,9575],{"class":2099},[679,52200,44760],{"class":685},[679,52202,52203],{"class":693}," user.id)) ",[679,52205,51108],{"class":685},[679,52207,51863],{"class":931},[679,52209,1208],{"class":693},[1004,52211,52212],{},[651,52213,52214],{},"Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.",[651,52216,52217],{},"This to me is the cleanest looking solution and I happen to really like it.",[4542,52219,52221],{"id":52220},"performance-results","Performance Results",[651,52223,52224,52225,27319,52228,52231],{},"I didn't mean to turn this into an article about functional programming in JavaScript but this is where we landed. I did some basic timing of each of the functions (using ",[676,52226,52227],{},"console.time()",[676,52229,52230],{},"console.timeEnd()",") and found the following results.",[5316,52233,52234,52237,52240,52243],{},[5332,52235,52236],{},"iterate test: 0.217ms",[5332,52238,52239],{},"map test: 0.191ms",[5332,52241,52242],{},"reduce test: 0.144ms",[5332,52244,52245],{},"spread test: 0.148ms",[4542,52247,9042],{"id":9041},[651,52249,52250],{},"While using reduce gave us an ever so slight performance advantage I prefer using Math.max and the spread operator here. To me it just looks cleaner and it is something that I enjoy writing. If you were given this problem what solution do you reach for? Am I missing any? I hope you enjoyed walking through how I would solve this problem and until next time...",[651,52252,41105,52253,41109],{},[41107,52254],{},[786,52256,52257],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":52259},[52260,52261,52262,52263,52264,52265,52266],{"id":51522,"depth":790,"text":51523},{"id":51782,"depth":790,"text":51510},{"id":51871,"depth":790,"text":51513},{"id":52052,"depth":790,"text":51516},{"id":52152,"depth":790,"text":52153},{"id":52220,"depth":790,"text":52221},{"id":9041,"depth":790,"text":9042},"A quick tutorial on the thought process on how you would go about finding the max id in an array of objects in JavaScript",{"slug":52269,"date":52270,"published":797,"author":798,"tags":52271,"cover":52272},"find-max-array-objects-javascript","2019-03-14T10:59:53.416Z",[25134],"joshua-aragon-EaB4Ml7C7fE-unsplash.jpg",{"title":519,"description":52267},"blog/2019/03/14/find-max-array-objects-javascript","hfSJtyp7n7g_8Xby9woAGsSbCxk0fFc0NB6duqtSRCg",{"id":52277,"title":516,"body":52278,"description":53676,"extension":793,"meta":53677,"navigation":797,"path":517,"seo":53682,"stem":53683,"__hash__":53684},"content/blog/2019/04/09/adding-twitter-cards-to-gridsome.md",{"type":648,"value":52279,"toc":53670},[52280,52289,52292,52296,52299,52306,52359,52362,52551,52554,52658,52661,52665,52674,52819,52826,52940,52951,52954,53010,53013,53022,53080,53083,53235,53244,53650,53653,53658,53660,53667],[651,52281,52282,52283,52288],{},"In a ",[812,52284,52287],{"href":52285,"rel":52286},"https://www.danvega.dev/blog",[816],"previous article",", I showed you how you could add Twitter Cards to your blog. If you haven't gone through that post yet I would suggest doing so before reading this one.",[651,52290,52291],{},"In this article, I am going to show you how to add Twitter Cards to your Gridsome Blog. This is not only for Twitter cards but more of a general example on adding meta tags to your blog posts.",[4542,52293,52295],{"id":52294},"blog-posts-in-gridsome","Blog Posts in Gridsome",[651,52297,52298],{},"I am going to make an assumption with this article that you have at least played around with Gridsome. If you're looking for a begginers guide to Gridsome I have one coming but give me a shout out on Twitter and let me know that you're interested in it.",[651,52300,52301,52302,52305],{},"In my ",[676,52303,52304],{},"gridsome.config.js"," I have defined the @gridsome/source-filesystem plugin and the key part here is the type name. This is the GraphQL type and template name. A .vue file in src/templates must match the typeName to have a template for it.",[669,52307,52309],{"className":25132,"code":52308,"language":25134,"meta":674,"style":674},"plugins: [{\n use: '@gridsome/source-filesystem',\n options: {\n path: 'blog/**/*.md',\n typeName: 'Post',\n route: '/blog/:year/:month/:day/:slug',\n",[676,52310,52311,52319,52328,52332,52341,52350],{"__ignoreMap":674},[679,52312,52313,52316],{"class":681,"line":682},[679,52314,52315],{"class":880},"plugins",[679,52317,52318],{"class":693},": [{\n",[679,52320,52321,52323,52326],{"class":681,"line":790},[679,52322,43921],{"class":693},[679,52324,52325],{"class":689},"'@gridsome/source-filesystem'",[679,52327,12083],{"class":693},[679,52329,52330],{"class":681,"line":892},[679,52331,43931],{"class":693},[679,52333,52334,52336,52339],{"class":681,"line":901},[679,52335,43936],{"class":693},[679,52337,52338],{"class":689},"'blog/**/*.md'",[679,52340,12083],{"class":693},[679,52342,52343,52345,52348],{"class":681,"line":909},[679,52344,43946],{"class":693},[679,52346,52347],{"class":689},"'Post'",[679,52349,12083],{"class":693},[679,52351,52352,52354,52357],{"class":681,"line":918},[679,52353,43956],{"class":693},[679,52355,52356],{"class":689},"'/blog/:year/:month/:day/:slug'",[679,52358,12083],{"class":693},[651,52360,52361],{},"If we look at the Post template the first thing you will see is the markup for each blog post. Again this is a single blog post, there is a separate template for the page that lists posts.",[669,52363,52365],{"className":4496,"code":52364,"language":4498,"meta":674,"style":674},"\u003CLayout>\n \u003Cdiv class=\"article content\">\n \u003Ch1 class=\"title is-2 article-title\">{{ $page.post.title }}\u003C/h1>\n \u003Csmall class=\"about\">{{ formatCreatedOn }} • ☕️ {{ $page.post.timeToRead }} min read\u003C/small>\n \u003Cg-image v-if=\"$page.post.cover\" :src=\"$page.post.cover\" class=\"cover\"/>\n \u003Carticle v-html=\"$page.post.content\" class=\"article\"/>\n \u003Cconvert-kit uid=\"44cc02ed05\" script=\"https://f.convertkit.com/44cc02ed05/38739557e4.js\">\u003C/convert-kit>\n \u003C/div>\n \u003Cbulma-tag :tags=\"$page.post.tags\"/>\n\u003C/Layout>\n",[676,52366,52367,52376,52391,52411,52432,52463,52489,52518,52526,52543],{"__ignoreMap":674},[679,52368,52369,52371,52374],{"class":681,"line":682},[679,52370,4505],{"class":693},[679,52372,52373],{"class":6561},"Layout",[679,52375,4519],{"class":693},[679,52377,52378,52380,52382,52384,52386,52389],{"class":681,"line":790},[679,52379,11738],{"class":693},[679,52381,4509],{"class":4508},[679,52383,4512],{"class":880},[679,52385,686],{"class":693},[679,52387,52388],{"class":689},"\"article content\"",[679,52390,4519],{"class":693},[679,52392,52393,52395,52397,52399,52401,52404,52407,52409],{"class":681,"line":892},[679,52394,4524],{"class":693},[679,52396,11859],{"class":4508},[679,52398,4512],{"class":880},[679,52400,686],{"class":693},[679,52402,52403],{"class":689},"\"title is-2 article-title\"",[679,52405,52406],{"class":693},">{{ $page.post.title }}\u003C/",[679,52408,11859],{"class":4508},[679,52410,4519],{"class":693},[679,52412,52413,52415,52418,52420,52422,52425,52428,52430],{"class":681,"line":901},[679,52414,4524],{"class":693},[679,52416,52417],{"class":4508},"small",[679,52419,4512],{"class":880},[679,52421,686],{"class":693},[679,52423,52424],{"class":689},"\"about\"",[679,52426,52427],{"class":693},">{{ formatCreatedOn }} • ☕️ {{ $page.post.timeToRead }} min read\u003C/",[679,52429,52417],{"class":4508},[679,52431,4519],{"class":693},[679,52433,52434,52436,52439,52442,52444,52447,52450,52452,52454,52456,52458,52461],{"class":681,"line":909},[679,52435,4524],{"class":693},[679,52437,52438],{"class":4508},"g-image",[679,52440,52441],{"class":880}," v-if",[679,52443,686],{"class":693},[679,52445,52446],{"class":689},"\"$page.post.cover\"",[679,52448,52449],{"class":880}," :src",[679,52451,686],{"class":693},[679,52453,52446],{"class":689},[679,52455,4512],{"class":880},[679,52457,686],{"class":693},[679,52459,52460],{"class":689},"\"cover\"",[679,52462,18854],{"class":693},[679,52464,52465,52467,52470,52473,52475,52478,52480,52482,52485,52487],{"class":681,"line":918},[679,52466,4524],{"class":693},[679,52468,52469],{"class":4508},"article",[679,52471,52472],{"class":880}," v-html",[679,52474,686],{"class":693},[679,52476,52477],{"class":689},"\"$page.post.content\"",[679,52479,4512],{"class":880},[679,52481,686],{"class":693},[679,52483,52484],{"class":689},"\"article\"",[679,52486,4408],{"class":6561},[679,52488,4519],{"class":693},[679,52490,52491,52493,52496,52499,52501,52504,52507,52509,52512,52514,52516],{"class":681,"line":935},[679,52492,4524],{"class":693},[679,52494,52495],{"class":4508},"convert-kit",[679,52497,52498],{"class":880}," uid",[679,52500,686],{"class":693},[679,52502,52503],{"class":689},"\"44cc02ed05\"",[679,52505,52506],{"class":880}," script",[679,52508,686],{"class":693},[679,52510,52511],{"class":689},"\"https://f.convertkit.com/44cc02ed05/38739557e4.js\"",[679,52513,4563],{"class":693},[679,52515,52495],{"class":4508},[679,52517,4519],{"class":693},[679,52519,52520,52522,52524],{"class":681,"line":944},[679,52521,11840],{"class":693},[679,52523,4509],{"class":4508},[679,52525,4519],{"class":693},[679,52527,52528,52530,52533,52536,52538,52541],{"class":681,"line":959},[679,52529,11738],{"class":693},[679,52531,52532],{"class":4508},"bulma-tag",[679,52534,52535],{"class":880}," :tags",[679,52537,686],{"class":693},[679,52539,52540],{"class":689},"\"$page.post.tags\"",[679,52542,18854],{"class":693},[679,52544,52545,52547,52549],{"class":681,"line":964},[679,52546,4586],{"class":693},[679,52548,52373],{"class":6561},[679,52550,4519],{"class":693},[651,52552,52553],{},"This is all of the content for this blog post, so where is that coming from? If you look below the markup there is a GraphQL page query that pulls the information for this post based on the path.",[669,52555,52557],{"className":25132,"code":52556,"language":25134,"meta":674,"style":674},"\u003Cpage-query>\nquery Post ($path: String!) {\n post: post (path: $path) {\n title\n content\n timeToRead\n cover\n slug\n date\n excerpt\n tags {\n id\n title\n path\n }\n }\n}\n\u003C/page-query>\n",[676,52558,52559,52568,52573,52583,52588,52593,52598,52603,52608,52613,52618,52623,52628,52633,52638,52642,52646,52650],{"__ignoreMap":674},[679,52560,52561,52563,52566],{"class":681,"line":682},[679,52562,4505],{"class":693},[679,52564,52565],{"class":931},"page-query",[679,52567,4519],{"class":693},[679,52569,52570],{"class":681,"line":790},[679,52571,52572],{"class":693},"query Post ($path: String!) {\n",[679,52574,52575,52578,52580],{"class":681,"line":892},[679,52576,52577],{"class":693}," post: ",[679,52579,5100],{"class":880},[679,52581,52582],{"class":693}," (path: $path) {\n",[679,52584,52585],{"class":681,"line":901},[679,52586,52587],{"class":693}," title\n",[679,52589,52590],{"class":681,"line":909},[679,52591,52592],{"class":693}," content\n",[679,52594,52595],{"class":681,"line":918},[679,52596,52597],{"class":693}," timeToRead\n",[679,52599,52600],{"class":681,"line":935},[679,52601,52602],{"class":693}," cover\n",[679,52604,52605],{"class":681,"line":944},[679,52606,52607],{"class":693}," slug\n",[679,52609,52610],{"class":681,"line":959},[679,52611,52612],{"class":693}," date\n",[679,52614,52615],{"class":681,"line":964},[679,52616,52617],{"class":693}," excerpt\n",[679,52619,52620],{"class":681,"line":977},[679,52621,52622],{"class":693}," tags {\n",[679,52624,52625],{"class":681,"line":982},[679,52626,52627],{"class":693}," id\n",[679,52629,52630],{"class":681,"line":988},[679,52631,52632],{"class":693}," title\n",[679,52634,52635],{"class":681,"line":993},[679,52636,52637],{"class":693}," path\n",[679,52639,52640],{"class":681,"line":2129},[679,52641,985],{"class":693},[679,52643,52644],{"class":681,"line":2140},[679,52645,21405],{"class":693},[679,52647,52648],{"class":681,"line":2145},[679,52649,996],{"class":693},[679,52651,52652,52654,52656],{"class":681,"line":2154},[679,52653,4586],{"class":693},[679,52655,52565],{"class":931},[679,52657,4519],{"class":693},[651,52659,52660],{},"Now that you are all caught up let's jump in to how we can create a Twitter card for each blog post.",[4542,52662,52664],{"id":52663},"gridsome-meta-tags","Gridsome Meta Tags",[651,52666,52667,52668,52673],{},"If you ",[812,52669,52672],{"href":52670,"rel":52671},"https://www.danvega.dev/blog/2019/02/18/twitter-cards-meta-tags",[816],"read my previous article"," you know that it isn't that hard to create a twitter card. All we need to do is add some meta tags that end up looking something like this.",[669,52675,52677],{"className":4496,"code":52676,"language":4498,"meta":674,"style":674},"\u003Chead>\n \u003Cmeta name=\"twitter:card\" content=\"summary_large_image\" />\n \u003Cmeta name=\"twitter:description\" content=\"How to create your first npm package and publish it.\"/>\n \u003Cmeta name=\"twitter:title\" content=\"Creating your first npm package\" />\n \u003Cmeta name=\"twitter:site\" content=\"@therealdanvega\" />\n \u003Cmeta name=\"twitter:image\" content=\"https://www.danvega.me/assets/static/npm_cover.bd64798.eced3da.png\"/>\n \u003Cmeta name=\"twitter:creator\" content=\"@therealdanvega\" />\n\u003C/head>\n",[676,52678,52679,52687,52707,52729,52749,52769,52791,52811],{"__ignoreMap":674},[679,52680,52681,52683,52685],{"class":681,"line":682},[679,52682,4505],{"class":693},[679,52684,11741],{"class":4508},[679,52686,4519],{"class":693},[679,52688,52689,52691,52693,52695,52697,52699,52701,52703,52705],{"class":681,"line":790},[679,52690,11738],{"class":693},[679,52692,11968],{"class":4508},[679,52694,5283],{"class":880},[679,52696,686],{"class":693},[679,52698,48118],{"class":689},[679,52700,11995],{"class":880},[679,52702,686],{"class":693},[679,52704,48125],{"class":689},[679,52706,5387],{"class":693},[679,52708,52709,52711,52713,52715,52717,52720,52722,52724,52727],{"class":681,"line":892},[679,52710,11738],{"class":693},[679,52712,11968],{"class":4508},[679,52714,5283],{"class":880},[679,52716,686],{"class":693},[679,52718,52719],{"class":689},"\"twitter:description\"",[679,52721,11995],{"class":880},[679,52723,686],{"class":693},[679,52725,52726],{"class":689},"\"How to create your first npm package and publish it.\"",[679,52728,18854],{"class":693},[679,52730,52731,52733,52735,52737,52739,52741,52743,52745,52747],{"class":681,"line":901},[679,52732,11738],{"class":693},[679,52734,11968],{"class":4508},[679,52736,5283],{"class":880},[679,52738,686],{"class":693},[679,52740,48244],{"class":689},[679,52742,11995],{"class":880},[679,52744,686],{"class":693},[679,52746,48251],{"class":689},[679,52748,5387],{"class":693},[679,52750,52751,52753,52755,52757,52759,52761,52763,52765,52767],{"class":681,"line":909},[679,52752,11738],{"class":693},[679,52754,11968],{"class":4508},[679,52756,5283],{"class":880},[679,52758,686],{"class":693},[679,52760,48266],{"class":689},[679,52762,11995],{"class":880},[679,52764,686],{"class":693},[679,52766,48273],{"class":689},[679,52768,5387],{"class":693},[679,52770,52771,52773,52775,52777,52779,52782,52784,52786,52789],{"class":681,"line":918},[679,52772,11738],{"class":693},[679,52774,11968],{"class":4508},[679,52776,5283],{"class":880},[679,52778,686],{"class":693},[679,52780,52781],{"class":689},"\"twitter:image\"",[679,52783,11995],{"class":880},[679,52785,686],{"class":693},[679,52787,52788],{"class":689},"\"https://www.danvega.me/assets/static/npm_cover.bd64798.eced3da.png\"",[679,52790,18854],{"class":693},[679,52792,52793,52795,52797,52799,52801,52803,52805,52807,52809],{"class":681,"line":935},[679,52794,11738],{"class":693},[679,52796,11968],{"class":4508},[679,52798,5283],{"class":880},[679,52800,686],{"class":693},[679,52802,48316],{"class":689},[679,52804,11995],{"class":880},[679,52806,686],{"class":693},[679,52808,48273],{"class":689},[679,52810,5387],{"class":693},[679,52812,52813,52815,52817],{"class":681,"line":944},[679,52814,4586],{"class":693},[679,52816,11741],{"class":4508},[679,52818,4519],{"class":693},[651,52820,52821,52822,52825],{},"The challenge here is that you want each blog post to generate tags specific to that article. Fortunately for us Gridsome gives us a really easy way to populate metadata in our components. All you need to do is add the ",[676,52823,52824],{},"metaInfo"," object to your components script section.",[669,52827,52829],{"className":25132,"code":52828,"language":25134,"meta":674,"style":674},"\u003Cscript>\nexport default {\n name: 'About',\n metaInfo: {\n title: 'About us',\n meta: [\n { name: 'author', content: 'John Doe' }\n ],\n link: [\n { rel: 'stylesheet', href: '/css/index.css' },\n ]\n // etc...\n }\n}\n\u003C/script>\n",[676,52830,52831,52839,52843,52852,52857,52867,52872,52888,52893,52898,52914,52919,52924,52928,52932],{"__ignoreMap":674},[679,52832,52833,52835,52837],{"class":681,"line":682},[679,52834,4505],{"class":693},[679,52836,47668],{"class":4508},[679,52838,4519],{"class":693},[679,52840,52841],{"class":681,"line":790},[679,52842,49965],{"class":693},[679,52844,52845,52847,52850],{"class":681,"line":892},[679,52846,49970],{"class":693},[679,52848,52849],{"class":689},"'About'",[679,52851,12083],{"class":693},[679,52853,52854],{"class":681,"line":901},[679,52855,52856],{"class":693}," metaInfo: {\n",[679,52858,52859,52862,52865],{"class":681,"line":909},[679,52860,52861],{"class":693}," title: ",[679,52863,52864],{"class":689},"'About us'",[679,52866,12083],{"class":693},[679,52868,52869],{"class":681,"line":918},[679,52870,52871],{"class":693}," meta: [\n",[679,52873,52874,52877,52880,52883,52886],{"class":681,"line":935},[679,52875,52876],{"class":693}," { name: ",[679,52878,52879],{"class":689},"'author'",[679,52881,52882],{"class":693},", content: ",[679,52884,52885],{"class":689},"'John Doe'",[679,52887,39987],{"class":693},[679,52889,52890],{"class":681,"line":944},[679,52891,52892],{"class":693}," ],\n",[679,52894,52895],{"class":681,"line":959},[679,52896,52897],{"class":693}," link: [\n",[679,52899,52900,52903,52906,52909,52912],{"class":681,"line":964},[679,52901,52902],{"class":693}," { rel: ",[679,52904,52905],{"class":689},"'stylesheet'",[679,52907,52908],{"class":693},", href: ",[679,52910,52911],{"class":689},"'/css/index.css'",[679,52913,51602],{"class":693},[679,52915,52916],{"class":681,"line":977},[679,52917,52918],{"class":693}," ]\n",[679,52920,52921],{"class":681,"line":982},[679,52922,52923],{"class":1400}," // etc...\n",[679,52925,52926],{"class":681,"line":988},[679,52927,21405],{"class":693},[679,52929,52930],{"class":681,"line":993},[679,52931,996],{"class":693},[679,52933,52934,52936,52938],{"class":681,"line":2129},[679,52935,4586],{"class":693},[679,52937,47668],{"class":4508},[679,52939,4519],{"class":693},[651,52941,52942],{},[7300,52943,52944,52945,52950],{},"Under the hood Gridsome uses ",[812,52946,52949],{"href":52947,"rel":52948},"https://github.com/nuxt/vue-meta",[816],"Vue Meta"," if you're interested in learning how it works.",[651,52952,52953],{},"In the metaInfo object you can define the following properties.",[1031,52955,52956,52965],{},[1034,52957,52958],{},[1037,52959,52960,52963],{},[1040,52961,52962],{},"Property",[1040,52964,12285],{},[1050,52966,52967,52974,52981,52988,52995,53003],{},[1037,52968,52969,52971],{},[1055,52970,786],{},[1055,52972,52973],{},"Adds a style tag",[1037,52975,52976,52978],{},[1055,52977,47668],{},[1055,52979,52980],{},"Adds a script tag",[1037,52982,52983,52985],{},[1055,52984,11968],{},[1055,52986,52987],{},"Adds a meta tag",[1037,52989,52990,52992],{},[1055,52991,11750],{},[1055,52993,52994],{},"Changes title text",[1037,52996,52997,53000],{},[1055,52998,52999],{},"titleTemplate",[1055,53001,53002],{},"Dynamic title text",[1037,53004,53005,53007],{},[1055,53006,6409],{},[1055,53008,53009],{},"Adds a link tag",[4542,53011,52664],{"id":53012},"gridsome-meta-tags-1",[651,53014,53015,53016,53018,53019,53021],{},"If you notice one of the properties in the ",[676,53017,52824],{}," object that you can add is ",[676,53020,11968],{},". This will allow us to create a meta tag using the following format:",[669,53023,53025],{"className":25132,"code":53024,"language":25134,"meta":674,"style":674},"metaInfo: {\n return {\n title: this.$page.post.title,\n meta: [\n { name: 'META_NAME_HERE' content: 'META_CONTENT_HERE' }\n ],\n };\n},\n",[676,53026,53027,53033,53039,53048,53052,53067,53071,53076],{"__ignoreMap":674},[679,53028,53029,53031],{"class":681,"line":682},[679,53030,52824],{"class":880},[679,53032,28468],{"class":693},[679,53034,53035,53037],{"class":681,"line":790},[679,53036,44767],{"class":685},[679,53038,884],{"class":693},[679,53040,53041,53043,53045],{"class":681,"line":892},[679,53042,52861],{"class":693},[679,53044,4732],{"class":931},[679,53046,53047],{"class":693},".$page.post.title,\n",[679,53049,53050],{"class":681,"line":901},[679,53051,52871],{"class":693},[679,53053,53054,53056,53059,53062,53065],{"class":681,"line":909},[679,53055,52876],{"class":693},[679,53057,53058],{"class":689},"'META_NAME_HERE'",[679,53060,53061],{"class":693}," content: ",[679,53063,53064],{"class":689},"'META_CONTENT_HERE'",[679,53066,39987],{"class":693},[679,53068,53069],{"class":681,"line":918},[679,53070,52892],{"class":693},[679,53072,53073],{"class":681,"line":935},[679,53074,53075],{"class":693}," };\n",[679,53077,53078],{"class":681,"line":944},[679,53079,6481],{"class":693},[651,53081,53082],{},"With that you have everything you need to create your meta tags. In the following snippet I am creating a description meta tag and each of the tags that I need for my Twitter card. For each of the tags I am populating the content with data from the post that we are on. The script property that I am adding is the Twitter API that we need to include for the cards to work.",[669,53084,53086],{"className":25132,"code":53085,"language":25134,"meta":674,"style":674},"metaInfo() {\n return {\n title: this.$page.post.title,\n meta: [\n { name: \"description\", content: this.$page.post.excerpt },\n // twitter-card: https://cards-dev.twitter.com/validator\n { name: \"twitter:card\", content: \"summary_large_image\" },\n { name: \"twitter:description\", content: this.$page.post.excerpt },\n { name: \"twitter:title\", content: this.$page.post.title },\n { name: \"twitter:site\", content: \"@therealdanvega\" },\n { name: \"twitter:image\", content: this.getCoverImage },\n { name: \"twitter:creator\", content: \"@therealdanvega\" }\n ],\n script: [{ src: \"https://platform.twitter.com/widgets.js\", async: true }]\n };\n },\n",[676,53087,53088,53094,53100,53109,53114,53128,53133,53145,53157,53170,53182,53195,53207,53211,53227,53231],{"__ignoreMap":674},[679,53089,53090,53092],{"class":681,"line":682},[679,53091,52824],{"class":880},[679,53093,2667],{"class":693},[679,53095,53096,53098],{"class":681,"line":790},[679,53097,21478],{"class":685},[679,53099,884],{"class":693},[679,53101,53102,53105,53107],{"class":681,"line":892},[679,53103,53104],{"class":693}," title: ",[679,53106,4732],{"class":931},[679,53108,53047],{"class":693},[679,53110,53111],{"class":681,"line":901},[679,53112,53113],{"class":693}," meta: [\n",[679,53115,53116,53119,53121,53123,53125],{"class":681,"line":909},[679,53117,53118],{"class":693}," { name: ",[679,53120,47908],{"class":689},[679,53122,52882],{"class":693},[679,53124,4732],{"class":931},[679,53126,53127],{"class":693},".$page.post.excerpt },\n",[679,53129,53130],{"class":681,"line":918},[679,53131,53132],{"class":1400}," // twitter-card: https://cards-dev.twitter.com/validator\n",[679,53134,53135,53137,53139,53141,53143],{"class":681,"line":935},[679,53136,53118],{"class":693},[679,53138,48118],{"class":689},[679,53140,52882],{"class":693},[679,53142,48125],{"class":689},[679,53144,51602],{"class":693},[679,53146,53147,53149,53151,53153,53155],{"class":681,"line":944},[679,53148,53118],{"class":693},[679,53150,52719],{"class":689},[679,53152,52882],{"class":693},[679,53154,4732],{"class":931},[679,53156,53127],{"class":693},[679,53158,53159,53161,53163,53165,53167],{"class":681,"line":959},[679,53160,53118],{"class":693},[679,53162,48244],{"class":689},[679,53164,52882],{"class":693},[679,53166,4732],{"class":931},[679,53168,53169],{"class":693},".$page.post.title },\n",[679,53171,53172,53174,53176,53178,53180],{"class":681,"line":964},[679,53173,53118],{"class":693},[679,53175,48266],{"class":689},[679,53177,52882],{"class":693},[679,53179,48273],{"class":689},[679,53181,51602],{"class":693},[679,53183,53184,53186,53188,53190,53192],{"class":681,"line":977},[679,53185,53118],{"class":693},[679,53187,52781],{"class":689},[679,53189,52882],{"class":693},[679,53191,4732],{"class":931},[679,53193,53194],{"class":693},".getCoverImage },\n",[679,53196,53197,53199,53201,53203,53205],{"class":681,"line":982},[679,53198,53118],{"class":693},[679,53200,48316],{"class":689},[679,53202,52882],{"class":693},[679,53204,48273],{"class":689},[679,53206,39987],{"class":693},[679,53208,53209],{"class":681,"line":988},[679,53210,28544],{"class":693},[679,53212,53213,53216,53219,53222,53224],{"class":681,"line":993},[679,53214,53215],{"class":693}," script: [{ src: ",[679,53217,53218],{"class":689},"\"https://platform.twitter.com/widgets.js\"",[679,53220,53221],{"class":693},", async: ",[679,53223,3441],{"class":931},[679,53225,53226],{"class":693}," }]\n",[679,53228,53229],{"class":681,"line":2129},[679,53230,50000],{"class":693},[679,53232,53233],{"class":681,"line":2140},[679,53234,28483],{"class":693},[651,53236,53237,53238,53243],{},"For more information on what each of these meta tags do you can check out the ",[812,53239,53242],{"href":53240,"rel":53241},"https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started.html",[816],"Twitter Cards Documentation",". This will produce html in your head that looks like this",[669,53245,53247],{"className":4496,"code":53246,"language":4498,"meta":674,"style":674}," \u003Chead>\n \u003Ctitle data-vue-tag=\"true\">How to add Twitter Card Meta Tags to your Blog - Dan Vega\u003C/title>\n \u003Cmeta data-vue-tag=\"true\" charset=\"utf-8\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"generator\" content=\"Gridsome v0.5.0\" />\n \u003Cmeta data-vue-tag=\"true\" data-key=\"viewport\" name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\" />\n \u003Cmeta data-vue-tag=\"true\" data-key=\"description\" name=\"description\" content=\"Person blog of Dan Vega\" />\n \u003Cmeta data-vue-tag=\"true\" data-key=\"format-detection\" name=\"format-detection\" content=\"telephone=no\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"description\" content=\"In this tutorial you will learn what a Twitter Card is along with step by step instructions how to add them to your blog and validate that they are working.\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"twitter:card\" content=\"summary_large_image\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"twitter:description\" content=\"In this tutorial you will learn what a Twitter Card is along with step by step instructions how to add them to your blog and validate that they are working.\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"twitter:title\" content=\"How to add Twitter Card Meta Tags to your Blog\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"twitter:site\" content=\"@therealdanvega\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"twitter:image\" content=\"https://www.danvega.dev/assets/static/twitter-cards.bd64798.89214ec.png\" />\n \u003Cmeta data-vue-tag=\"true\" name=\"twitter:creator\" content=\"@therealdanvega\" />\n \u003Cscript data-vue-tag=\"true\" src=\"https://platform.twitter.com/widgets.js\" async=\"true\">\u003C/script>\n \u003C/head>\n",[676,53248,53249,53257,53277,53297,53325,53359,53392,53426,53453,53479,53505,53532,53558,53585,53611,53642],{"__ignoreMap":674},[679,53250,53251,53253,53255],{"class":681,"line":682},[679,53252,11738],{"class":693},[679,53254,11741],{"class":4508},[679,53256,4519],{"class":693},[679,53258,53259,53261,53263,53266,53268,53270,53273,53275],{"class":681,"line":790},[679,53260,4524],{"class":693},[679,53262,11750],{"class":4508},[679,53264,53265],{"class":880}," data-vue-tag",[679,53267,686],{"class":693},[679,53269,10249],{"class":689},[679,53271,53272],{"class":693},">How to add Twitter Card Meta Tags to your Blog - Dan Vega\u003C/",[679,53274,11750],{"class":4508},[679,53276,4519],{"class":693},[679,53278,53279,53281,53283,53285,53287,53289,53291,53293,53295],{"class":681,"line":892},[679,53280,4524],{"class":693},[679,53282,11968],{"class":4508},[679,53284,53265],{"class":880},[679,53286,686],{"class":693},[679,53288,10249],{"class":689},[679,53290,11971],{"class":880},[679,53292,686],{"class":693},[679,53294,11976],{"class":689},[679,53296,5387],{"class":693},[679,53298,53299,53301,53303,53305,53307,53309,53311,53313,53316,53318,53320,53323],{"class":681,"line":901},[679,53300,4524],{"class":693},[679,53302,11968],{"class":4508},[679,53304,53265],{"class":880},[679,53306,686],{"class":693},[679,53308,10249],{"class":689},[679,53310,5283],{"class":880},[679,53312,686],{"class":693},[679,53314,53315],{"class":689},"\"generator\"",[679,53317,11995],{"class":880},[679,53319,686],{"class":693},[679,53321,53322],{"class":689},"\"Gridsome v0.5.0\"",[679,53324,5387],{"class":693},[679,53326,53327,53329,53331,53333,53335,53337,53340,53342,53344,53346,53348,53350,53352,53354,53357],{"class":681,"line":909},[679,53328,4524],{"class":693},[679,53330,11968],{"class":4508},[679,53332,53265],{"class":880},[679,53334,686],{"class":693},[679,53336,10249],{"class":689},[679,53338,53339],{"class":880}," data-key",[679,53341,686],{"class":693},[679,53343,12015],{"class":689},[679,53345,5283],{"class":880},[679,53347,686],{"class":693},[679,53349,12015],{"class":689},[679,53351,11995],{"class":880},[679,53353,686],{"class":693},[679,53355,53356],{"class":689},"\"width=device-width, initial-scale=1, viewport-fit=cover\"",[679,53358,5387],{"class":693},[679,53360,53361,53363,53365,53367,53369,53371,53373,53375,53377,53379,53381,53383,53385,53387,53390],{"class":681,"line":918},[679,53362,4524],{"class":693},[679,53364,11968],{"class":4508},[679,53366,53265],{"class":880},[679,53368,686],{"class":693},[679,53370,10249],{"class":689},[679,53372,53339],{"class":880},[679,53374,686],{"class":693},[679,53376,47908],{"class":689},[679,53378,5283],{"class":880},[679,53380,686],{"class":693},[679,53382,47908],{"class":689},[679,53384,11995],{"class":880},[679,53386,686],{"class":693},[679,53388,53389],{"class":689},"\"Person blog of Dan Vega\"",[679,53391,5387],{"class":693},[679,53393,53394,53396,53398,53400,53402,53404,53406,53408,53411,53413,53415,53417,53419,53421,53424],{"class":681,"line":935},[679,53395,4524],{"class":693},[679,53397,11968],{"class":4508},[679,53399,53265],{"class":880},[679,53401,686],{"class":693},[679,53403,10249],{"class":689},[679,53405,53339],{"class":880},[679,53407,686],{"class":693},[679,53409,53410],{"class":689},"\"format-detection\"",[679,53412,5283],{"class":880},[679,53414,686],{"class":693},[679,53416,53410],{"class":689},[679,53418,11995],{"class":880},[679,53420,686],{"class":693},[679,53422,53423],{"class":689},"\"telephone=no\"",[679,53425,5387],{"class":693},[679,53427,53428,53430,53432,53434,53436,53438,53440,53442,53444,53446,53448,53451],{"class":681,"line":944},[679,53429,4524],{"class":693},[679,53431,11968],{"class":4508},[679,53433,53265],{"class":880},[679,53435,686],{"class":693},[679,53437,10249],{"class":689},[679,53439,5283],{"class":880},[679,53441,686],{"class":693},[679,53443,47908],{"class":689},[679,53445,11995],{"class":880},[679,53447,686],{"class":693},[679,53449,53450],{"class":689},"\"In this tutorial you will learn what a Twitter Card is along with step by step instructions how to add them to your blog and validate that they are working.\"",[679,53452,5387],{"class":693},[679,53454,53455,53457,53459,53461,53463,53465,53467,53469,53471,53473,53475,53477],{"class":681,"line":959},[679,53456,4524],{"class":693},[679,53458,11968],{"class":4508},[679,53460,53265],{"class":880},[679,53462,686],{"class":693},[679,53464,10249],{"class":689},[679,53466,5283],{"class":880},[679,53468,686],{"class":693},[679,53470,48118],{"class":689},[679,53472,11995],{"class":880},[679,53474,686],{"class":693},[679,53476,48125],{"class":689},[679,53478,5387],{"class":693},[679,53480,53481,53483,53485,53487,53489,53491,53493,53495,53497,53499,53501,53503],{"class":681,"line":964},[679,53482,4524],{"class":693},[679,53484,11968],{"class":4508},[679,53486,53265],{"class":880},[679,53488,686],{"class":693},[679,53490,10249],{"class":689},[679,53492,5283],{"class":880},[679,53494,686],{"class":693},[679,53496,52719],{"class":689},[679,53498,11995],{"class":880},[679,53500,686],{"class":693},[679,53502,53450],{"class":689},[679,53504,5387],{"class":693},[679,53506,53507,53509,53511,53513,53515,53517,53519,53521,53523,53525,53527,53530],{"class":681,"line":977},[679,53508,4524],{"class":693},[679,53510,11968],{"class":4508},[679,53512,53265],{"class":880},[679,53514,686],{"class":693},[679,53516,10249],{"class":689},[679,53518,5283],{"class":880},[679,53520,686],{"class":693},[679,53522,48244],{"class":689},[679,53524,11995],{"class":880},[679,53526,686],{"class":693},[679,53528,53529],{"class":689},"\"How to add Twitter Card Meta Tags to your Blog\"",[679,53531,5387],{"class":693},[679,53533,53534,53536,53538,53540,53542,53544,53546,53548,53550,53552,53554,53556],{"class":681,"line":982},[679,53535,4524],{"class":693},[679,53537,11968],{"class":4508},[679,53539,53265],{"class":880},[679,53541,686],{"class":693},[679,53543,10249],{"class":689},[679,53545,5283],{"class":880},[679,53547,686],{"class":693},[679,53549,48266],{"class":689},[679,53551,11995],{"class":880},[679,53553,686],{"class":693},[679,53555,48273],{"class":689},[679,53557,5387],{"class":693},[679,53559,53560,53562,53564,53566,53568,53570,53572,53574,53576,53578,53580,53583],{"class":681,"line":988},[679,53561,4524],{"class":693},[679,53563,11968],{"class":4508},[679,53565,53265],{"class":880},[679,53567,686],{"class":693},[679,53569,10249],{"class":689},[679,53571,5283],{"class":880},[679,53573,686],{"class":693},[679,53575,52781],{"class":689},[679,53577,11995],{"class":880},[679,53579,686],{"class":693},[679,53581,53582],{"class":689},"\"https://www.danvega.dev/assets/static/twitter-cards.bd64798.89214ec.png\"",[679,53584,5387],{"class":693},[679,53586,53587,53589,53591,53593,53595,53597,53599,53601,53603,53605,53607,53609],{"class":681,"line":993},[679,53588,4524],{"class":693},[679,53590,11968],{"class":4508},[679,53592,53265],{"class":880},[679,53594,686],{"class":693},[679,53596,10249],{"class":689},[679,53598,5283],{"class":880},[679,53600,686],{"class":693},[679,53602,48316],{"class":689},[679,53604,11995],{"class":880},[679,53606,686],{"class":693},[679,53608,48273],{"class":689},[679,53610,5387],{"class":693},[679,53612,53613,53615,53617,53619,53621,53623,53625,53627,53629,53632,53634,53636,53638,53640],{"class":681,"line":2129},[679,53614,4524],{"class":693},[679,53616,47668],{"class":4508},[679,53618,53265],{"class":880},[679,53620,686],{"class":693},[679,53622,10249],{"class":689},[679,53624,5361],{"class":880},[679,53626,686],{"class":693},[679,53628,53218],{"class":689},[679,53630,53631],{"class":880}," async",[679,53633,686],{"class":693},[679,53635,10249],{"class":689},[679,53637,4563],{"class":693},[679,53639,47668],{"class":4508},[679,53641,4519],{"class":693},[679,53643,53644,53646,53648],{"class":681,"line":2140},[679,53645,11840],{"class":693},[679,53647,11741],{"class":4508},[679,53649,4519],{"class":693},[651,53651,53652],{},"And this is an example of what a card might look like",[651,53654,53655],{},[660,53656],{"alt":674,"src":53657},"/images/blog/2019/04/09/2019-04-09_14-22-55-7de0bfb4-6ad0-4bd0-bf31-d2e9092d37ca.png",[4542,53659,9042],{"id":9041},[651,53661,53662,53663,664],{},"Twitter cards add a really nice touch to sharing your articles on Twitter. Tweets with an image will get more impressions and clicks than tweets without one so what are you waiting for? If you're interested in seeing the code for my entire Gridsome blog you can ",[812,53664,17991],{"href":53665,"rel":53666},"https://github.com/danvega/danvega-dev",[816],[786,53668,53669],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}",{"title":674,"searchDepth":790,"depth":790,"links":53671},[53672,53673,53674,53675],{"id":52294,"depth":790,"text":52295},{"id":52663,"depth":790,"text":52664},{"id":53012,"depth":790,"text":52664},{"id":9041,"depth":790,"text":9042},"In this tutorial I will show you how to add Twitter Cards to your Gridsome Blog.",{"slug":53678,"date":53679,"published":797,"author":798,"tags":53680,"cover":53681},"adding-twitter-cards-to-gridsome","2019-04-09T19:21:53.368Z",[44169,43847],"./twittter-cards-gridsome-cover-936ee6d4-220e-4800-873c-9d700fa44469.png",{"title":516,"description":53676},"blog/2019/04/09/adding-twitter-cards-to-gridsome","bb1y8dMKyD8fvUU4X9JVJbFuwKzkAOvSrKukKtY5Ohk",{"id":53686,"title":513,"body":53687,"description":53912,"extension":793,"meta":53913,"navigation":797,"path":514,"seo":53917,"stem":53918,"__hash__":53919},"content/blog/2019/04/11/dont-call-it-a-comeback.md",{"type":648,"value":53688,"toc":53897},[53689,53692,53694,53697,53700,53703,53711,53719,53724,53727,53730,53733,53737,53740,53743,53746,53749,53752,53756,53759,53762,53773,53776,53779,53782,53785,53788,53797,53801,53804,53815,53818,53821,53824,53827,53834,53839,53842,53845,53848,53851,53854,53857,53860,53864,53876,53881,53883,53890,53893],[651,53690,53691],{},"I'm not sure if you have noticed or not but the personal blog is making a comeback. There aren't just more people blogging these days, there are more people building their own blogs which are really great to see. In this article, I am to give you some background on why I started blogging which I believe are good reasons for you to start blogging if you haven't already.",[4542,53693,19021],{"id":19020},[651,53695,53696],{},"I have been blogging since October of 2005 and have written somewhere near 1,000 blog posts. If that sounds like a lot I can assure you its weird for me to say out loud because I can't believe it. I can, however, remember clearly when and why I started blogging.",[651,53698,53699],{},"In 2005 I was a ColdFusion developer and I wanted some practice building real applications. Like most junior developers I knew the only way I was going to get better was by doing. You have to remember that at this time we didn't have Github or the vast amount of resources that we have today.",[651,53701,53702],{},"What we did have, especially in the ColdFusion community is a number of people who loved to blog and share their experiences. I looked up to a lot of these developers and wanted to follow in their footsteps. I thought this was a great opportunity to set up my own blog.",[651,53704,53705,53706,53710],{},"I found an open source blogging platform called ",[812,53707,19028],{"href":53708,"rel":53709},"http://www.blogcfc.com/",[816]," that was written in ColdFusion. Before I could even start blogging I learned how to set up and work with an open source project which was a great experience. I got a chance to see how someone outside of me or the company I worked for at the time was creating and distributing software.",[651,53712,53713,53714,664],{},"I really loved the idea of theming the blog and giving it a unique look to match my personality. Now, I have never been known for my design skills but It is still something I enjoy doing to this day. Here is a screenshot of an early version of my blog using the ",[812,53715,53718],{"href":53716,"rel":53717},"https://archive.org/web/",[816],"Wayback Machine",[651,53720,53721],{},[660,53722],{"alt":674,"src":53723},"/images/blog/2019/04/11/2019-04-09_21-55-34-783a547c-404d-4337-9402-6648cae05340.png",[651,53725,53726],{},"Now that I had my new blog up and running I can remember a lot of my early posts were talking about things that I learned while setting up that blog. My motivation for blogging always came down to 2 simple objectives.",[651,53728,53729],{},"First I wanted to help others that were trying to do the same thing I was doing but allow them to avoid all of the mistakes I made. If I could help someone avoid the discomfort of yelling at the monitor and banging their heads against the desk (I speak from experience) then I considered it a success.",[651,53731,53732],{},"Second, I wanted a reference for a problem that I just solved that I could come back to later. I have to tell you one of the coolest things for me is googling something that you're stuck on only to find your own blog post that solves your issue. This has a great way of reminding you why you do what you do.",[4542,53734,53736],{"id":53735},"why-developers-stopped-building-personal-blogs","Why developers stopped building personal blogs",[651,53738,53739],{},"Now that you have some background on me I can talk a little bit on why I think developers stopped creating their own blogs.",[5909,53741,51474],{"id":53742},"twitter",[651,53744,53745],{},"I am really starting to date myself in this article but when I started blogging Twitter wasn't even a thing yet. In fact I didn't even join Twitter until May of 2008 and I can't even remember when I began seriously using it a platform to curate information.",[651,53747,53748],{},"The reason I bring up Twitter is that a lot of bloggers used their blog to share information & links with others. If I had a cool tool, product or link to a website I would just blog about it. Now, unless I have more to say about this cool thing I just found I will just share it on Twitter.",[651,53750,53751],{},"Now this isn't a bad thing because I am big fan of Twitter but I really enjoy a developers insight and experience into using this tool that I can come back to later.",[5909,53753,53755],{"id":53754},"wordpress","WordPress",[651,53757,53758],{},"WordPress is an amazing CMS + Blogging platform that is used by something like 33% of the entire internet. That number is staggering but not shocking if you have ever used it. When I moved away from ColdFusion there were a lot of features that I was looking for and WordPress seem to have them all, so I switched.",[651,53760,53761],{},"I have been using it for awhile now and have really enjoyed it for the most part. The 3 main issues I have had with it are:",[5316,53763,53764,53767,53770],{},[5332,53765,53766],{},"Hosting: WordPress is a resource hog and hosting runs me $30 month",[5332,53768,53769],{},"Plugins: Installing lots of plugins can cause performance issue",[5332,53771,53772],{},"PHP: I am not a PHP developer nor do I want to be.",[651,53774,53775],{},"I think the last one is really the big issue for me. I know enough to get in there and hack some things together but whenever I want to update themes it just seemed overwhelming for me. I think this could be the case for a lot of developers. Maybe they moved to WordPress, bought some theme and just used it as a blogging platform.",[651,53777,53778],{},"I am not saying there is anything wrong with this at all but it could be something that led to developers like myself from building a personal blog. Again when I say building I just mean creating something from scratch and giving it some style to match their personality.",[5909,53780,25676],{"id":53781},"medium",[651,53783,53784],{},"In 2012 Medium launched and it offered a platform for writers to start writing without having to setup a blog. A few years ago I started writing on there and at first I really liked it but I always came back to the fact that I didn't own my content. I also didn't want to have to migrate my 700 posts at the time so it quickly became a place where I would syndicate posts from my own blog.",[651,53786,53787],{},"Fast forward to today and I see a lot of developers that did move to Medium, moving off of it. Medium took a bunch of funding so you had to know at some point that they needed to start making money. If you're reading a post on Medium these days you might be all of a sudden hit with a paywall. I also don't think its a great writing experience for developers.",[651,53789,53790],{},[7300,53791,53792,53793,664],{},"If you're looking for a solution like Medium but for developers, I would suggest taking a look at ",[812,53794,53795],{"href":53795,"rel":53796},"http://www.dev.to",[816],[4542,53798,53800],{"id":53799},"dont-call-it-a-comeback","Don't call it a comeback",[651,53802,53803],{},"I gave some reasons why I think there was a period where developers weren't building their own blogs. I'm here to tell you that times, they are a changing! Here are a few things that I think have contributed to the comeback.",[5316,53805,53806,53809,53812],{},[5332,53807,53808],{},"JAMStack (Static Site Generators)",[5332,53810,53811],{},"Evolution of JavaScript Frameworks",[5332,53813,53814],{},"JavaScript Rocks 🤘🏻",[5909,53816,53808],{"id":53817},"jamstack-static-site-generators",[651,53819,53820],{},"I think that static site generators are a huge reason for the comeback. I know for me personally as great as a platform like WordPress was it was just too much for what I wanted to do. My visitors are coming to my site to read my content and view information on the courses that I have available. They don't visit my site for long load times and the occasional random WordPress is out of resources error.",[651,53822,53823],{},"Static site generators allow us to build our entire site so that when our visitors get the content they are looking at HTML/CSS/JS and it is blazing fast. This has always been the appeal of static sites but until recently they have been pretty limited in what they can do. With advancements in JavaScript frameworks, tooling and cloud computing we now have a pretty awesome workflow.",[651,53825,53826],{},"I write a lot of documentation, tutorials, and exercises in Markdown so starting with a simple markdown file is very appealing to me. From there I am able to just write a new blog post and when I am ready to commit the file to my git repository and push it. My hosting provider builds my static site, deploys it and its live for everyone to see.",[651,53828,53829,53830,53833],{},"I have mentioned Gridsome a few times in this article and it's the one that I selected because it uses Vue. There are plenty of great options out there including a very popular one called ",[812,53831,43821],{"href":43819,"rel":53832},[816],". I would suggest looking at your options, trying out a couple that fit your stack and go from there.",[651,53835,53836],{},[7300,53837,53838],{},"P.S If you're interested in hearing more on JAMStack tweet at me and tell me to write that article! I love everything about it and have a lot to say on the subject.",[5909,53840,53811],{"id":53841},"evolution-of-javascript-frameworks",[651,53843,53844],{},"We wouldn't have these amazing static site generators without the evolution of JavaScript frameworks. In the past 5 years we have seen frameworks like Angular, React & Vue rise to the top. These frameworks along with the JavaScript language itself have really made front end development fun again.",[651,53846,53847],{},"No matter what your project requirements are chances are any of the big 3 frameworks have an answer for you. Developers love to get caught up in framework wars but if you just like to build stuff you can't go wrong with any of them.",[651,53849,53850],{},"I always tell people that they shouldn't listen to anyone when it comes to \"which framework should I choose\". Try building some components or applications using each of them and decide for yourself.",[5909,53852,53814],{"id":53853},"javascript-rocks",[651,53855,53856],{},"Behind every great framework is a great language. JavaScript has taken a lot of flak over the years (rightfully so) but it has stood the test of time as \"the language of the web\". I had to learn JavaScript before jQuery was cool and I can't tell you how good you have it today.",[651,53858,53859],{},"In the last few years, JavaScript has really come into its own and I think ES6 brought a lot of features to the language that got developers excited. There is just something very clean and elegant about some well-written JavaScript and I can't wait to see what the future of the language brings.",[4542,53861,53863],{"id":53862},"danvegadev","danvega.dev",[651,53865,53866,53867,53871,53872,664],{},"If you're not reading this article on my blog and want to check it out head over to ",[812,53868,53869],{"href":53869,"rel":53870},"https://www.danvega.dev",[816],". I also have a few articles on why I moved over to Gridsome and how I built certain features. If you're interested my entire site is open source and you can ",[812,53873,53875],{"href":53665,"rel":53874},[816],"grab the code here",[651,53877,53878],{},[660,53879],{"alt":674,"src":53880},"/images/blog/2019/04/11/2019-04-10_11-55-41-4bdf4a5b-10e8-476d-a340-26267e9fed0e.png",[4542,53882,9042],{"id":9041},[651,53884,53885,53886,53889],{},"I had so much fun building my new personal website using ",[812,53887,43848],{"href":43854,"rel":53888},[816],". It really took me back to what I love about being a software developer. I like to build things and tell people how I did it so that others can learn and avoid making the same mistakes that I did. If you're new to this blog that is what you can expect from so I hope you will visit again.",[651,53891,53892],{},"If you moved your blog or created a new on top of a new framework or static site generator please let me know. I would love to check it out and I will retweet some of my favorites.",[651,53894,41105,53895,41109],{},[41107,53896],{},{"title":674,"searchDepth":790,"depth":790,"links":53898},[53899,53900,53905,53910,53911],{"id":19020,"depth":790,"text":19021},{"id":53735,"depth":790,"text":53736,"children":53901},[53902,53903,53904],{"id":53742,"depth":892,"text":51474},{"id":53754,"depth":892,"text":53755},{"id":53781,"depth":892,"text":25676},{"id":53799,"depth":790,"text":53800,"children":53906},[53907,53908,53909],{"id":53817,"depth":892,"text":53808},{"id":53841,"depth":892,"text":53811},{"id":53853,"depth":892,"text":53814},{"id":53862,"depth":790,"text":53863},{"id":9041,"depth":790,"text":9042},"I'm not sure if you have noticed or not but the personal blog is making a comeback",{"slug":53799,"date":53914,"published":797,"author":798,"tags":53915,"cover":53916},"2019-04-11T11:38:39.435Z",[19164],"./personal-blog-4b0867c8-0c1b-421c-afaf-7fc7e7986a77.png",{"title":513,"description":53912},"blog/2019/04/11/dont-call-it-a-comeback","WbjQc67lXXoorXoRoIvSYmW07YUvs68umsur5mgsS8I",{"id":53921,"title":510,"body":53922,"description":54663,"extension":793,"meta":54664,"navigation":797,"path":511,"seo":54669,"stem":54670,"__hash__":54671},"content/blog/2019/04/18/vue-tips-avoid-direct-dom-manipulation.md",{"type":648,"value":53923,"toc":54657},[53924,53930,53965,53968,53992,53995,53998,54075,54079,54082,54117,54124,54157,54160,54233,54237,54240,54247,54274,54324,54328,54334,54337,54397,54400,54479,54482,54635,54644,54649,54651,54654],[651,53925,53926,53927],{},"I was working with a student today on a final capstone project and they were having some issues with data binding. When we started digging through the code one thing stuck out to me as a big red flag. There was a checkbox with an id and then a change event handler ",[676,53928,53929],{},"toggleStatus()",[669,53931,53933],{"className":4496,"code":53932,"language":4498,"meta":674,"style":674},"\u003Cinput type=\"checkbox\" id=\"thecheckbox\" @change=\"toggleStatus\" />\n",[676,53934,53935],{"__ignoreMap":674},[679,53936,53937,53939,53941,53943,53945,53948,53950,53952,53955,53958,53960,53963],{"class":681,"line":682},[679,53938,4505],{"class":693},[679,53940,27722],{"class":4508},[679,53942,27725],{"class":880},[679,53944,686],{"class":693},[679,53946,53947],{"class":689},"\"checkbox\"",[679,53949,5578],{"class":880},[679,53951,686],{"class":693},[679,53953,53954],{"class":689},"\"thecheckbox\"",[679,53956,53957],{"class":880}," @change",[679,53959,686],{"class":693},[679,53961,53962],{"class":689},"\"toggleStatus\"",[679,53964,5387],{"class":693},[651,53966,53967],{},"Before we could even get to the root of the issue I saw some code that looked like this.",[669,53969,53971],{"className":25132,"code":53970,"language":25134,"meta":674,"style":674},"const isChecked = document.getElementById(\"thecheckbox\");\n",[676,53972,53973],{"__ignoreMap":674},[679,53974,53975,53977,53980,53982,53984,53986,53988,53990],{"class":681,"line":682},[679,53976,45172],{"class":685},[679,53978,53979],{"class":931}," isChecked",[679,53981,6883],{"class":685},[679,53983,46823],{"class":693},[679,53985,46913],{"class":880},[679,53987,745],{"class":693},[679,53989,53954],{"class":689},[679,53991,1208],{"class":693},[651,53993,53994],{},"When you're working with Vue you kind of have to forget that you have access to the DOM. The reason I say this is because if you're directly accessing the DOM there is probably a better way to do this. I want to be clear that you're not violating any rules here and nobody is going to yell at you for it but we should think about this problem in another way.",[651,53996,53997],{},"In our toggle status method imagine that you needed to know if that checkbox was checked to determine what action to take. This is a very common process in any application.",[669,53999,54001],{"className":25132,"code":54000,"language":25134,"meta":674,"style":674},"methods: {\n toggleStatus() {\n const isChecked = document.getElementById('thecheckbox').checked;\n if( isChecked ) {\n // do something\n } else {\n // do something else\n }\n }\n}\n",[676,54002,54003,54010,54017,54038,54045,54050,54058,54063,54067,54071],{"__ignoreMap":674},[679,54004,54005,54008],{"class":681,"line":682},[679,54006,54007],{"class":880},"methods",[679,54009,28468],{"class":693},[679,54011,54012,54015],{"class":681,"line":790},[679,54013,54014],{"class":880}," toggleStatus",[679,54016,2667],{"class":693},[679,54018,54019,54022,54024,54026,54028,54030,54032,54035],{"class":681,"line":892},[679,54020,54021],{"class":685}," const",[679,54023,53979],{"class":931},[679,54025,6883],{"class":685},[679,54027,46823],{"class":693},[679,54029,46913],{"class":880},[679,54031,745],{"class":693},[679,54033,54034],{"class":689},"'thecheckbox'",[679,54036,54037],{"class":693},").checked;\n",[679,54039,54040,54042],{"class":681,"line":901},[679,54041,1231],{"class":685},[679,54043,54044],{"class":693},"( isChecked ) {\n",[679,54046,54047],{"class":681,"line":909},[679,54048,54049],{"class":1400}," // do something\n",[679,54051,54052,54054,54056],{"class":681,"line":918},[679,54053,47298],{"class":693},[679,54055,2256],{"class":685},[679,54057,884],{"class":693},[679,54059,54060],{"class":681,"line":935},[679,54061,54062],{"class":1400}," // do something else\n",[679,54064,54065],{"class":681,"line":944},[679,54066,985],{"class":693},[679,54068,54069],{"class":681,"line":959},[679,54070,21405],{"class":693},[679,54072,54073],{"class":681,"line":964},[679,54074,996],{"class":693},[5909,54076,54078],{"id":54077},"a-better-approach","A Better Approach",[651,54080,54081],{},"A better approach for this is to understand that each instance of this component has its own state. This means that we can bind controls in the component to our data. I am going to refactor our previous example by first creating a variable called isChecked and default it to false. This is because by default I want this checkbox unchecked.",[669,54083,54085],{"className":25132,"code":54084,"language":25134,"meta":674,"style":674},"data() {\n return {\n isChecked = false\n }\n}\n",[676,54086,54087,54094,54100,54109,54113],{"__ignoreMap":674},[679,54088,54089,54092],{"class":681,"line":682},[679,54090,54091],{"class":880},"data",[679,54093,2667],{"class":693},[679,54095,54096,54098],{"class":681,"line":790},[679,54097,44767],{"class":685},[679,54099,884],{"class":693},[679,54101,54102,54105,54107],{"class":681,"line":892},[679,54103,54104],{"class":693}," isChecked ",[679,54106,686],{"class":685},[679,54108,5076],{"class":931},[679,54110,54111],{"class":681,"line":901},[679,54112,21405],{"class":693},[679,54114,54115],{"class":681,"line":909},[679,54116,996],{"class":693},[651,54118,54119,54120,54123],{},"Now that we have some default state for the checkbox we can use this in our component. You can bind the checkbox checked attribute to the variable ",[676,54121,54122],{},"isChecked",". When the component first loads it will be unchecked but is now bound to our variable so any time that it changes our checkbox will be updated.",[669,54125,54127],{"className":4496,"code":54126,"language":4498,"meta":674,"style":674},"\u003Cinput type=\"checkbox\" :checked=\"isChecked\" @change=\"toggleStatus\" />\n",[676,54128,54129],{"__ignoreMap":674},[679,54130,54131,54133,54135,54137,54139,54141,54144,54146,54149,54151,54153,54155],{"class":681,"line":682},[679,54132,4505],{"class":693},[679,54134,27722],{"class":4508},[679,54136,27725],{"class":880},[679,54138,686],{"class":693},[679,54140,53947],{"class":689},[679,54142,54143],{"class":880}," :checked",[679,54145,686],{"class":693},[679,54147,54148],{"class":689},"\"isChecked\"",[679,54150,53957],{"class":880},[679,54152,686],{"class":693},[679,54154,53962],{"class":689},[679,54156,5387],{"class":693},[651,54158,54159],{},"Now in our toggle status method we can just use the components data in our if expression. We can also flip the status of the checkbox by setting it to the opposite of whatever it is currently.",[669,54161,54163],{"className":25132,"code":54162,"language":25134,"meta":674,"style":674},"methods: {\n toggleStatus() {\n if(this.isChecked) {\n // do sommething\n } else {\n // do something else\n }\n this.isChecked = !this.isChecked;\n }\n}\n",[676,54164,54165,54171,54177,54188,54193,54201,54205,54209,54225,54229],{"__ignoreMap":674},[679,54166,54167,54169],{"class":681,"line":682},[679,54168,54007],{"class":880},[679,54170,28468],{"class":693},[679,54172,54173,54175],{"class":681,"line":790},[679,54174,54014],{"class":880},[679,54176,2667],{"class":693},[679,54178,54179,54181,54183,54185],{"class":681,"line":892},[679,54180,1231],{"class":685},[679,54182,745],{"class":693},[679,54184,4732],{"class":931},[679,54186,54187],{"class":693},".isChecked) {\n",[679,54189,54190],{"class":681,"line":901},[679,54191,54192],{"class":1400}," // do sommething\n",[679,54194,54195,54197,54199],{"class":681,"line":909},[679,54196,47298],{"class":693},[679,54198,2256],{"class":685},[679,54200,884],{"class":693},[679,54202,54203],{"class":681,"line":918},[679,54204,54062],{"class":1400},[679,54206,54207],{"class":681,"line":935},[679,54208,985],{"class":693},[679,54210,54211,54213,54216,54218,54220,54222],{"class":681,"line":944},[679,54212,27825],{"class":931},[679,54214,54215],{"class":693},".isChecked ",[679,54217,686],{"class":685},[679,54219,14618],{"class":685},[679,54221,4732],{"class":931},[679,54223,54224],{"class":693},".isChecked;\n",[679,54226,54227],{"class":681,"line":959},[679,54228,21405],{"class":693},[679,54230,54231],{"class":681,"line":964},[679,54232,996],{"class":693},[4542,54234,54236],{"id":54235},"accessing-the-dom-using-refs","Accessing the DOM using $refs",[651,54238,54239],{},"What if you absolutely need access to the DOM? There are instances where you might need a reference to an element to perform some type of manipulation. I have seen this come up while working with 3rd party components and when working with parent/child components.",[651,54241,54242,54243,54246],{},"I am going to start with a very simple example but this case where you shouldn't use $refs. Say you had a button in your component and you wanted to get access to it so you could change the text. You can assign a ref attribute to the button and later get access to it using the ",[676,54244,54245],{},"$refs"," object.",[669,54248,54250],{"className":4496,"code":54249,"language":4498,"meta":674,"style":674},"\u003Cbutton ref=\"myButton\">My Button\u003C/button>\n",[676,54251,54252],{"__ignoreMap":674},[679,54253,54254,54256,54259,54262,54264,54267,54270,54272],{"class":681,"line":682},[679,54255,4505],{"class":693},[679,54257,54258],{"class":4508},"button",[679,54260,54261],{"class":880}," ref",[679,54263,686],{"class":693},[679,54265,54266],{"class":689},"\"myButton\"",[679,54268,54269],{"class":693},">My Button\u003C/",[679,54271,54258],{"class":4508},[679,54273,4519],{"class":693},[669,54275,54277],{"className":25132,"code":54276,"language":25134,"meta":674,"style":674},"methods: {\n onButtonClick() {\n const btn = this.$refs.myButton;\n btn.innerText = 'New Button Text'\n }\n}\n",[676,54278,54279,54285,54292,54306,54316,54320],{"__ignoreMap":674},[679,54280,54281,54283],{"class":681,"line":682},[679,54282,54007],{"class":880},[679,54284,28468],{"class":693},[679,54286,54287,54290],{"class":681,"line":790},[679,54288,54289],{"class":880}," onButtonClick",[679,54291,2667],{"class":693},[679,54293,54294,54296,54299,54301,54303],{"class":681,"line":892},[679,54295,54021],{"class":685},[679,54297,54298],{"class":931}," btn",[679,54300,6883],{"class":685},[679,54302,21353],{"class":931},[679,54304,54305],{"class":693},".$refs.myButton;\n",[679,54307,54308,54311,54313],{"class":681,"line":901},[679,54309,54310],{"class":693}," btn.innerText ",[679,54312,686],{"class":685},[679,54314,54315],{"class":689}," 'New Button Text'\n",[679,54317,54318],{"class":681,"line":909},[679,54319,21405],{"class":693},[679,54321,54322],{"class":681,"line":918},[679,54323,996],{"class":693},[4542,54325,54327],{"id":54326},"practical-example-using-refs-in-vue","Practical example using $refs in Vue",[651,54329,54330,54331,664],{},"Again, this isn't the most practical example because we are just doing the same thing we did in our earlier example. As I said earlier you might come across this issue when you're working with parent/child components. For this example let's say that you have a checkout form and in that component, you have a child component called ",[676,54332,54333],{},"CustomerForm.vue",[651,54335,54336],{},"In our checkout form component, we want to programmatically set the focus of an input in our customer form component. The way we can do this is to assign a ref to our customer form within our checkout form.",[669,54338,54340],{"className":4496,"code":54339,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"checkout\">\n \u003Ccustomer-form ref=\"customer\" />\n \u003C/div>\n\u003C/template>\n",[676,54341,54342,54350,54365,54381,54389],{"__ignoreMap":674},[679,54343,54344,54346,54348],{"class":681,"line":682},[679,54345,4505],{"class":693},[679,54347,45982],{"class":4508},[679,54349,4519],{"class":693},[679,54351,54352,54354,54356,54358,54360,54363],{"class":681,"line":790},[679,54353,11738],{"class":693},[679,54355,4509],{"class":4508},[679,54357,5578],{"class":880},[679,54359,686],{"class":693},[679,54361,54362],{"class":689},"\"checkout\"",[679,54364,4519],{"class":693},[679,54366,54367,54369,54372,54374,54376,54379],{"class":681,"line":892},[679,54368,4524],{"class":693},[679,54370,54371],{"class":4508},"customer-form",[679,54373,54261],{"class":880},[679,54375,686],{"class":693},[679,54377,54378],{"class":689},"\"customer\"",[679,54380,5387],{"class":693},[679,54382,54383,54385,54387],{"class":681,"line":901},[679,54384,11840],{"class":693},[679,54386,4509],{"class":4508},[679,54388,4519],{"class":693},[679,54390,54391,54393,54395],{"class":681,"line":909},[679,54392,4586],{"class":693},[679,54394,45982],{"class":4508},[679,54396,4519],{"class":693},[651,54398,54399],{},"In the customer form, we will then assign a ref to the first name input box.",[669,54401,54403],{"className":4496,"code":54402,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"customer-form\">\n First Name:\n \u003Cinput type=\"text\" placeholder=\"Enter your first name\" ref=\"firstName\" />\n \u003C/div>\n\u003C/template>\n",[676,54404,54405,54413,54428,54433,54463,54471],{"__ignoreMap":674},[679,54406,54407,54409,54411],{"class":681,"line":682},[679,54408,4505],{"class":693},[679,54410,45982],{"class":4508},[679,54412,4519],{"class":693},[679,54414,54415,54417,54419,54421,54423,54426],{"class":681,"line":790},[679,54416,11738],{"class":693},[679,54418,4509],{"class":4508},[679,54420,5578],{"class":880},[679,54422,686],{"class":693},[679,54424,54425],{"class":689},"\"customer-form\"",[679,54427,4519],{"class":693},[679,54429,54430],{"class":681,"line":892},[679,54431,54432],{"class":693}," First Name:\n",[679,54434,54435,54437,54439,54441,54443,54446,54449,54451,54454,54456,54458,54461],{"class":681,"line":901},[679,54436,4524],{"class":693},[679,54438,27722],{"class":4508},[679,54440,27725],{"class":880},[679,54442,686],{"class":693},[679,54444,54445],{"class":689},"\"text\"",[679,54447,54448],{"class":880}," placeholder",[679,54450,686],{"class":693},[679,54452,54453],{"class":689},"\"Enter your first name\"",[679,54455,54261],{"class":880},[679,54457,686],{"class":693},[679,54459,54460],{"class":689},"\"firstName\"",[679,54462,5387],{"class":693},[679,54464,54465,54467,54469],{"class":681,"line":909},[679,54466,11840],{"class":693},[679,54468,4509],{"class":4508},[679,54470,4519],{"class":693},[679,54472,54473,54475,54477],{"class":681,"line":918},[679,54474,4586],{"class":693},[679,54476,45982],{"class":4508},[679,54478,4519],{"class":693},[651,54480,54481],{},"Now in the checkout form in our mounted method, we can focus on the customer form first name input.",[669,54483,54485],{"className":50297,"code":54484,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"checkout\">\n \u003Ccustomer-form ref=\"customer\" />\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nimport CustomerForm from \"@/components/CustomerForm\";\n\nexport default {\n name: \"checkout-form\",\n components: {\n CustomerForm\n },\n mounted() {\n this.$refs.customer.$refs.firstName.focus();\n }\n};\n\u003C/script>\n",[676,54486,54487,54495,54509,54523,54531,54539,54543,54551,54565,54569,54577,54586,54591,54596,54600,54607,54619,54623,54627],{"__ignoreMap":674},[679,54488,54489,54491,54493],{"class":681,"line":682},[679,54490,4505],{"class":693},[679,54492,45982],{"class":4508},[679,54494,4519],{"class":693},[679,54496,54497,54499,54501,54503,54505,54507],{"class":681,"line":790},[679,54498,11738],{"class":693},[679,54500,4509],{"class":4508},[679,54502,5578],{"class":880},[679,54504,686],{"class":693},[679,54506,54362],{"class":689},[679,54508,4519],{"class":693},[679,54510,54511,54513,54515,54517,54519,54521],{"class":681,"line":892},[679,54512,4524],{"class":693},[679,54514,54371],{"class":4508},[679,54516,54261],{"class":880},[679,54518,686],{"class":693},[679,54520,54378],{"class":689},[679,54522,5387],{"class":693},[679,54524,54525,54527,54529],{"class":681,"line":901},[679,54526,11840],{"class":693},[679,54528,4509],{"class":4508},[679,54530,4519],{"class":693},[679,54532,54533,54535,54537],{"class":681,"line":909},[679,54534,4586],{"class":693},[679,54536,45982],{"class":4508},[679,54538,4519],{"class":693},[679,54540,54541],{"class":681,"line":918},[679,54542,889],{"emptyLinePlaceholder":797},[679,54544,54545,54547,54549],{"class":681,"line":935},[679,54546,4505],{"class":693},[679,54548,47668],{"class":4508},[679,54550,4519],{"class":693},[679,54552,54553,54555,54558,54560,54563],{"class":681,"line":944},[679,54554,1999],{"class":685},[679,54556,54557],{"class":693}," CustomerForm ",[679,54559,28887],{"class":685},[679,54561,54562],{"class":689}," \"@/components/CustomerForm\"",[679,54564,1186],{"class":693},[679,54566,54567],{"class":681,"line":959},[679,54568,889],{"emptyLinePlaceholder":797},[679,54570,54571,54573,54575],{"class":681,"line":964},[679,54572,29245],{"class":685},[679,54574,50460],{"class":685},[679,54576,884],{"class":693},[679,54578,54579,54581,54584],{"class":681,"line":977},[679,54580,49970],{"class":693},[679,54582,54583],{"class":689},"\"checkout-form\"",[679,54585,12083],{"class":693},[679,54587,54588],{"class":681,"line":982},[679,54589,54590],{"class":693}," components: {\n",[679,54592,54593],{"class":681,"line":988},[679,54594,54595],{"class":693}," CustomerForm\n",[679,54597,54598],{"class":681,"line":993},[679,54599,28483],{"class":693},[679,54601,54602,54605],{"class":681,"line":2129},[679,54603,54604],{"class":880}," mounted",[679,54606,2667],{"class":693},[679,54608,54609,54611,54614,54617],{"class":681,"line":2140},[679,54610,27825],{"class":931},[679,54612,54613],{"class":693},".$refs.customer.$refs.firstName.",[679,54615,54616],{"class":880},"focus",[679,54618,9317],{"class":693},[679,54620,54621],{"class":681,"line":2145},[679,54622,21405],{"class":693},[679,54624,54625],{"class":681,"line":2154},[679,54626,44055],{"class":693},[679,54628,54629,54631,54633],{"class":681,"line":2159},[679,54630,4586],{"class":693},[679,54632,47668],{"class":4508},[679,54634,4519],{"class":693},[651,54636,54637,54638,54643],{},"Just a little note from the ",[812,54639,54642],{"href":54640,"rel":54641},"https://vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements",[816],"Vue Documentation"," that you need to be aware of.",[1004,54645,54646],{},[651,54647,54648],{},"$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.",[4542,54650,9042],{"id":9041},[651,54652,54653],{},"I want to stress that you aren't doing anything wrong by accessing the DOM in your Vue application. I just think that in most cases there is a better approach and I hope this example was able to show that. If you know of a good use case for manipulating the DOM directly please reach out to me and let me know.",[786,54655,54656],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":54658},[54659,54660,54661,54662],{"id":54077,"depth":892,"text":54078},{"id":54235,"depth":790,"text":54236},{"id":54326,"depth":790,"text":54327},{"id":9041,"depth":790,"text":9042},"In this article I will look at an example of where manipulating the DOM in our components might not be the best approach.",{"slug":54665,"date":54666,"published":797,"author":798,"tags":54667,"cover":54668},"vue-tips-avoid-direct-dom-manipulation","2019-04-18T20:24:32.502Z",[44169,25134],"./vue-dom.png",{"title":510,"description":54663},"blog/2019/04/18/vue-tips-avoid-direct-dom-manipulation","xlzIWSllg-wkvUEq-3pOg8TkUXqB6xPlybHc_2S2Jhc",{"id":54673,"title":507,"body":54674,"description":55051,"extension":793,"meta":55052,"navigation":797,"path":508,"seo":55057,"stem":55058,"__hash__":55059},"content/blog/2019/04/19/npm-scripts-parallel.md",{"type":648,"value":54675,"toc":55042},[54676,54685,54699,54702,54710,54713,54717,54723,54726,54735,54751,54754,54767,54770,54785,54790,54793,54807,54810,54814,54822,54838,54841,54856,54860,54870,54873,54879,54927,54930,54940,54943,54949,54952,54956,54959,54968,55017,55023,55025,55036,55040],[651,54677,54678,54679,54684],{},"I was working on an exercise for ",[812,54680,54683],{"href":54681,"rel":54682},"https://www.techelevator.com",[816],"our students"," and In the process I learned something new. When our students finish their exercises they are asked to run some tests to validate that their solution works. We also use these tests as a way to grade certain exercises.",[651,54686,54687,54688,54693,54694,664],{},"The problem is that I needed to run some end to end tests using ",[812,54689,54692],{"href":54690,"rel":54691},"http://www.cypress.io",[816],"Cypress"," but before doing so I needed the project to be running. In some of our vanilla JavaScript examples you can run the them right from Visual Studio Code using the ",[812,54695,54698],{"href":54696,"rel":54697},"https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer",[816],"Live Server Extension",[651,54700,54701],{},"This works for development but about when it comes time to test? I suppose the instructions for the exercise could state \"make sure your project is running before running the tests\" but this introduces a couple of problems.",[651,54703,54704,54705,54709],{},"First, if we do it this way I'm not sure we can always assume that the project will be running at ",[812,54706,54707],{"href":54707,"rel":54708},"http://localhost:5500/my-project",[816],". That might be the case for most but I never like to just assume it. I could probably stick this in some configuration but again it doesn't feel right to me.",[651,54711,54712],{},"Second, what happens we want to grade our students exercises? This would mean that our instructors would have to start each project, run the test and record their score. This is a very a very tedious workflow and something we want to avoid. We would also want this automated in case we run everything through some continuous integration build.",[4542,54714,54716],{"id":54715},"creating-a-new-project","Creating a new project",[651,54718,54719,54720,664],{},"If you want to follow along you can create your own project but it isn't necessary. This article will assume you have some experience building modern web applications. To get started create a new folder and create a new package.json by running the command ",[676,54721,54722],{},"npm -y",[5909,54724,54692],{"id":54725},"cypress",[651,54727,54728,54729,54734],{},"We won't be getting into what Cypress is but if you haven't heard of it or had a chance to play around with it I highly suggest ",[812,54730,54733],{"href":54731,"rel":54732},"https://www.cypress.io/",[816],"checking it out",". You can install cypress using the following command:",[669,54736,54738],{"className":5851,"code":54737,"language":5853,"meta":674,"style":674},"npm install -D cypress\n",[676,54739,54740],{"__ignoreMap":674},[679,54741,54742,54744,54746,54748],{"class":681,"line":682},[679,54743,24568],{"class":880},[679,54745,24571],{"class":689},[679,54747,42645],{"class":931},[679,54749,54750],{"class":689}," cypress\n",[651,54752,54753],{},"Installing Cypress gives you access to some command line tools like the ability to run a headless version of the tests or to open them up in chrome.",[669,54755,54757],{"className":5851,"code":54756,"language":5853,"meta":674,"style":674},"./node_modules/.bin/cypress open\n",[676,54758,54759],{"__ignoreMap":674},[679,54760,54761,54764],{"class":681,"line":682},[679,54762,54763],{"class":880},"./node_modules/.bin/cypress",[679,54765,54766],{"class":689}," open\n",[651,54768,54769],{},"With that we can add a new test to our scripts section in our package.json",[669,54771,54773],{"className":5851,"code":54772,"language":5853,"meta":674,"style":674},"\"test:e2e\": \"./node_modules/.bin/cypress open\"\n",[676,54774,54775],{"__ignoreMap":674},[679,54776,54777,54780,54782],{"class":681,"line":682},[679,54778,54779],{"class":880},"\"test:e2e\"",[679,54781,2391],{"class":931},[679,54783,54784],{"class":689}," \"./node_modules/.bin/cypress open\"\n",[651,54786,54787],{},[2939,54788,54789],{},"Running Cypress Tests in VueJS",[651,54791,54792],{},"When working in a framework like VueJS the framework solves this problem for us. You can write end to end tests using Cypress and when you're ready to test you just run the command",[669,54794,54796],{"className":5851,"code":54795,"language":5853,"meta":674,"style":674},"npm run test:e2e\n",[676,54797,54798],{"__ignoreMap":674},[679,54799,54800,54802,54804],{"class":681,"line":682},[679,54801,24568],{"class":880},[679,54803,16486],{"class":689},[679,54805,54806],{"class":689}," test:e2e\n",[651,54808,54809],{},"This will start the application up and then run the cypress integration tests. When the tests are finished you get the test results and the application is shut down. This is the preferred workflow and something we will try and mimic.",[5909,54811,54813],{"id":54812},"http-server","Http Server",[651,54815,54816,54817,54821],{},"An easy solution to the problem of asking everyone to run the project manually is to install a small http server. You can do some searching and find one that fits your needs but for us ",[812,54818,54812],{"href":54819,"rel":54820},"https://www.npmjs.com/package/http-server",[816]," was tiny and fast, 2 qualities we were looking for. You can install this globally but for this demo we will just install it as a dev dependency by running the following command:",[669,54823,54825],{"className":5851,"code":54824,"language":5853,"meta":674,"style":674},"npm install -D http-server\n",[676,54826,54827],{"__ignoreMap":674},[679,54828,54829,54831,54833,54835],{"class":681,"line":682},[679,54830,24568],{"class":880},[679,54832,24571],{"class":689},[679,54834,42645],{"class":931},[679,54836,54837],{"class":689}," http-server\n",[651,54839,54840],{},"Now that you have it installed you can add a new script to start your http server.",[669,54842,54844],{"className":5851,"code":54843,"language":5853,"meta":674,"style":674},"\"start\": \"./node_modules/.bin/http-server\"\n",[676,54845,54846],{"__ignoreMap":674},[679,54847,54848,54851,54853],{"class":681,"line":682},[679,54849,54850],{"class":880},"\"start\"",[679,54852,2391],{"class":931},[679,54854,54855],{"class":689}," \"./node_modules/.bin/http-server\"\n",[4542,54857,54859],{"id":54858},"running-your-npm-scripts-in-parallel","Running your npm scripts in parallel",[651,54861,54862,54863,54866,54867,664],{},"Now that you have both of scripts created you need to find a way to run them both. In the first iteration of the exercise I asked the students to run both of them. This meant that in Visual Studio Code you need to open up a terminal, run ",[676,54864,54865],{},"npm run start"," and then open up a new terminal instance and run ",[676,54868,54869],{},"npm run test:e2e",[651,54871,54872],{},"This isn't a huge deal but when you're introducing new concepts to someone you want to remove as much friction as possible. The point of this exercise was not how to run multiple scripts it was to just run the tests to make sure the code they wrote was correct.",[651,54874,54875,54876,54878],{},"I was already aware that I could run 2 scripts one after another using the ",[676,54877,4201],{}," operator. This means that If I had 2 scripts the following script would work.",[669,54880,54882],{"className":5851,"code":54881,"language":5853,"meta":674,"style":674},"\"scripts\": {\n \"one\": \"./node_modules/.bin/one\",\n \"two\": \"./node_modules/.bin/two\",\n \"start\": \"npm run one && npm run two\"\n}\n",[676,54883,54884,54893,54903,54913,54923],{"__ignoreMap":674},[679,54885,54886,54889,54891],{"class":681,"line":682},[679,54887,54888],{"class":880},"\"scripts\"",[679,54890,2391],{"class":931},[679,54892,884],{"class":689},[679,54894,54895,54898,54900],{"class":681,"line":790},[679,54896,54897],{"class":880}," \"one\"",[679,54899,2391],{"class":931},[679,54901,54902],{"class":689}," \"./node_modules/.bin/one\",\n",[679,54904,54905,54908,54910],{"class":681,"line":892},[679,54906,54907],{"class":880}," \"two\"",[679,54909,2391],{"class":931},[679,54911,54912],{"class":689}," \"./node_modules/.bin/two\",\n",[679,54914,54915,54918,54920],{"class":681,"line":901},[679,54916,54917],{"class":880}," \"start\"",[679,54919,2391],{"class":931},[679,54921,54922],{"class":689}," \"npm run one && npm run two\"\n",[679,54924,54925],{"class":681,"line":909},[679,54926,996],{"class":693},[651,54928,54929],{},"This works by running the scripts sequentially and it also means that the first script has to complete before the 2nd script will run. In the case of an HTTP Server it stays running waiting to accept new requests. With the HTTP server never finishing the end to end tests would never run so it was back to the board for me.",[651,54931,54932,54933,54935,54936,54939],{},"After some searching around I did find a couple packages, one of which I will talk about later in this article. I also came across some documentation that said using ",[676,54934,4201],{}," will run your scripts sequentially while ",[676,54937,54938],{},"&"," will run them in parallel.",[651,54941,54942],{},"This was a real mind blown 🤯moment for me. I immediately tried this and it worked which prompted me to send out the following tweet.",[651,54944,54945],{},[812,54946,54947],{"href":54947,"rel":54948},"https://twitter.com/therealdanvega/status/1116403685452668928",[816],[651,54950,54951],{},"I got a lot of responses and most of them were similar to my reaction which was wow, I didn't know it could do that. I did have a couple though that spoiled my party and and asked about Windows.",[4542,54953,54955],{"id":54954},"what-about-windows-dan","What about windows Dan?",[651,54957,54958],{},"I am primarily mac user but I do have bootcamp on my mac so that I can jump into Windows when need be. When people started asking me about this my initial thought was this probably isn't going to work on Windows. Sure enough after a quick test, this actually doesn't work on Windows because cmd.exe doesn't support it.",[651,54960,54961,54962,54967],{},"No worries though as there is still a cross platform solution to this problem. I did some more searching around and came across a few npm packages that looked like they would work. I ended up installing ",[812,54963,54966],{"href":54964,"rel":54965},"https://www.npmjs.com/package/npm-run-all",[816],"npm-run-all"," which worked out great.",[669,54969,54971],{"className":5851,"code":54970,"language":5853,"meta":674,"style":674},"npm install -D npm-run-all\n\n\"start\": \"./node_modules/.bin/http-server\",\n\"cypress\": \"./node_modules/.bin/cypress run\",\n\"test\": \"npm-run-all -p start cypress\"\n",[676,54972,54973,54984,54988,54997,55007],{"__ignoreMap":674},[679,54974,54975,54977,54979,54981],{"class":681,"line":682},[679,54976,24568],{"class":880},[679,54978,24571],{"class":689},[679,54980,42645],{"class":931},[679,54982,54983],{"class":689}," npm-run-all\n",[679,54985,54986],{"class":681,"line":790},[679,54987,889],{"emptyLinePlaceholder":797},[679,54989,54990,54992,54994],{"class":681,"line":892},[679,54991,54850],{"class":880},[679,54993,2391],{"class":931},[679,54995,54996],{"class":689}," \"./node_modules/.bin/http-server\",\n",[679,54998,54999,55002,55004],{"class":681,"line":901},[679,55000,55001],{"class":880},"\"cypress\"",[679,55003,2391],{"class":931},[679,55005,55006],{"class":689}," \"./node_modules/.bin/cypress run\",\n",[679,55008,55009,55012,55014],{"class":681,"line":909},[679,55010,55011],{"class":880},"\"test\"",[679,55013,2391],{"class":931},[679,55015,55016],{"class":689}," \"npm-run-all -p start cypress\"\n",[651,55018,55019,55020,55022],{},"According to some people much smarter than me this is probably a safer route than using ",[676,55021,54938],{}," which I don't quite understand but sounds good to me.",[4542,55024,9042],{"id":9041},[651,55026,55027,55028,55030,55031,55035],{},"I feel so lucky to be in a position where I get to learn something every single day. This was one of those days though where I was genuinely excited to learn about the single ampersand ",[676,55029,54938],{}," operator. If you have learned something lately that warrants the head exploding 🤯emoji please ",[812,55032,55034],{"href":51472,"rel":55033},[816],"reach out to me on twitter"," because I would love to hear about it. As always....",[651,55037,41105,55038,41109],{},[41107,55039],{},[786,55041,37420],{},{"title":674,"searchDepth":790,"depth":790,"links":55043},[55044,55048,55049,55050],{"id":54715,"depth":790,"text":54716,"children":55045},[55046,55047],{"id":54725,"depth":892,"text":54692},{"id":54812,"depth":892,"text":54813},{"id":54858,"depth":790,"text":54859},{"id":54954,"depth":790,"text":54955},{"id":9041,"depth":790,"text":9042},"In this article I will talk about a problem I ran into recently and a couple of the solutions I found.",{"slug":55053,"date":55054,"published":797,"author":798,"tags":55055,"cover":55056},"npm-scripts-parallel","2019-04-19T16:51:56.537Z",[24568,27318,25134],"./npm-parallel-cover.png",{"title":507,"description":55051},"blog/2019/04/19/npm-scripts-parallel","dOuGruK-uz_UuiW2P6hikZ2_o4T8vY3FSK6b4_KV-vI",{"id":55061,"title":504,"body":55062,"description":56572,"extension":793,"meta":56573,"navigation":797,"path":505,"seo":56578,"stem":56579,"__hash__":56580},"content/blog/2019/04/23/gridsome-blog-post-generator.md",{"type":648,"value":55063,"toc":56555},[55064,55074,55077,55080,55083,55087,55090,55097,55100,55139,55142,55174,55177,55207,55214,55248,55254,55269,55272,55286,55290,55293,55304,55307,55317,55320,55323,55332,55363,55366,55382,55385,55408,55415,55431,55439,55442,55630,55637,55640,55648,55651,55678,55681,55684,55688,55691,55695,55698,55707,55748,55752,55755,55764,55767,55892,55898,55926,55929,55972,55976,55983,56003,56007,56010,56040,56063,56108,56112,56115,56136,56144,56249,56251,56254,56344,56353,56405,56409,56418,56434,56437,56496,56499,56536,56538,56541,56549,56552],[651,55065,55066,55067,51393,55070,55073],{},"If you're using something like ",[812,55068,43821],{"href":43819,"rel":55069},[816],[812,55071,43848],{"href":43854,"rel":55072},[816]," as your blogging platform there is no UI to create a new blog post. The process usually starts with creating a directory or series of directories depending on your post format and then creating a new markdown file.",[651,55075,55076],{},"From there you have to add a bunch front matter that describes your post. This is usually specific to your blog requirements but you might have things like title, slug, author, date and so on.",[651,55078,55079],{},"This become tedious and even worse a copy/paste project over and over again which I don't like to do. In fact anytime I catch myself copying/pasting something more then a couple times it's probably time to find a solution to that problem.",[651,55081,55082],{},"In this article I am going to walk you through the blog post generator that I wrote. There have been a few iterations of this script and I have definitely learned a few tricks from others who have done something similar.",[4542,55084,55086],{"id":55085},"creating-initializing-the-script","Creating & Initializing the Script",[651,55088,55089],{},"The first thing you need to decide is where is this script going to go. There really is no wrong or right answer so for me, I just created a folder off of my root called scripts. I figured this can be a place for random scripts I might need and If I find a better place for them later I can refactor. This, by the way, is something I do every single time I write code, find a fast way to get it done and make it work and make it pretty later on.",[651,55091,55092,55093,55096],{},"The first thing I am going to do is to create a script called ",[676,55094,55095],{},"newpost.js"," in my scripts folder. Next, we need to how this script is going to be structured. In some cases, we could just write it top down but for this instance, it won't work.",[651,55098,55099],{},"It is common to wrap code in a function and execute that function and there are a few ways that we could do that. We could just write a normal function that contains all of our logic and then call that function at the end of the script to kick things off.",[669,55101,55103],{"className":25132,"code":55102,"language":25134,"meta":674,"style":674},"function newPost() {\n console.log(\"create new post...\");\n}\nnewPost();\n",[676,55104,55105,55115,55128,55132],{"__ignoreMap":674},[679,55106,55107,55110,55113],{"class":681,"line":682},[679,55108,55109],{"class":685},"function",[679,55111,55112],{"class":880}," newPost",[679,55114,2667],{"class":693},[679,55116,55117,55119,55121,55123,55126],{"class":681,"line":790},[679,55118,46950],{"class":693},[679,55120,21374],{"class":880},[679,55122,745],{"class":693},[679,55124,55125],{"class":689},"\"create new post...\"",[679,55127,1208],{"class":693},[679,55129,55130],{"class":681,"line":892},[679,55131,996],{"class":693},[679,55133,55134,55137],{"class":681,"line":901},[679,55135,55136],{"class":880},"newPost",[679,55138,9317],{"class":693},[651,55140,55141],{},"If all you're going to do though is call the function there is a better way of approaching this. You can write what's called a Self Executing Function, also referred to as an Immediately Invoked Function Expression or IIFE. To accomplish that",[669,55143,55145],{"className":25132,"code":55144,"language":25134,"meta":674,"style":674},"(function newPost() {\n console.log(\"create new post...\");\n})();\n",[676,55146,55147,55157,55169],{"__ignoreMap":674},[679,55148,55149,55151,55153,55155],{"class":681,"line":682},[679,55150,745],{"class":693},[679,55152,55109],{"class":685},[679,55154,55112],{"class":880},[679,55156,2667],{"class":693},[679,55158,55159,55161,55163,55165,55167],{"class":681,"line":790},[679,55160,46950],{"class":693},[679,55162,21374],{"class":880},[679,55164,745],{"class":693},[679,55166,55125],{"class":689},[679,55168,1208],{"class":693},[679,55170,55171],{"class":681,"line":892},[679,55172,55173],{"class":693},"})();\n",[651,55175,55176],{},"You can also write this using an arrow function",[669,55178,55180],{"className":25132,"code":55179,"language":25134,"meta":674,"style":674},"(() => {\n console.log(\"create new post...\");\n})();\n",[676,55181,55182,55191,55203],{"__ignoreMap":674},[679,55183,55184,55187,55189],{"class":681,"line":682},[679,55185,55186],{"class":693},"(() ",[679,55188,21350],{"class":685},[679,55190,884],{"class":693},[679,55192,55193,55195,55197,55199,55201],{"class":681,"line":790},[679,55194,46950],{"class":693},[679,55196,21374],{"class":880},[679,55198,745],{"class":693},[679,55200,55125],{"class":689},[679,55202,1208],{"class":693},[679,55204,55205],{"class":681,"line":892},[679,55206,55173],{"class":693},[651,55208,55209,55210,55213],{},"And just like any normal function if you're going to be performing an asynchronous task you can use the ",[676,55211,55212],{},"async"," keyword. In this case we are going to pull in a library to help us write our command line application so we are going to start with the following.",[669,55215,55217],{"className":25132,"code":55216,"language":25134,"meta":674,"style":674},"(async () => {\n console.log(\"create new post...\");\n})();\n",[676,55218,55219,55232,55244],{"__ignoreMap":674},[679,55220,55221,55223,55225,55228,55230],{"class":681,"line":682},[679,55222,745],{"class":693},[679,55224,55212],{"class":685},[679,55226,55227],{"class":693}," () ",[679,55229,21350],{"class":685},[679,55231,884],{"class":693},[679,55233,55234,55236,55238,55240,55242],{"class":681,"line":790},[679,55235,46950],{"class":693},[679,55237,21374],{"class":880},[679,55239,745],{"class":693},[679,55241,55125],{"class":689},[679,55243,1208],{"class":693},[679,55245,55246],{"class":681,"line":892},[679,55247,55173],{"class":693},[651,55249,55250,55251],{},"Before you can test this out you need to add a new script to your ",[676,55252,55253],{},"package.json",[669,55255,55257],{"className":25132,"code":55256,"language":25134,"meta":674,"style":674},"\"newpost\": \"node ./scripts/newpost.js\"\n",[676,55258,55259],{"__ignoreMap":674},[679,55260,55261,55264,55266],{"class":681,"line":682},[679,55262,55263],{"class":689},"\"newpost\"",[679,55265,4282],{"class":693},[679,55267,55268],{"class":689},"\"node ./scripts/newpost.js\"\n",[651,55270,55271],{},"At this point I would give the script a quick test just to make sure everything is working as expected.",[669,55273,55275],{"className":5851,"code":55274,"language":5853,"meta":674,"style":674},"npm run newpost\n",[676,55276,55277],{"__ignoreMap":674},[679,55278,55279,55281,55283],{"class":681,"line":682},[679,55280,24568],{"class":880},[679,55282,16486],{"class":689},[679,55284,55285],{"class":689}," newpost\n",[4542,55287,55289],{"id":55288},"accepting-user-input","Accepting user input",[651,55291,55292],{},"Now that you have script ready to go it's time to start building out some functionality. The first thing you need to do is to ask for some details about the new post. This is obviously going to be different for everyone based on your needs but here are the pieces of data I want to ask for and the pieces of data I can infer.",[5316,55294,55295,55298,55301],{},[5332,55296,55297],{},"Title",[5332,55299,55300],{},"Excerpt",[5332,55302,55303],{},"Tags",[651,55305,55306],{},"These are a list of items that I can determine based off of what the user entered for above or when the script was run.",[5316,55308,55309,55312,55314],{},[5332,55310,55311],{},"slug",[5332,55313,48502],{},[5332,55315,55316],{},"Author",[651,55318,55319],{},"And that is really all I need to get started. As I said before this might be different for you but you can adjust accordingly.",[5909,55321,55322],{"id":55322},"inquirer",[651,55324,55325,55326,55331],{},"To help with asking for user input we are going to ",[812,55327,55330],{"href":55328,"rel":55329},"https://www.npmjs.com/package/inquirer",[816],"install the package inquirer",". Inquirer is a collection of common interactive command line user interfaces. Inquirer should ease the process of:",[5316,55333,55334,55340,55345,55351,55357],{},[5332,55335,55336,55337],{},"providing ",[7300,55338,55339],{},"error feedback",[5332,55341,55342],{},[7300,55343,55344],{},"asking questions",[5332,55346,55347,55350],{},[7300,55348,55349],{},"parsing"," input",[5332,55352,55353,55356],{},[7300,55354,55355],{},"validating"," answers",[5332,55358,55359,55360],{},"managing ",[7300,55361,55362],{},"hierarchical prompts",[651,55364,55365],{},"To get started you can install it as a dev dependency by running the following command:",[669,55367,55369],{"className":5851,"code":55368,"language":5853,"meta":674,"style":674},"npm install -D inquirer\n",[676,55370,55371],{"__ignoreMap":674},[679,55372,55373,55375,55377,55379],{"class":681,"line":682},[679,55374,24568],{"class":880},[679,55376,24571],{"class":689},[679,55378,42645],{"class":931},[679,55380,55381],{"class":689}," inquirer\n",[651,55383,55384],{},"and require it in your script",[669,55386,55388],{"className":25132,"code":55387,"language":25134,"meta":674,"style":674},"const inquirer = require(\"inquirer\");\n",[676,55389,55390],{"__ignoreMap":674},[679,55391,55392,55394,55397,55399,55401,55403,55406],{"class":681,"line":682},[679,55393,45172],{"class":685},[679,55395,55396],{"class":931}," inquirer",[679,55398,6883],{"class":685},[679,55400,45180],{"class":880},[679,55402,745],{"class":693},[679,55404,55405],{"class":689},"\"inquirer\"",[679,55407,1208],{"class":693},[651,55409,55410,55411,55414],{},"This package can do at lot more than we will use it for so if you have a chance ",[812,55412,40328],{"href":55328,"rel":55413},[816],". The first thing you need to do is ask for the process arguments.",[669,55416,55418],{"className":25132,"code":55417,"language":25134,"meta":674,"style":674},"const args = process.argv;\n",[676,55419,55420],{"__ignoreMap":674},[679,55421,55422,55424,55426,55428],{"class":681,"line":682},[679,55423,45172],{"class":685},[679,55425,16805],{"class":931},[679,55427,6883],{"class":685},[679,55429,55430],{"class":693}," process.argv;\n",[1004,55432,55433],{},[651,55434,55435,55436,55438],{},"The process.argv property returns an array containing the command line arguments passed when the Node.js process was launched. The first element will be process.execPath. See process.argv0 if access to the original value of argv",[679,55437,1060],{}," is needed. The second element will be the path to the JavaScript file being executed. The remaining elements will be any additional command line arguments.",[651,55440,55441],{},"If you wanted to you could check for the existence of user supplied arguments and take those in but in this example I am going to say as long as there are no custom arguments let's ask the user for some data.",[669,55443,55445],{"className":25132,"code":55444,"language":25134,"meta":674,"style":674},"if (args.length \u003C 3) {\n const { title, excerpt, tags } = await inquirer.prompt([\n {\n type: \"input\",\n name: \"title\",\n message: \"Post Title:\"\n },\n {\n type: \"input\",\n name: \"excerpt\",\n message: \"Post description:\"\n },\n {\n type: \"input\",\n name: \"tags\",\n message: \"Tags (comma separated):\"\n }\n ]);\n} else {\n log(error(\"Please don't provide any arguments to the new post generator\"));\n}\n",[676,55446,55447,55463,55498,55502,55512,55521,55529,55533,55537,55545,55554,55561,55565,55569,55577,55586,55593,55597,55602,55610,55626],{"__ignoreMap":674},[679,55448,55449,55451,55454,55456,55458,55461],{"class":681,"line":682},[679,55450,1217],{"class":685},[679,55452,55453],{"class":693}," (args.",[679,55455,51959],{"class":931},[679,55457,48609],{"class":685},[679,55459,55460],{"class":931}," 3",[679,55462,4390],{"class":693},[679,55464,55465,55467,55469,55471,55473,55476,55478,55481,55484,55486,55489,55492,55495],{"class":681,"line":790},[679,55466,46903],{"class":685},[679,55468,2998],{"class":693},[679,55470,11750],{"class":931},[679,55472,2797],{"class":693},[679,55474,55475],{"class":931},"excerpt",[679,55477,2797],{"class":693},[679,55479,55480],{"class":931},"tags",[679,55482,55483],{"class":693}," } ",[679,55485,686],{"class":685},[679,55487,55488],{"class":685}," await",[679,55490,55491],{"class":693}," inquirer.",[679,55493,55494],{"class":880},"prompt",[679,55496,55497],{"class":693},"([\n",[679,55499,55500],{"class":681,"line":892},[679,55501,28496],{"class":693},[679,55503,55504,55507,55510],{"class":681,"line":901},[679,55505,55506],{"class":693}," type: ",[679,55508,55509],{"class":689},"\"input\"",[679,55511,12083],{"class":693},[679,55513,55514,55517,55519],{"class":681,"line":909},[679,55515,55516],{"class":693}," name: ",[679,55518,5250],{"class":689},[679,55520,12083],{"class":693},[679,55522,55523,55526],{"class":681,"line":918},[679,55524,55525],{"class":693}," message: ",[679,55527,55528],{"class":689},"\"Post Title:\"\n",[679,55530,55531],{"class":681,"line":935},[679,55532,28763],{"class":693},[679,55534,55535],{"class":681,"line":944},[679,55536,28496],{"class":693},[679,55538,55539,55541,55543],{"class":681,"line":959},[679,55540,55506],{"class":693},[679,55542,55509],{"class":689},[679,55544,12083],{"class":693},[679,55546,55547,55549,55552],{"class":681,"line":964},[679,55548,55516],{"class":693},[679,55550,55551],{"class":689},"\"excerpt\"",[679,55553,12083],{"class":693},[679,55555,55556,55558],{"class":681,"line":977},[679,55557,55525],{"class":693},[679,55559,55560],{"class":689},"\"Post description:\"\n",[679,55562,55563],{"class":681,"line":982},[679,55564,28763],{"class":693},[679,55566,55567],{"class":681,"line":988},[679,55568,28496],{"class":693},[679,55570,55571,55573,55575],{"class":681,"line":993},[679,55572,55506],{"class":693},[679,55574,55509],{"class":689},[679,55576,12083],{"class":693},[679,55578,55579,55581,55584],{"class":681,"line":2129},[679,55580,55516],{"class":693},[679,55582,55583],{"class":689},"\"tags\"",[679,55585,12083],{"class":693},[679,55587,55588,55590],{"class":681,"line":2140},[679,55589,55525],{"class":693},[679,55591,55592],{"class":689},"\"Tags (comma separated):\"\n",[679,55594,55595],{"class":681,"line":2145},[679,55596,985],{"class":693},[679,55598,55599],{"class":681,"line":2154},[679,55600,55601],{"class":693}," ]);\n",[679,55603,55604,55606,55608],{"class":681,"line":2159},[679,55605,2253],{"class":693},[679,55607,2256],{"class":685},[679,55609,884],{"class":693},[679,55611,55612,55615,55617,55619,55621,55624],{"class":681,"line":2164},[679,55613,55614],{"class":880}," log",[679,55616,745],{"class":693},[679,55618,29446],{"class":880},[679,55620,745],{"class":693},[679,55622,55623],{"class":689},"\"Please don't provide any arguments to the new post generator\"",[679,55625,1669],{"class":693},[679,55627,55628],{"class":681,"line":3134},[679,55629,996],{"class":693},[651,55631,55632,55633,55636],{},"We will talk about the log line in a bit but for now let's focus on inquirer. When we were setting up the script I said that we needed to mark the self executing function as async and this is why. ",[676,55634,55635],{},"inquirer.prompt"," returns a promise so we will use await here.",[651,55638,55639],{},"We are asking for 3 different pieces of data from the user",[5316,55641,55642,55644,55646],{},[5332,55643,11750],{},[5332,55645,55475],{},[5332,55647,55480],{},[651,55649,55650],{},"We could have just created a single variable to hold the responses but instead we are destructuring the responses into 3 variables.",[669,55652,55654],{"className":25132,"code":55653,"language":25134,"meta":674,"style":674},"const { title, excerpt, tags } = ...\n",[676,55655,55656],{"__ignoreMap":674},[679,55657,55658,55660,55662,55664,55666,55668,55670,55672,55674,55676],{"class":681,"line":682},[679,55659,45172],{"class":685},[679,55661,2998],{"class":693},[679,55663,11750],{"class":931},[679,55665,2797],{"class":693},[679,55667,55475],{"class":931},[679,55669,2797],{"class":693},[679,55671,55480],{"class":931},[679,55673,55483],{"class":693},[679,55675,686],{"class":685},[679,55677,24757],{"class":685},[651,55679,55680],{},"Each object in the array argument supplied to the prompt method is a Question. In our example we are asking for simple input, defining the name of the question and what the message should display to the user. Again these can get much more complex so check out the documentation if you have more specific needs.",[651,55682,55683],{},"Now that we have the answers from our user we can use those to build out our new post.",[4542,55685,55687],{"id":55686},"creating-the-post-directory","Creating the post directory",[651,55689,55690],{},"Before we start to create any folder or files you need to do a little more setup.",[5909,55692,55694],{"id":55693},"post-slug","Post Slug",[651,55696,55697],{},"Now that I have the title of the post I need to create a slug. A slug is a URL friendly version of my title that helps when it comes to SEO. If the title of my post was 'My First Post' a slug would be 'my-first-post'.",[651,55699,55700,55701,55706],{},"Now in this simple example this is something we could probably handle on our own but this can get pretty complex. For this, I am going to install in a ",[812,55702,55705],{"href":55703,"rel":55704},"https://www.npmjs.com/package/slugify",[816],"package called slugify",", require it and then create a slug.",[669,55708,55710],{"className":25132,"code":55709,"language":25134,"meta":674,"style":674},"const slugify = require(\"slugify\");\n\nconst slug = slugify(title);\n",[676,55711,55712,55730,55734],{"__ignoreMap":674},[679,55713,55714,55716,55719,55721,55723,55725,55728],{"class":681,"line":682},[679,55715,45172],{"class":685},[679,55717,55718],{"class":931}," slugify",[679,55720,6883],{"class":685},[679,55722,45180],{"class":880},[679,55724,745],{"class":693},[679,55726,55727],{"class":689},"\"slugify\"",[679,55729,1208],{"class":693},[679,55731,55732],{"class":681,"line":790},[679,55733,889],{"emptyLinePlaceholder":797},[679,55735,55736,55738,55741,55743,55745],{"class":681,"line":892},[679,55737,45172],{"class":685},[679,55739,55740],{"class":931}," slug",[679,55742,6883],{"class":685},[679,55744,55718],{"class":880},[679,55746,55747],{"class":693},"(title);\n",[5909,55749,55751],{"id":55750},"folder-url-format","Folder & URL Format",[651,55753,55754],{},"Each of my blog posts use the following format",[669,55756,55758],{"className":4496,"code":55757,"language":4498,"meta":674,"style":674},"https://www.danvega.dev/{year}/{month}/{day}/{slug}\n",[676,55759,55760],{"__ignoreMap":674},[679,55761,55762],{"class":681,"line":682},[679,55763,55757],{"class":693},[651,55765,55766],{},"So far we have the slug but now I need to extract some parts for the date. Since we are running the generator right now I am going to assume we want to post this today and use that as the basis for our date. You would think this would be easier but working with dates is one of those things that never seems easy in any language.",[669,55768,55770],{"className":25132,"code":55769,"language":25134,"meta":674,"style":674},"const createdOn = new Date();\nconst year = createdOn.getFullYear();\nconst month = `${createdOn.getMonth() + 1 \u003C 10 ? \"0\" : \"\"}${createdOn.getMonth() + 1}`;\nconst day = `${createdOn.getDate() \u003C 10 ? \"0\" : \"\"}${createdOn.getDate()}`;\n",[676,55771,55772,55786,55800,55850],{"__ignoreMap":674},[679,55773,55774,55776,55778,55780,55782,55784],{"class":681,"line":682},[679,55775,45172],{"class":685},[679,55777,48485],{"class":931},[679,55779,6883],{"class":685},[679,55781,2054],{"class":685},[679,55783,17511],{"class":880},[679,55785,9317],{"class":693},[679,55787,55788,55790,55792,55794,55796,55798],{"class":681,"line":790},[679,55789,45172],{"class":685},[679,55791,48567],{"class":931},[679,55793,6883],{"class":685},[679,55795,48572],{"class":693},[679,55797,48575],{"class":880},[679,55799,9317],{"class":693},[679,55801,55802,55804,55806,55808,55810,55812,55814,55816,55818,55820,55822,55824,55826,55828,55830,55832,55834,55836,55838,55840,55842,55844,55846,55848],{"class":681,"line":892},[679,55803,45172],{"class":685},[679,55805,48584],{"class":931},[679,55807,6883],{"class":685},[679,55809,48656],{"class":689},[679,55811,48630],{"class":693},[679,55813,664],{"class":689},[679,55815,48599],{"class":880},[679,55817,6700],{"class":689},[679,55819,3065],{"class":685},[679,55821,48606],{"class":931},[679,55823,48609],{"class":685},[679,55825,48612],{"class":931},[679,55827,14615],{"class":685},[679,55829,48617],{"class":689},[679,55831,3033],{"class":685},[679,55833,48678],{"class":689},[679,55835,48630],{"class":693},[679,55837,664],{"class":689},[679,55839,48599],{"class":880},[679,55841,6700],{"class":689},[679,55843,3065],{"class":685},[679,55845,48606],{"class":931},[679,55847,47580],{"class":689},[679,55849,1186],{"class":693},[679,55851,55852,55854,55856,55858,55860,55862,55864,55866,55868,55870,55872,55874,55876,55878,55880,55882,55884,55886,55888,55890],{"class":681,"line":901},[679,55853,45172],{"class":685},[679,55855,48651],{"class":931},[679,55857,6883],{"class":685},[679,55859,48656],{"class":689},[679,55861,48630],{"class":693},[679,55863,664],{"class":689},[679,55865,48663],{"class":880},[679,55867,6700],{"class":689},[679,55869,4505],{"class":685},[679,55871,48612],{"class":931},[679,55873,14615],{"class":685},[679,55875,48617],{"class":689},[679,55877,3033],{"class":685},[679,55879,48678],{"class":689},[679,55881,48630],{"class":693},[679,55883,664],{"class":689},[679,55885,48663],{"class":880},[679,55887,48687],{"class":689},[679,55889,47580],{"class":689},[679,55891,1186],{"class":693},[651,55893,55894,55895,55897],{},"Now that we have our date parts we can create a variable called ",[676,55896,48880],{}," that will be a path to the folder where the new markdown file will be created.",[669,55899,55900],{"className":25132,"code":48884,"language":25134,"meta":674,"style":674},[676,55901,55902],{"__ignoreMap":674},[679,55903,55904,55906,55908,55910,55912,55914,55916,55918,55920,55922,55924],{"class":681,"line":682},[679,55905,45172],{"class":685},[679,55907,48893],{"class":931},[679,55909,6883],{"class":685},[679,55911,48898],{"class":689},[679,55913,48901],{"class":693},[679,55915,48904],{"class":689},[679,55917,48907],{"class":693},[679,55919,48904],{"class":689},[679,55921,48912],{"class":693},[679,55923,47580],{"class":689},[679,55925,1186],{"class":693},[651,55927,55928],{},"And finally I am just going to clean up the tags and turn them into a list.",[669,55930,55932],{"className":25132,"code":55931,"language":25134,"meta":674,"style":674},"const tagsList = tags.split(\",\").map(t => t.trim());\n",[676,55933,55934],{"__ignoreMap":674},[679,55935,55936,55938,55941,55943,55946,55949,55951,55953,55955,55957,55959,55962,55964,55967,55970],{"class":681,"line":682},[679,55937,45172],{"class":685},[679,55939,55940],{"class":931}," tagsList",[679,55942,6883],{"class":685},[679,55944,55945],{"class":693}," tags.",[679,55947,55948],{"class":880},"split",[679,55950,745],{"class":693},[679,55952,697],{"class":689},[679,55954,47213],{"class":693},[679,55956,51904],{"class":880},[679,55958,745],{"class":693},[679,55960,55961],{"class":2099},"t",[679,55963,44760],{"class":685},[679,55965,55966],{"class":693}," t.",[679,55968,55969],{"class":880},"trim",[679,55971,9431],{"class":693},[4542,55973,55975],{"id":55974},"creating-files-folders","Creating Files & Folders",[651,55977,55978,55979,664],{},"Now that you have all the variables in place it's time to start creating some files and folders. To do so you need to require the ",[812,55980,55982],{"href":48922,"rel":55981},[816],"File System Module",[669,55984,55985],{"className":25132,"code":48928,"language":25134,"meta":674,"style":674},[676,55986,55987],{"__ignoreMap":674},[679,55988,55989,55991,55993,55995,55997,55999,56001],{"class":681,"line":682},[679,55990,45172],{"class":685},[679,55992,48937],{"class":931},[679,55994,6883],{"class":685},[679,55996,45180],{"class":880},[679,55998,745],{"class":693},[679,56000,48946],{"class":689},[679,56002,1208],{"class":693},[5909,56004,56006],{"id":56005},"creating-recursive-directories-in-node","Creating Recursive Directories in node",[651,56008,56009],{},"We already created a variable for our blog post folder location so let's start there. The first thing you will want to do is to check to see if it already exists because if it does we don't need to create it. This will almost never be the case for me because It is hard enough for me to crank out 1 per week but let's play it safe in case I get ambitious one day.",[669,56011,56013],{"className":25132,"code":56012,"language":25134,"meta":674,"style":674},"if (!fs.existsSync(blogPostFolder)) {\n // create directory\n}\n",[676,56014,56015,56031,56036],{"__ignoreMap":674},[679,56016,56017,56019,56021,56023,56025,56028],{"class":681,"line":682},[679,56018,1217],{"class":685},[679,56020,4193],{"class":693},[679,56022,1223],{"class":685},[679,56024,48969],{"class":693},[679,56026,56027],{"class":880},"existsSync",[679,56029,56030],{"class":693},"(blogPostFolder)) {\n",[679,56032,56033],{"class":681,"line":790},[679,56034,56035],{"class":1400}," // create directory\n",[679,56037,56038],{"class":681,"line":892},[679,56039,996],{"class":693},[651,56041,56042,56043,56046,56047,56050,56051,56054,56055,56057,56058,56062],{},"This is the tricky part that can trip some people up and indeed got me the first time. If you're just creating a single directory ",[812,56044,48972],{"href":48954,"rel":56045},[816]," with no options will work just fine. What I mean by that is let's say that you already have the folder ",[676,56048,56049],{},"blog/2019/04/"," created and you just needed to create the day ",[676,56052,56053],{},"24"," folder than this will work fine. If you need to recursively (more than 1 level deep) create folders you need to pass an option to the ",[676,56056,48972],{}," method. I ",[812,56059,36328],{"href":56060,"rel":56061},"https://www.danvega.dev/blog/2019/02/20/node-recursive-directories",[816]," that goes a little more into this if you're interested.",[669,56064,56066],{"className":25132,"code":56065,"language":25134,"meta":674,"style":674},"if (!fs.existsSync(blogPostFolder)) {\n fs.mkdirSync(blogPostFolder, {\n recursive: true\n });\n}\n",[676,56067,56068,56082,56092,56099,56104],{"__ignoreMap":674},[679,56069,56070,56072,56074,56076,56078,56080],{"class":681,"line":682},[679,56071,1217],{"class":685},[679,56073,4193],{"class":693},[679,56075,1223],{"class":685},[679,56077,48969],{"class":693},[679,56079,56027],{"class":880},[679,56081,56030],{"class":693},[679,56083,56084,56087,56089],{"class":681,"line":790},[679,56085,56086],{"class":693}," fs.",[679,56088,48972],{"class":880},[679,56090,56091],{"class":693},"(blogPostFolder, {\n",[679,56093,56094,56097],{"class":681,"line":892},[679,56095,56096],{"class":693}," recursive: ",[679,56098,5134],{"class":931},[679,56100,56101],{"class":681,"line":901},[679,56102,56103],{"class":693}," });\n",[679,56105,56106],{"class":681,"line":909},[679,56107,996],{"class":693},[5909,56109,56111],{"id":56110},"front-matter","Front Matter",[651,56113,56114],{},"In each Markdown file, we define the blog post using something called front matter. These are variables inside of a YAML declaration block",[669,56116,56118],{"className":25132,"code":56117,"language":25134,"meta":674,"style":674},"---\nkey: value\n---\n",[676,56119,56120,56124,56132],{"__ignoreMap":674},[679,56121,56122],{"class":681,"line":682},[679,56123,31635],{"class":685},[679,56125,56126,56129],{"class":681,"line":790},[679,56127,56128],{"class":880},"key",[679,56130,56131],{"class":693},": value\n",[679,56133,56134],{"class":681,"line":892},[679,56135,31635],{"class":685},[651,56137,56138,56139,664],{},"To help us create the front matter we are going to bring in a package called ",[812,56140,56143],{"href":56141,"rel":56142},"https://www.npmjs.com/package/json-to-pretty-yaml",[816],"json-to-pretty-yaml",[669,56145,56147],{"className":25132,"code":56146,"language":25134,"meta":674,"style":674},"const jsToYaml = require(\"json-to-pretty-yaml\");\n\nconst yaml = jsToYaml.stringify({\n slug,\n title,\n date: createdOn.toISOString(),\n published: true,\n description: excerpt,\n author: \"Dan Vega\",\n tags: tagsList,\n cover: \"\"\n});\n",[676,56148,56149,56167,56171,56188,56193,56198,56209,56218,56223,56232,56237,56245],{"__ignoreMap":674},[679,56150,56151,56153,56156,56158,56160,56162,56165],{"class":681,"line":682},[679,56152,45172],{"class":685},[679,56154,56155],{"class":931}," jsToYaml",[679,56157,6883],{"class":685},[679,56159,45180],{"class":880},[679,56161,745],{"class":693},[679,56163,56164],{"class":689},"\"json-to-pretty-yaml\"",[679,56166,1208],{"class":693},[679,56168,56169],{"class":681,"line":790},[679,56170,889],{"emptyLinePlaceholder":797},[679,56172,56173,56175,56178,56180,56183,56186],{"class":681,"line":892},[679,56174,45172],{"class":685},[679,56176,56177],{"class":931}," yaml",[679,56179,6883],{"class":685},[679,56181,56182],{"class":693}," jsToYaml.",[679,56184,56185],{"class":880},"stringify",[679,56187,21218],{"class":693},[679,56189,56190],{"class":681,"line":901},[679,56191,56192],{"class":693}," slug,\n",[679,56194,56195],{"class":681,"line":909},[679,56196,56197],{"class":693}," title,\n",[679,56199,56200,56203,56206],{"class":681,"line":918},[679,56201,56202],{"class":693}," date: createdOn.",[679,56204,56205],{"class":880},"toISOString",[679,56207,56208],{"class":693},"(),\n",[679,56210,56211,56214,56216],{"class":681,"line":935},[679,56212,56213],{"class":693}," published: ",[679,56215,3441],{"class":931},[679,56217,12083],{"class":693},[679,56219,56220],{"class":681,"line":944},[679,56221,56222],{"class":693}," description: excerpt,\n",[679,56224,56225,56228,56230],{"class":681,"line":959},[679,56226,56227],{"class":693}," author: ",[679,56229,45450],{"class":689},[679,56231,12083],{"class":693},[679,56233,56234],{"class":681,"line":964},[679,56235,56236],{"class":693}," tags: tagsList,\n",[679,56238,56239,56242],{"class":681,"line":977},[679,56240,56241],{"class":693}," cover: ",[679,56243,56244],{"class":689},"\"\"\n",[679,56246,56247],{"class":681,"line":982},[679,56248,46842],{"class":693},[5909,56250,43883],{"id":43882},[651,56252,56253],{},"With our front matter in place it's time to create our markdown file. I am going to bring in a package called prettier to format our markdown and make it, well, prettier ☺️",[669,56255,56257],{"className":25132,"code":56256,"language":25134,"meta":674,"style":674},"const prettier = require(\"prettier\");\n\nconst markdown = prettier.format(`---\\n${yaml}\\n---\\n`, {\n parser: \"markdown\",\n singleQuote: true\n});\n",[676,56258,56259,56277,56281,56323,56333,56340],{"__ignoreMap":674},[679,56260,56261,56263,56266,56268,56270,56272,56275],{"class":681,"line":682},[679,56262,45172],{"class":685},[679,56264,56265],{"class":931}," prettier",[679,56267,6883],{"class":685},[679,56269,45180],{"class":880},[679,56271,745],{"class":693},[679,56273,56274],{"class":689},"\"prettier\"",[679,56276,1208],{"class":693},[679,56278,56279],{"class":681,"line":790},[679,56280,889],{"emptyLinePlaceholder":797},[679,56282,56283,56285,56288,56290,56293,56295,56297,56300,56303,56306,56309,56312,56314,56316,56318,56320],{"class":681,"line":892},[679,56284,45172],{"class":685},[679,56286,56287],{"class":931}," markdown",[679,56289,6883],{"class":685},[679,56291,56292],{"class":693}," prettier.",[679,56294,17581],{"class":880},[679,56296,745],{"class":693},[679,56298,56299],{"class":689},"`---",[679,56301,56302],{"class":931},"\\n",[679,56304,56305],{"class":689},"${",[679,56307,56308],{"class":693},"yaml",[679,56310,56311],{"class":689},"}",[679,56313,56302],{"class":931},[679,56315,24728],{"class":689},[679,56317,56302],{"class":931},[679,56319,44388],{"class":689},[679,56321,56322],{"class":693},", {\n",[679,56324,56325,56328,56331],{"class":681,"line":901},[679,56326,56327],{"class":693}," parser: ",[679,56329,56330],{"class":689},"\"markdown\"",[679,56332,12083],{"class":693},[679,56334,56335,56338],{"class":681,"line":909},[679,56336,56337],{"class":693}," singleQuote: ",[679,56339,5134],{"class":931},[679,56341,56342],{"class":681,"line":918},[679,56343,46842],{"class":693},[651,56345,56346,56347,56350,56351,664],{},"Now that you have the content for the file all that's left to do is create the file. You will be using the File System module again but this time you will use the ",[676,56348,56349],{},"writeFileSync"," method. You will write this file to the blog post folder that you created earlier and the slug will be the name of the file with the file extension ",[676,56352,793],{},[669,56354,56356],{"className":25132,"code":56355,"language":25134,"meta":674,"style":674},"fs.writeFileSync(`${blogPostFolder}/${slug}.md`, markdown);\n\nlog(success(`Post ${title} was created successfully`));\n",[676,56357,56358,56381,56385],{"__ignoreMap":674},[679,56359,56360,56362,56364,56366,56369,56371,56373,56375,56378],{"class":681,"line":682},[679,56361,48969],{"class":693},[679,56363,56349],{"class":880},[679,56365,745],{"class":693},[679,56367,56368],{"class":689},"`${",[679,56370,48880],{"class":693},[679,56372,48904],{"class":689},[679,56374,55311],{"class":693},[679,56376,56377],{"class":689},"}.md`",[679,56379,56380],{"class":693},", markdown);\n",[679,56382,56383],{"class":681,"line":790},[679,56384,889],{"emptyLinePlaceholder":797},[679,56386,56387,56389,56391,56393,56395,56398,56400,56403],{"class":681,"line":892},[679,56388,21374],{"class":880},[679,56390,745],{"class":693},[679,56392,18088],{"class":880},[679,56394,745],{"class":693},[679,56396,56397],{"class":689},"`Post ${",[679,56399,11750],{"class":693},[679,56401,56402],{"class":689},"} was created successfully`",[679,56404,1669],{"class":693},[4542,56406,56408],{"id":56407},"logging","Logging",[651,56410,56411,56412,56417],{},"To add some styling to my terminal logging I am using a ",[812,56413,56416],{"href":56414,"rel":56415},"https://www.npmjs.com/package/chalk",[816],"package called chalk",". To install it locally run the following command:",[669,56419,56421],{"className":5851,"code":56420,"language":5853,"meta":674,"style":674},"npm install -D chalk\n",[676,56422,56423],{"__ignoreMap":674},[679,56424,56425,56427,56429,56431],{"class":681,"line":682},[679,56426,24568],{"class":880},[679,56428,24571],{"class":689},[679,56430,42645],{"class":931},[679,56432,56433],{"class":689}," chalk\n",[651,56435,56436],{},"And then add the following variable declarations to the top of your script.",[669,56438,56440],{"className":25132,"code":56439,"language":25134,"meta":674,"style":674},"const chalk = require(\"chalk\");\nconst log = console.log;\nconst error = chalk.bold.red;\nconst success = chalk.bold.green.inverse;\n",[676,56441,56442,56460,56472,56484],{"__ignoreMap":674},[679,56443,56444,56446,56449,56451,56453,56455,56458],{"class":681,"line":682},[679,56445,45172],{"class":685},[679,56447,56448],{"class":931}," chalk",[679,56450,6883],{"class":685},[679,56452,45180],{"class":880},[679,56454,745],{"class":693},[679,56456,56457],{"class":689},"\"chalk\"",[679,56459,1208],{"class":693},[679,56461,56462,56464,56467,56469],{"class":681,"line":790},[679,56463,45172],{"class":685},[679,56465,56466],{"class":931}," log",[679,56468,6883],{"class":685},[679,56470,56471],{"class":693}," console.log;\n",[679,56473,56474,56476,56479,56481],{"class":681,"line":892},[679,56475,45172],{"class":685},[679,56477,56478],{"class":931}," error",[679,56480,6883],{"class":685},[679,56482,56483],{"class":693}," chalk.bold.red;\n",[679,56485,56486,56488,56491,56493],{"class":681,"line":901},[679,56487,45172],{"class":685},[679,56489,56490],{"class":931}," success",[679,56492,6883],{"class":685},[679,56494,56495],{"class":693}," chalk.bold.green.inverse;\n",[651,56497,56498],{},"This allows me to write the following statements when I want to log errors or success and have some stylish log statements.",[669,56500,56502],{"className":25132,"code":56501,"language":25134,"meta":674,"style":674},"log(success(`Post ${title} was created successfully`));\nlog(error(\"Please don't provide any arguments to the new post generator\"));\n",[676,56503,56504,56522],{"__ignoreMap":674},[679,56505,56506,56508,56510,56512,56514,56516,56518,56520],{"class":681,"line":682},[679,56507,21374],{"class":880},[679,56509,745],{"class":693},[679,56511,18088],{"class":880},[679,56513,745],{"class":693},[679,56515,56397],{"class":689},[679,56517,11750],{"class":693},[679,56519,56402],{"class":689},[679,56521,1669],{"class":693},[679,56523,56524,56526,56528,56530,56532,56534],{"class":681,"line":790},[679,56525,21374],{"class":880},[679,56527,745],{"class":693},[679,56529,29446],{"class":880},[679,56531,745],{"class":693},[679,56533,55623],{"class":689},[679,56535,1669],{"class":693},[4542,56537,9042],{"id":9041},[651,56539,56540],{},"The plan here was to show you exactly how to create your own blog post generator but I hope you learned something more here. When you're building out projects like this and you find that you need something you can just create it yourself.",[651,56542,56543,56544,664],{},"If you have written something similar or solved a problem for your project I would love to hear about it. If you want to check out the src for my blog post generator along with the code for my entire website ",[812,56545,56548],{"href":56546,"rel":56547},"https://github.com/danvega/danvega-dev/blob/master/scripts/newpost.js",[816],"you can check it out here",[651,56550,56551],{},"FYI - I created the post you are reading using this exact script 🤯",[786,56553,56554],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":56556},[56557,56558,56561,56565,56570,56571],{"id":55085,"depth":790,"text":55086},{"id":55288,"depth":790,"text":55289,"children":56559},[56560],{"id":55322,"depth":892,"text":55322},{"id":55686,"depth":790,"text":55687,"children":56562},[56563,56564],{"id":55693,"depth":892,"text":55694},{"id":55750,"depth":892,"text":55751},{"id":55974,"depth":790,"text":55975,"children":56566},[56567,56568,56569],{"id":56005,"depth":892,"text":56006},{"id":56110,"depth":892,"text":56111},{"id":43882,"depth":892,"text":43883},{"id":56407,"depth":790,"text":56408},{"id":9041,"depth":790,"text":9042},"In this article I will show you how to crate a blog post generator for your static site.",{"slug":56574,"date":56575,"published":797,"author":798,"tags":56576,"cover":56577},"gridsome-blog-post-generator","2019-04-23T19:28:51.186Z",[43866],"nick-morrison-FHnnjk1Yj7Y-unsplash.jpg",{"title":504,"description":56572},"blog/2019/04/23/gridsome-blog-post-generator","AoWQyIeLUUbGcXNvJeJ0tC848r5hHKf8E6t0DMUuuwE",{"id":56582,"title":501,"body":56583,"description":56925,"extension":793,"meta":56926,"navigation":797,"path":502,"seo":56931,"stem":56932,"__hash__":56933},"content/blog/2019/04/25/my-new-blog-post-workflow.md",{"type":648,"value":56584,"toc":56907},[56585,56588,56592,56595,56598,56608,56611,56614,56617,56619,56622,56626,56629,56633,56636,56644,56647,56650,56653,56658,56662,56669,56672,56675,56680,56689,56693,56696,56699,56704,56707,56710,56715,56718,56723,56726,56730,56733,56777,56780,56785,56788,56792,56795,56798,56801,56805,56808,56813,56822,56826,56829,56832,56835,56839,56842,56847,56855,56860,56863,56866,56870,56878,56881,56890,56893,56898,56900,56903],[651,56586,56587],{},"Well, I have been blogging for a few months now on my new website and I thought I would share how I write all of my blog posts. I am really happy with my writing process and it how it allows me to write from any computer and quickly go from idea to live post. In this article, I will talk a little bit about how I used to write articles and what I was looking for in a new workflow.",[4542,56589,56591],{"id":56590},"writing-requirements","Writing Requirements",[651,56593,56594],{},"If you have been following me you know that I am coming from writing all of my articles using the WordPress administrator. I would say that 90% of my WordPress posts were using the classic editor but I was one of the few people that really liked the Gutenberg block editor.",[651,56596,56597],{},"I write a lot of documentation for personal projects and work. Almost everything I write these days is in Markdown so as far as requirements go I only have a few:",[5316,56599,56600,56603,56605],{},[5332,56601,56602],{},"Clean & Distraction free writing",[5332,56604,43883],{},[5332,56606,56607],{},"Spelling & Grammar checking",[5909,56609,56602],{"id":56610},"clean-distraction-free-writing",[651,56612,56613],{},"I can be easily distracted when I sit down to start writing so for me I need a distraction-free zone. If I am writing in a notebook this means no tv on in the background and somewhere comfortable by myself where I can collect my thoughts.",[651,56615,56616],{},"If I am writing in a text editor application that the document needs to be the only thing open. This is especially true when I am working in the browser. If there are any tabs open or the lure of something else is right there I might be pulled in another direction.",[5909,56618,43883],{"id":43882},[651,56620,56621],{},"I really like Markdown because it is the easiest format that can transfer between platforms. I remember clearly when I started writing in Markdown and for the life of me, I couldn't understand why people liked it. Now, I couldn't imagine writing in any other format. For me, I am more productive writing in Markdown than anything else.",[5909,56623,56625],{"id":56624},"spelling-grammar-checking","Spelling & Grammar Checking",[651,56627,56628],{},"I feel like an important aspect of being productive is to know your strengths and weaknesses. If you can spend more of your time on your strengths and less time worrying about your weaknesses you can be more productive. I am pretty darn awful when it comes to spelling and grammar and this is where I need help. So until someone wants to volunteer to be my full-time editor I will rely on tools & services to help me out with this.",[4542,56630,56632],{"id":56631},"visual-studio-code-markdown","Visual Studio Code Markdown",[651,56634,56635],{},"For the last 6 months or so I have been writing documentation, tutorials, and exercises on a full-time basis in markdown using Visual Studio Code. For the most part, I really do enjoy it but there is one problem that is a deal breaker for me.",[651,56637,56638,56639,56643],{},"I am a huge fan of ",[812,56640,19087],{"href":56641,"rel":56642},"https://www.grammarly.com/",[816]," and use it for almost everything in the browser. This could be for writing an important email to the non-trivial social media status update. It is my go to service and I just can't imagine writing without it.",[651,56645,56646],{},"Well it turns out there is no way to use Grammarly in Visual Studio Code. So what I used to do is write an entire markdown document in visual studio code and the copy and paste the text content into a Google Document or Grammarly has an editor you can use.",[651,56648,56649],{},"As you can imagine this isn't very efficient and I got really tired of doing this. I have reached out to Grammarly on Twitter on a number of occasions asking them to please add support to Visual Studio Code. I am still hoping that this becomes a reality one day but until then I was on the hunt to find another process.",[651,56651,56652],{},"Another component of this that is not a deal breaker but it just bothers me from time to time is the markdown preview window. I don't like having my Markdown file open and the need for a preview window to see what the result looks like.",[651,56654,56655],{},[660,56656],{"alt":674,"src":56657},"/images/blog/2019/04/25/2019-04-25_09-37-18-fa09bb0d-9460-41fc-95ce-dbe3222c21b4.png",[4542,56659,56661],{"id":56660},"notion","Notion",[651,56663,56664,56668],{},[812,56665,56661],{"href":56666,"rel":56667},"https://www.notion.so",[816]," was one of those tools that I kept hearing about but I just didn't get it. Now granted they have made some major improvements over the last year but I remember trying it a number of times and saying I just don't get it.",[651,56670,56671],{},"The reason I kept trying it is that I kept hearing about it on podcasts and Twitter and I kept asking myself what do they know that I don't. Well, it turns out that this is one of those tools you can't really appreciate playing around with for just 5 min.",[651,56673,56674],{},"After I spent a little time over a weekend setting up Notion I really started to see the power behind it. This was a replacement for a number of tools I used and became a one-stop shop for everything I needed. Below is a screenshot of my personal Notion workspace. If you're interested in how I use Notion for almost everything let me know and I will put together a post or video walking you through how I use it.",[651,56676,56677],{},[660,56678],{"alt":674,"src":56679},"/images/blog/2019/04/25/2019-04-25_08-22-10-45f2e734-1c6b-4f79-963e-b560d0606e9b.png",[651,56681,56682,56683,56688],{},"If you're new to Notion I would also suggest checking out ",[812,56684,56687],{"href":56685,"rel":56686},"https://www.notion.so/181e961aeb5c4ee6915307c0dfd5156d",[816],"their new template gallery",". This is a great place to get some ideas and templates for your own workspace. I think most of the battle with the platform is understanding what to use it for and then how to set it up.",[5909,56690,56692],{"id":56691},"blogging-on-notion","Blogging on Notion",[651,56694,56695],{},"The one feature that I absolutely love about Notion is that you can use 1 set of data and have multiple views for it. If you're not quite sure what that means, that's ok I am going to explain that now.",[651,56697,56698],{},"I have set up a kanban board for blog posts and this is where I have a view into all of my ideas, what's up next, what I am working on now and what has been completed. Having a place where you can quickly add an idea for a blog post with some notes, images or sample code is crucial for my writing process.",[651,56700,56701],{},[660,56702],{"alt":674,"src":56703},"/images/blog/2019/04/25/2019-04-25_08-25-43-599d95ee-f20d-478c-9f84-41fcca921b54.png",[651,56705,56706],{},"I did something like this in Asana but over there I had to create a new content calendar to see what would be posted and when.",[651,56708,56709],{},"In Notion, you can set up some properties for each new post, which essentially is just a new page. This allows me to define a publish date for the post for when I expect this article to go live.",[651,56711,56712],{},[660,56713],{"alt":674,"src":56714},"/images/blog/2019/04/25/2019-04-25_08-28-37-4a42f7e2-b6de-4dd3-bfa1-ecbc46a972d5.png",[651,56716,56717],{},"So far nothing new but he is where the real power of Notion comes into play. You can create a different view of the same set of data. So now I can create a new calendar view for by blog posts and based on the published date I can get a calendar view of all my blog posts. This really helps me stay on track and let's me know what I have published and when.",[651,56719,56720],{},[660,56721],{"alt":674,"src":56722},"/images/blog/2019/04/25/2019-04-25_08-30-45-9f7af7e4-476e-4777-a79c-76197151e32a.png",[651,56724,56725],{},"I also like using this view to see if I have been posting about a certain topic too frequently. I try and break my posts up so it's not focused on a single topic and this really helps out.",[5909,56727,56729],{"id":56728},"markdown-block-editor","Markdown & Block Editor",[651,56731,56732],{},"I really enjoy the writing process in Notion. Everything you do is just a block so when you start writing it's a block of text. If you hit enter and you know you're going to add a new headline (or any block type for that matter) you just type a forward slash (/) and you are given a list of block types. This is just a sample of the types of blocks you can use when creating a page in Notion.",[5316,56734,56735,56738,56741,56744,56747,56750,56753,56756,56759,56762,56765,56768,56771,56774],{},[5332,56736,56737],{},"Text",[5332,56739,56740],{},"Page",[5332,56742,56743],{},"To-do List",[5332,56745,56746],{},"Heading 1, 2 & 3",[5332,56748,56749],{},"Bulleted, Numbered & Toggle List",[5332,56751,56752],{},"Quote",[5332,56754,56755],{},"Link to Page",[5332,56757,56758],{},"Mention a Person or Page",[5332,56760,56761],{},"Date or Reminder",[5332,56763,56764],{},"Database (A large list of blocks here)",[5332,56766,56767],{},"Images, Bookmarks, video, audio",[5332,56769,56770],{},"Code",[5332,56772,56773],{},"Files",[5332,56775,56776],{},"Embeds (Things like tweets, Gists, Google Maps & More)",[651,56778,56779],{},"Most of my blog posts are text, images and code. So I really appreciate that working with these content types are so easy. Here is a code block from a previous blog post. I enjoy the nice code formatting out of the box and the ability to determine what langauge that code is.",[651,56781,56782],{},[660,56783],{"alt":674,"src":56784},"/images/blog/2019/04/25/2019-04-25_08-36-25-5ba41d92-9cfd-460c-9bb8-cec0acff8574.png",[651,56786,56787],{},"The other really nice part of the writing process I enjoy is that because it's web-based I can do it from any computer or phone. Yes, I use my phone sometimes to write something I don't want to forget or adding a link to the article so I remember to reference it later.",[5909,56789,56791],{"id":56790},"grammarly-support","Grammarly Support",[651,56793,56794],{},"Remember when I said that Grammarly support was a must? Well the good news is that it works, sorta. When I first started writing in Notion it didn't work at all. The Chrome plugin would kick in and show me suggestions but whenever I tried to apply that suggestion something in Notion would kick in and revert it back to what it was. The other problem is that it sometimes takes awhile for the plugin to notice problems.",[651,56796,56797],{},"The weird thing is that just this week it sorta started working for me. Sometimes I have to hard refresh the page but when I do get Grammarly suggestions now I can hover over them and accept the change. I reached out to Notion and asked them if they fixed anything and they did not.",[651,56799,56800],{},"While this sorta works it's not working as I would hope and this is something I absolutely need going forward so I hope they are working on this.",[5909,56802,56804],{"id":56803},"export-to-markdown","Export to Markdown",[651,56806,56807],{},"When my blog post is ready to go I just go to the upper right hand corner, click on the 3 little dots and export as Markdown. It can be confusing as there is also an option to Export All as Markdown. What that feature is for is in the situation where you have pages embedded inside of pages.",[651,56809,56810],{},[660,56811],{"alt":674,"src":56812},"/images/blog/2019/04/25/2019-04-25_08-56-24-1d34f10b-cdc5-428b-9f67-5340472771e5.png",[651,56814,56815,56816,56821],{},"What I really appreciate about the export is that I have all of my code and images in Markdown and I am ready to post. From there I will generate a new blog post using a custom generator ",[812,56817,56820],{"href":56818,"rel":56819},"https://www.danvega.dev/blog/2019/04/23/gridsome-blog-post-generator",[816],"I wrote and blogged about here",". Then I copy all of my images into my new post folder and all of the markdown into the new markdown file my generator created.",[5909,56823,56825],{"id":56824},"whats-missing","What's Missing",[651,56827,56828],{},"This process is far from perfect and there are some things that don't work well and some things I wish they would add. As I mentioned earlier the Grammarly support is a must for me and I hope they can fix that up a bit. The export as Markdown works but it isn't perfect.",[651,56830,56831],{},"The code blocks don't export in Markdown format so there are no backticks with the language definition around them. This means that I have to go through and manually fix these before I post which is a bit tedious and annoying.",[651,56833,56834],{},"The other part of the export process is that when I create a table it doesn't export it as a simple table in Markdown format. It thinks of the table as a database and therefore exports it as another page and this doesn't work for me.",[5909,56836,56838],{"id":56837},"wish-list","Wish List",[651,56840,56841],{},"Notion asked me on Twitter what would be the top 5 things on my wish list would be and here is how I responded.",[651,56843,56844],{},[660,56845],{"alt":674,"src":56846},"/images/blog/2019/04/25/2019-04-25_08-47-50-93f60af8-b691-41ce-ab49-e30761f57c9c.png",[651,56848,56849,56850,56854],{},"A few of those I have already talked about but I want to talk quickly about the last 2. I am a huge fan of mind maps as a teaching tool. I ",[812,56851,56853],{"href":41021,"rel":56852},[816],"created this map"," of what to learn as a Java Developer in 2018 and it was really well received.",[651,56856,56857],{},[660,56858],{"alt":674,"src":56859},"/images/blog/2019/04/25/2019-04-25_09-14-56-c49f496f-084f-46e1-b4dc-05484696df21.png",[651,56861,56862],{},"I would love the ability to create Mind Maps within Notion and then include them in my posts. I'm still not sure if that would mean Notion creating a whole new tool for Mind Mapping or just embedding something like Coggle.",[651,56864,56865],{},"The other feature I would love to see is a drawing block. If I had an iPad Pro with a pencil and I could quickly sketch something out or add a drawing to a post I think that would be super cool. I am not the greatest artist but I could see this coming in super handy sometimes.",[4542,56867,56869],{"id":56868},"cross-posting-on-devto","Cross posting on dev.to",[651,56871,56872,56873,56877],{},"The final piece of the blogging process is to cross-post my article on ",[812,56874,56875],{"href":56875,"rel":56876},"https://www.dev.to",[816],". If you're not familiar with DEV it's a great community for developers. I found that when I posted my articles on platforms like Reddit I got a lot of hate thrown at me for being a spammer. I mean my articles are meant to help others, what part of this process is spamming?",[651,56879,56880],{},"DEV allows me to not only post my articles but I get some really great feedback from the community. Everyone there is so nice and I have never had the engagement I do there on any other platform. I usually wait a day or 2 after it appears on my blog to post it there but when I do DEV allows me to add a canonical URL to the front matter for each post.",[651,56882,56883,56884,56889],{},"I also create all of my blog post cover images using the dimensions of 1000w x 492h because this is the size DEV would like them in. That size works out well for my blog as well but now I have a cover image for both platforms. If you haven't checked out ",[812,56885,56888],{"href":56886,"rel":56887},"https://dev.to",[816],"DEV"," yet, I highly recommend doing so.",[651,56891,56892],{},"Because you can add custom properties to each post I have even added a DEV publish date so I know when this will go live there.",[651,56894,56895],{},[660,56896],{"alt":674,"src":56897},"/images/blog/2019/04/25/2019-04-25_09-42-53-41efac07-a9a3-48c2-8ac4-4c1151d89b1a.png",[4542,56899,9042],{"id":9041},[651,56901,56902],{},"So that is my blogging process which I am sure will change again at some point but for now, I am really happy. I would love to hear from you on what your process is and any tips and tricks you are willing to share. Are you using Notion for anything, what are your thoughts on it as a tool and or a blogging platform? Until next time...",[651,56904,41105,56905,41109],{},[41107,56906],{},{"title":674,"searchDepth":790,"depth":790,"links":56908},[56909,56914,56915,56923,56924],{"id":56590,"depth":790,"text":56591,"children":56910},[56911,56912,56913],{"id":56610,"depth":892,"text":56602},{"id":43882,"depth":892,"text":43883},{"id":56624,"depth":892,"text":56625},{"id":56631,"depth":790,"text":56632},{"id":56660,"depth":790,"text":56661,"children":56916},[56917,56918,56919,56920,56921,56922],{"id":56691,"depth":892,"text":56692},{"id":56728,"depth":892,"text":56729},{"id":56790,"depth":892,"text":56791},{"id":56803,"depth":892,"text":56804},{"id":56824,"depth":892,"text":56825},{"id":56837,"depth":892,"text":56838},{"id":56868,"depth":790,"text":56869},{"id":9041,"depth":790,"text":9042},"In this article I will outline for you the tools and process I use to create a new blog post",{"slug":56927,"date":56928,"published":797,"author":798,"tags":56929,"cover":56930},"my-new-blog-post-workflow","2019-04-25T13:53:35.054Z",[19164],"super-snapper-zIwAchjDirM-unsplash.jpg",{"title":501,"description":56925},"blog/2019/04/25/my-new-blog-post-workflow","rGhNBfynUte-5TTnvEySqKVKtomrvTjeQ72PUK7kXD4",{"id":56935,"title":498,"body":56936,"description":58617,"extension":793,"meta":58618,"navigation":797,"path":499,"seo":58623,"stem":58624,"__hash__":58625},"content/blog/2019/04/30/up-and-running-with-vue.md",{"type":648,"value":56937,"toc":58596},[56938,56945,56948,56951,56955,56958,56961,56972,56975,56978,56983,56991,56996,57003,57008,57015,57020,57027,57032,57039,57044,57047,57052,57075,57080,57103,57106,57109,57181,57184,57187,57192,57195,57198,57201,57205,57208,57211,57214,57218,57224,57358,57361,57402,57409,57412,57435,57438,57487,57490,57567,57570,57623,57631,57634,57639,57652,57659,57810,57813,57816,57819,57822,57826,57829,57845,57855,57860,57863,57877,57883,57888,57895,58122,58125,58138,58144,58149,58152,58156,58159,58171,58179,58184,58187,58192,58195,58200,58204,58211,58214,58219,58222,58227,58231,58449,58452,58458,58463,58465,58473,58496,58498,58510,58514,58535,58539,58542,58550,58576,58579,58581,58589,58593],[651,56939,56940,56941,56944],{},"At this point, you have most likely heard about the JavaScript framework called ",[812,56942,43866],{"href":43587,"rel":56943},[816],". If it's been on your list as something to try out but you weren't quite sure where to start I am here to help. In this article, I am going to tell you why I love Vue and give you 4 different ways you can write your first application.",[651,56946,56947],{},"Normally I shy away from giving someone new to a framework too many options for doing the same thing but in this case, I think it's important. You might be someone who enjoys working with the command line or you might be someone who prefers working within a GUI. There are also ways to start writing Vue applications without a CLI by just adding a script tag to an HTML page. Finally, you might not want to bother setting up a development environment and instead dive right in and try it now. No matter what option suits you best I got you covered.",[651,56949,56950],{},"As you move through this article I will leave you with some resources and when you reach the end I will leave you with what I think you should focus on next. This is not a deep dive into what Vue is or how to use the each of the options I give you so please keep that in mind.",[4542,56952,56954],{"id":56953},"low-barrier-of-entry","Low Barrier of entry",[651,56956,56957],{},"For me, the real power of Vue is just how welcoming of a framework it is. The core team has done an amazing job of making sure that developers of all skill levels can jump on in and be productive. As long as you are comfortable with HTML, CSS & JavaScript you can get started building some pretty powerful applications right now.",[651,56959,56960],{},"I also believe that of the 3 top frameworks (Angular, React & Vue) Vue has the lowest barrier to entry. There are a few reasons for this but for me, it comes down to 3 things",[5316,56962,56963,56966,56969],{},[5332,56964,56965],{},"Great Documentation",[5332,56967,56968],{},"Awesome Community",[5332,56970,56971],{},"Progressive Framework",[5909,56973,56965],{"id":56974},"great-documentation",[651,56976,56977],{},"When you ask most developers who enjoy using Vue I think one of the first things they will tell you is just how great the documentation is. If you head over to the Vue.js website you will find the docs broken down into a few sections.",[651,56979,56980],{},[2939,56981,56982],{},"Guide",[651,56984,56985,56986,56990],{},"In the ",[812,56987,56982],{"href":56988,"rel":56989},"https://vuejs.org/v2/guide/",[816],", you will find everything you need to get started with Vue.js. It's broken down into a nice logical flow of concepts you will need to learn to start building component-based applications.",[651,56992,56993],{},[2939,56994,56995],{},"API",[651,56997,56985,56998,57002],{},[812,56999,56995],{"href":57000,"rel":57001},"https://vuejs.org/v2/api/",[816],", you will find everything you need to begin working with the Vue API. If you ever see a method in Vue and you're not quite sure what it does, this should give you the answers.",[651,57004,57005],{},[2939,57006,57007],{},"Style Guide",[651,57009,56985,57010,57014],{},[812,57011,57007],{"href":57012,"rel":57013},"https://vuejs.org/v2/style-guide/",[816],", you will find some awesome recommendations for avoided errors and anti-patterns. It's important that these aren't rules but more of a guide. After you get comfortable writing some applications I would start to walk through as I have found it to be a very valuable resource.",[651,57016,57017],{},[2939,57018,57019],{},"Examples",[651,57021,56985,57022,57026],{},[812,57023,57019],{"href":57024,"rel":57025},"https://vuejs.org/v2/examples/",[816],", you will find sample applications built with Vue. I would save this resource for later and focus on building simple components but when you're ready to see how full applications are composed check it out.",[651,57028,57029],{},[2939,57030,57031],{},"Cookbook",[651,57033,56985,57034,57038],{},[812,57035,57031],{"href":57036,"rel":57037},"https://vuejs.org/v2/cookbook/",[816],", you will find answers to common questions you might have. This could be things like \"How do I do form validation\" or \"How can I unit test a component\". This again is a valuable resource but I would save it for when you start asking these types of questions.",[651,57040,57041],{},[2939,57042,57043],{},"Tooling & Core Libraries",[651,57045,57046],{},"There are also great resources and documentation around tooling and core libraries. I am not going to far into these because I want to dive in and start writing some code. I do want to leave these as resources and I suggest giving them a look over so you know what is available to you.",[651,57048,57049],{},[2939,57050,57051],{},"Tooling",[5316,57053,57054,57061,57068],{},[5332,57055,57056],{},[812,57057,57060],{"href":57058,"rel":57059},"https://github.com/vuejs/vue-devtools",[816],"DevTools",[5332,57062,57063],{},[812,57064,57067],{"href":57065,"rel":57066},"https://cli.vuejs.org/",[816],"Vue CLI",[5332,57069,57070],{},[812,57071,57074],{"href":57072,"rel":57073},"https://vue-loader.vuejs.org/",[816],"Vue Loader",[651,57076,57077],{},[2939,57078,57079],{},"Core Libraries",[5316,57081,57082,57089,57096],{},[5332,57083,57084],{},[812,57085,57088],{"href":57086,"rel":57087},"https://router.vuejs.org/",[816],"Vue Router",[5332,57090,57091],{},[812,57092,57095],{"href":57093,"rel":57094},"https://vuex.vuejs.org/",[816],"Vuex",[5332,57097,57098],{},[812,57099,57102],{"href":57100,"rel":57101},"https://ssr.vuejs.org/",[816],"Vue Server Renderer",[5909,57104,56968],{"id":57105},"awesome-community",[651,57107,57108],{},"I feel like I have been a part of some amazing communities in my development career and the Vue community is no different. Everyone is very welcoming, friendly and beyond helpful. An easy way to see what others in the community are building or excited about is to follow some of them on Twitter. Here are 10 people I think you should start following today.",[5316,57110,57111,57118,57125,57132,57139,57146,57153,57160,57167,57174],{},[5332,57112,57113],{},[812,57114,57117],{"href":57115,"rel":57116},"https://twitter.com/youyuxi",[816],"Evan You",[5332,57119,57120],{},[812,57121,57124],{"href":57122,"rel":57123},"https://twitter.com/chrisvfritz",[816],"Chris Fritz",[5332,57126,57127],{},[812,57128,57131],{"href":57129,"rel":57130},"https://twitter.com/sarah_edo",[816],"Sarah Drasner",[5332,57133,57134],{},[812,57135,57138],{"href":57136,"rel":57137},"https://twitter.com/DamianDulisz",[816],"Damian Sulisz",[5332,57140,57141],{},[812,57142,57145],{"href":57143,"rel":57144},"https://twitter.com/shortdiv",[816],"Divya Sasidharan",[5332,57147,57148],{},[812,57149,57152],{"href":57150,"rel":57151},"https://twitter.com/Akryum",[816],"Guillaume Chau",[5332,57154,57155],{},[812,57156,57159],{"href":57157,"rel":57158},"https://twitter.com/bencodezen",[816],"Ben Hong",[5332,57161,57162],{},[812,57163,57166],{"href":57164,"rel":57165},"https://twitter.com/Atinux",[816],"Sebastien Chopin",[5332,57168,57169],{},[812,57170,57173],{"href":57171,"rel":57172},"https://twitter.com/N_Tepluhina",[816],"Natalia Tepluhina",[5332,57175,57176],{},[812,57177,57180],{"href":57178,"rel":57179},"https://twitter.com/EddYerburgh",[816],"Edd Yerburgh",[5909,57182,56971],{"id":57183},"progressive-framework",[651,57185,57186],{},"If you visit the home page for Vue.js you will see it is known as \"The Progressive Framework\" and while it might seem like marketing mumbo jumbo at first, you quickly realize that it isn't. Let's start with the definition of progressive",[1004,57188,57189],{},[651,57190,57191],{},"happening or developing gradually or in stages; proceeding step by step.",[651,57193,57194],{},"As you will see in just a minute you can take a very gradual approach to building Vue applications. If you have an existing project that you would like to add Vue too, no problem. You can start by dropping in a script tag and writing a few lines of code.",[651,57196,57197],{},"If you want to use a CLI to scaffold a new project out based on features you might need, also not a problem. As your application starts to scale and you need to add things like routing or state management it's really easy to do.",[651,57199,57200],{},"We talked about it earlier but Vue is an approachable framework. If you already know HTML, CSS & JavaScript you can get started today. If this is you let's jump on in and start writing some",[4542,57202,57204],{"id":57203},"vue-js-script","Vue JS Script",[651,57206,57207],{},"The first option we are going to look at is using the script tag. If you have worked with JavaScript in the past this is nothing new to you. You have an HTML page and you need to add some functionality to the page so you drop in a script tag. This isn't just for learning Vue because it has some real practical uses.",[651,57209,57210],{},"It's usually really hard to introduce new tools, libraries and frameworks at work. If you really like working with Vue and went to your boss and asked if you could rewrite the whole system in Vue he or she would almost immediately shoot your idea down.",[651,57212,57213],{},"If instead you went to them and asked to introduce Vue on this new page you were building, that is a much easier sell. In this example, we are going to build a pretty arbitrary application but it's good for explaining a couple of different concepts.",[5909,57215,57217],{"id":57216},"hello-vue","Hello, Vue!",[651,57219,57220,57221,664],{},"In this application you are going to display the text \"Hello, Vue!\" and the current date and time below it. You will then write some logic to update the current date/time so that the user will see the time change in real time. You are going to start off by creating a directory and adding the following to a new page ",[676,57222,57223],{},"index.html",[669,57225,57227],{"className":4496,"code":57226,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n \u003Chead>\n \u003Cmeta charset=\"UTF-8\" />\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n \u003Ctitle>Hello, Vue.js\u003C/title>\n \u003C/head>\n \u003Cbody>\u003C/body>\n\u003C/html>\n",[676,57228,57229,57239,57254,57262,57276,57296,57317,57330,57338,57350],{"__ignoreMap":674},[679,57230,57231,57233,57235,57237],{"class":681,"line":682},[679,57232,11904],{"class":693},[679,57234,11907],{"class":4508},[679,57236,11910],{"class":880},[679,57238,4519],{"class":693},[679,57240,57241,57243,57245,57247,57249,57252],{"class":681,"line":790},[679,57242,4505],{"class":693},[679,57244,4498],{"class":4508},[679,57246,18806],{"class":880},[679,57248,686],{"class":693},[679,57250,57251],{"class":689},"\"en\"",[679,57253,4519],{"class":693},[679,57255,57256,57258,57260],{"class":681,"line":892},[679,57257,11738],{"class":693},[679,57259,11741],{"class":4508},[679,57261,4519],{"class":693},[679,57263,57264,57266,57268,57270,57272,57274],{"class":681,"line":901},[679,57265,4524],{"class":693},[679,57267,11968],{"class":4508},[679,57269,11971],{"class":880},[679,57271,686],{"class":693},[679,57273,47958],{"class":689},[679,57275,5387],{"class":693},[679,57277,57278,57280,57282,57284,57286,57288,57290,57292,57294],{"class":681,"line":909},[679,57279,4524],{"class":693},[679,57281,11968],{"class":4508},[679,57283,5283],{"class":880},[679,57285,686],{"class":693},[679,57287,12015],{"class":689},[679,57289,11995],{"class":880},[679,57291,686],{"class":693},[679,57293,48042],{"class":689},[679,57295,5387],{"class":693},[679,57297,57298,57300,57302,57304,57306,57308,57310,57312,57315],{"class":681,"line":918},[679,57299,4524],{"class":693},[679,57301,11968],{"class":4508},[679,57303,11987],{"class":880},[679,57305,686],{"class":693},[679,57307,11992],{"class":689},[679,57309,11995],{"class":880},[679,57311,686],{"class":693},[679,57313,57314],{"class":689},"\"ie=edge\"",[679,57316,5387],{"class":693},[679,57318,57319,57321,57323,57326,57328],{"class":681,"line":935},[679,57320,4524],{"class":693},[679,57322,11750],{"class":4508},[679,57324,57325],{"class":693},">Hello, Vue.js\u003C/",[679,57327,11750],{"class":4508},[679,57329,4519],{"class":693},[679,57331,57332,57334,57336],{"class":681,"line":944},[679,57333,11840],{"class":693},[679,57335,11741],{"class":4508},[679,57337,4519],{"class":693},[679,57339,57340,57342,57344,57346,57348],{"class":681,"line":959},[679,57341,11738],{"class":693},[679,57343,3006],{"class":4508},[679,57345,4563],{"class":693},[679,57347,3006],{"class":4508},[679,57349,4519],{"class":693},[679,57351,57352,57354,57356],{"class":681,"line":964},[679,57353,4586],{"class":693},[679,57355,4498],{"class":4508},[679,57357,4519],{"class":693},[651,57359,57360],{},"Now that you have the basic part of your page setup it's time to add the Vue.js script. You can add the following lines of code right before the body closing tag.",[669,57362,57364],{"className":4496,"code":57363,"language":4498,"meta":674,"style":674},"\u003C!-- development version includes helpful console warnings -->\n\u003Cscript src=\"https://cdn.jsdelivr.net/npm/vue/dist/vue.js\">\u003C/script>\n\u003Cscript>\u003C/script>\n",[676,57365,57366,57371,57390],{"__ignoreMap":674},[679,57367,57368],{"class":681,"line":682},[679,57369,57370],{"class":1400},"\u003C!-- development version includes helpful console warnings -->\n",[679,57372,57373,57375,57377,57379,57381,57384,57386,57388],{"class":681,"line":790},[679,57374,4505],{"class":693},[679,57376,47668],{"class":4508},[679,57378,5361],{"class":880},[679,57380,686],{"class":693},[679,57382,57383],{"class":689},"\"https://cdn.jsdelivr.net/npm/vue/dist/vue.js\"",[679,57385,4563],{"class":693},[679,57387,47668],{"class":4508},[679,57389,4519],{"class":693},[679,57391,57392,57394,57396,57398,57400],{"class":681,"line":892},[679,57393,4505],{"class":693},[679,57395,47668],{"class":4508},[679,57397,4563],{"class":693},[679,57399,47668],{"class":4508},[679,57401,4519],{"class":693},[651,57403,57404,57405,664],{},"I left the comment in there because it's important to note this is the development version of the script. If you were going to put this in production you would want to use the production script. You can read more about this is the ",[812,57406,57408],{"href":56988,"rel":57407},[816],"Vue.js Guide Documentation",[651,57410,57411],{},"The first thing you need to is add a root element to mount your Vue instance to.",[669,57413,57415],{"className":4496,"code":57414,"language":4498,"meta":674,"style":674},"\u003Cdiv id=\"app\">\u003C/div>\n",[676,57416,57417],{"__ignoreMap":674},[679,57418,57419,57421,57423,57425,57427,57429,57431,57433],{"class":681,"line":682},[679,57420,4505],{"class":693},[679,57422,4509],{"class":4508},[679,57424,5578],{"class":880},[679,57426,686],{"class":693},[679,57428,28626],{"class":689},[679,57430,4563],{"class":693},[679,57432,4509],{"class":4508},[679,57434,4519],{"class":693},[651,57436,57437],{},"And then create a new Vue instance and tell Vue what your root element is.",[669,57439,57441],{"className":4496,"code":57440,"language":4498,"meta":674,"style":674},"\u003Cscript>\n const app = new Vue({\n el: \"#app\"\n });\n\u003C/script>\n",[676,57442,57443,57451,57467,57475,57479],{"__ignoreMap":674},[679,57444,57445,57447,57449],{"class":681,"line":682},[679,57446,4505],{"class":693},[679,57448,47668],{"class":4508},[679,57450,4519],{"class":693},[679,57452,57453,57455,57458,57460,57462,57465],{"class":681,"line":790},[679,57454,46903],{"class":685},[679,57456,57457],{"class":931}," app",[679,57459,6883],{"class":685},[679,57461,2054],{"class":685},[679,57463,57464],{"class":880}," Vue",[679,57466,21218],{"class":693},[679,57468,57469,57472],{"class":681,"line":892},[679,57470,57471],{"class":693}," el: ",[679,57473,57474],{"class":689},"\"#app\"\n",[679,57476,57477],{"class":681,"line":901},[679,57478,56103],{"class":693},[679,57480,57481,57483,57485],{"class":681,"line":909},[679,57482,4586],{"class":693},[679,57484,47668],{"class":4508},[679,57486,4519],{"class":693},[651,57488,57489],{},"What you want to do now is to create a couple of variables that hold values that you can then use to display information on the page. You do this by declaring properties on the data object.",[669,57491,57493],{"className":4496,"code":57492,"language":4498,"meta":674,"style":674},"\u003Cscript>\n const app = new Vue({\n el: \"#app\",\n data: {\n message: \"Hello Vue!\",\n now: new Date()\n }\n });\n\u003C/script>\n",[676,57494,57495,57503,57517,57526,57531,57540,57551,57555,57559],{"__ignoreMap":674},[679,57496,57497,57499,57501],{"class":681,"line":682},[679,57498,4505],{"class":693},[679,57500,47668],{"class":4508},[679,57502,4519],{"class":693},[679,57504,57505,57507,57509,57511,57513,57515],{"class":681,"line":790},[679,57506,46903],{"class":685},[679,57508,57457],{"class":931},[679,57510,6883],{"class":685},[679,57512,2054],{"class":685},[679,57514,57464],{"class":880},[679,57516,21218],{"class":693},[679,57518,57519,57521,57524],{"class":681,"line":892},[679,57520,57471],{"class":693},[679,57522,57523],{"class":689},"\"#app\"",[679,57525,12083],{"class":693},[679,57527,57528],{"class":681,"line":901},[679,57529,57530],{"class":693}," data: {\n",[679,57532,57533,57535,57538],{"class":681,"line":909},[679,57534,55525],{"class":693},[679,57536,57537],{"class":689},"\"Hello Vue!\"",[679,57539,12083],{"class":693},[679,57541,57542,57545,57547,57549],{"class":681,"line":918},[679,57543,57544],{"class":693}," now: ",[679,57546,8930],{"class":685},[679,57548,17511],{"class":880},[679,57550,17545],{"class":693},[679,57552,57553],{"class":681,"line":935},[679,57554,985],{"class":693},[679,57556,57557],{"class":681,"line":944},[679,57558,56103],{"class":693},[679,57560,57561,57563,57565],{"class":681,"line":959},[679,57562,4586],{"class":693},[679,57564,47668],{"class":4508},[679,57566,4519],{"class":693},[651,57568,57569],{},"Now that your data is setup Vue gives us a really clean way to display that data on the page.",[669,57571,57573],{"className":4496,"code":57572,"language":4498,"meta":674,"style":674},"\u003Cdiv id=\"app\">\n \u003Ch1>{{ message }}\u003C/h1>\n \u003Cp>{{ now }}\u003C/p>\n\u003C/div>\n",[676,57574,57575,57589,57602,57615],{"__ignoreMap":674},[679,57576,57577,57579,57581,57583,57585,57587],{"class":681,"line":682},[679,57578,4505],{"class":693},[679,57580,4509],{"class":4508},[679,57582,5578],{"class":880},[679,57584,686],{"class":693},[679,57586,28626],{"class":689},[679,57588,4519],{"class":693},[679,57590,57591,57593,57595,57598,57600],{"class":681,"line":790},[679,57592,11738],{"class":693},[679,57594,11859],{"class":4508},[679,57596,57597],{"class":693},">{{ message }}\u003C/",[679,57599,11859],{"class":4508},[679,57601,4519],{"class":693},[679,57603,57604,57606,57608,57611,57613],{"class":681,"line":892},[679,57605,11738],{"class":693},[679,57607,651],{"class":4508},[679,57609,57610],{"class":693},">{{ now }}\u003C/",[679,57612,651],{"class":4508},[679,57614,4519],{"class":693},[679,57616,57617,57619,57621],{"class":681,"line":901},[679,57618,4586],{"class":693},[679,57620,4509],{"class":4508},[679,57622,4519],{"class":693},[651,57624,57625,57626,57630],{},"Give yourself a big pat on the back because just created your first Vue application. If you run this application you should get the heading \"Hello, Vue!\" and the current date/time below it. I am using Visual Studio Code and the ",[812,57627,57629],{"href":54696,"rel":57628},[816],"Live Server extension"," to run mine.",[651,57632,57633],{},"This doesn't look like much but Vue is actually doing a lot under the hood for you. The data and the DOM are now linked, and everything is now reactive. How do we know? Open your browser’s JavaScript console (right now, on this page) and set app.message to a different value. You should see the rendered example above update accordingly.",[651,57635,57636],{},[660,57637],{"alt":674,"src":57638},"/images/blog/2019/04/30/2019-04-30_12-12-13-88a57b30-f402-4444-befb-262701bb347c.png",[651,57640,57641,57642,2797,57645,2797,57648,57651],{},"Vue also gives you the ability to \"hook\" into the lifecycle of a component. This means that you can listen for events like ",[676,57643,57644],{},"created",[676,57646,57647],{},"mounted",[676,57649,57650],{},"destroyed"," and more. I don't want to get too much further into this because this article is meant to show you different ways to create a Vue application, not everything else. I do want to finish our application though.",[651,57653,57654,57655,57658],{},"The following code will update the variable now every second. When Vue is mounted you run a function every second using the ",[676,57656,57657],{},"setInterval"," method. Just like adding your own data on the Vue instance you can create methods by adding them to the methods object.",[669,57660,57662],{"className":4496,"code":57661,"language":4498,"meta":674,"style":674},"\u003Cscript>\n const app = new Vue({\n el: \"#app\",\n data: {\n message: \"Hello Vue!\",\n now: new Date()\n },\n methods: {\n updateDate() {\n this.now = new Date();\n }\n },\n mounted() {\n setInterval(() => {\n this.updateDate();\n }, 100);\n }\n });\n\u003C/script>\n",[676,57663,57664,57672,57686,57694,57698,57706,57716,57720,57725,57732,57747,57751,57755,57762,57773,57784,57794,57798,57802],{"__ignoreMap":674},[679,57665,57666,57668,57670],{"class":681,"line":682},[679,57667,4505],{"class":693},[679,57669,47668],{"class":4508},[679,57671,4519],{"class":693},[679,57673,57674,57676,57678,57680,57682,57684],{"class":681,"line":790},[679,57675,46903],{"class":685},[679,57677,57457],{"class":931},[679,57679,6883],{"class":685},[679,57681,2054],{"class":685},[679,57683,57464],{"class":880},[679,57685,21218],{"class":693},[679,57687,57688,57690,57692],{"class":681,"line":892},[679,57689,57471],{"class":693},[679,57691,57523],{"class":689},[679,57693,12083],{"class":693},[679,57695,57696],{"class":681,"line":901},[679,57697,57530],{"class":693},[679,57699,57700,57702,57704],{"class":681,"line":909},[679,57701,55525],{"class":693},[679,57703,57537],{"class":689},[679,57705,12083],{"class":693},[679,57707,57708,57710,57712,57714],{"class":681,"line":918},[679,57709,57544],{"class":693},[679,57711,8930],{"class":685},[679,57713,17511],{"class":880},[679,57715,17545],{"class":693},[679,57717,57718],{"class":681,"line":935},[679,57719,28763],{"class":693},[679,57721,57722],{"class":681,"line":944},[679,57723,57724],{"class":693}," methods: {\n",[679,57726,57727,57730],{"class":681,"line":959},[679,57728,57729],{"class":880}," updateDate",[679,57731,2667],{"class":693},[679,57733,57734,57736,57739,57741,57743,57745],{"class":681,"line":964},[679,57735,7862],{"class":931},[679,57737,57738],{"class":693},".now ",[679,57740,686],{"class":685},[679,57742,2054],{"class":685},[679,57744,17511],{"class":880},[679,57746,9317],{"class":693},[679,57748,57749],{"class":681,"line":977},[679,57750,11804],{"class":693},[679,57752,57753],{"class":681,"line":982},[679,57754,28763],{"class":693},[679,57756,57757,57760],{"class":681,"line":988},[679,57758,57759],{"class":880}," mounted",[679,57761,2667],{"class":693},[679,57763,57764,57767,57769,57771],{"class":681,"line":993},[679,57765,57766],{"class":880}," setInterval",[679,57768,55186],{"class":693},[679,57770,21350],{"class":685},[679,57772,884],{"class":693},[679,57774,57775,57777,57779,57782],{"class":681,"line":2129},[679,57776,7862],{"class":931},[679,57778,664],{"class":693},[679,57780,57781],{"class":880},"updateDate",[679,57783,9317],{"class":693},[679,57785,57786,57789,57792],{"class":681,"line":2140},[679,57787,57788],{"class":693}," }, ",[679,57790,57791],{"class":931},"100",[679,57793,1208],{"class":693},[679,57795,57796],{"class":681,"line":2145},[679,57797,985],{"class":693},[679,57799,57800],{"class":681,"line":2154},[679,57801,56103],{"class":693},[679,57803,57804,57806,57808],{"class":681,"line":2159},[679,57805,4586],{"class":693},[679,57807,47668],{"class":4508},[679,57809,4519],{"class":693},[651,57811,57812],{},"If you run the application now you should get the same display but every second the current date time is now updated. You didn't have to update the DOM manually because Vue has tied your data variables to the DOM for you and when they are updated, the DOM is updated.",[4542,57814,57067],{"id":57815},"vue-cli",[651,57817,57818],{},"While dropping the script tag on a page got us up and running quickly it doesn't scale all that well. In cases where you want to build full-blown Single Page Applications (SPA) and take advantage of the toolchain, the Vue CLI is a great way to get up and running.",[651,57820,57821],{},"If you're not a huge fan of doing everything on the command line don't worry, the same CLI tool that you install here has a UI version.",[5909,57823,57825],{"id":57824},"installing-the-vue-cli","Installing the Vue CLI",[651,57827,57828],{},"The first thing you need to do is install the Vue CLI. A requirement for this is having NodeJS and npm installed so if those are new to you take some time to install them and do a little reading on them to get a better understanding of what they are used for. To install the Vue CLI run the following command:",[669,57830,57832],{"className":5851,"code":57831,"language":5853,"meta":674,"style":674}," npm install -g @vue/cli\n",[676,57833,57834],{"__ignoreMap":674},[679,57835,57836,57839,57841,57843],{"class":681,"line":682},[679,57837,57838],{"class":880}," npm",[679,57840,24571],{"class":689},[679,57842,24574],{"class":931},[679,57844,42483],{"class":689},[651,57846,57847,57848,57850,57851,57854],{},"To ",[676,57849,5697],{}," a new application you can use the command ",[676,57852,57853],{},"vue create",". If you're not sure what commands are available you can run vue -h",[651,57856,57857],{},[660,57858],{"alt":674,"src":57859},"/images/blog/2019/04/30/2019-04-30_14-12-28-8dc58daf-3581-4eb4-a81c-bce59eb86341.png",[651,57861,57862],{},"The create command takes an app name so let's create a new vue application by running the following command:",[669,57864,57866],{"className":5851,"code":57865,"language":5853,"meta":674,"style":674},"vue create hello-vue\n",[676,57867,57868],{"__ignoreMap":674},[679,57869,57870,57872,57874],{"class":681,"line":682},[679,57871,44169],{"class":880},[679,57873,49468],{"class":689},[679,57875,57876],{"class":689}," hello-vue\n",[651,57878,57879,57880,57882],{},"This will create a new folder for you in the current directory called ",[676,57881,57216],{},". I will go through this in more detail in a later post but for now, just accept the defaults and a new application will be created for you.",[651,57884,57885],{},[660,57886],{"alt":674,"src":57887},"/images/blog/2019/04/30/2019-04-30_14-18-14-001b241f-0bda-4f21-b382-cc597bd97095.png",[651,57889,57890,57891,57894],{},"Open up ",[676,57892,57893],{},"src/components/HelloWorld.vue"," and replace everything in there with the following.",[669,57896,57898],{"className":50297,"code":57897,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"hello\">\n \u003Ch1>{{ msg }}\u003C/h1>\n \u003Cp>{{ now }}\u003C/p>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: \"Hello Vue\",\n props: {\n msg: String\n },\n data() {\n return {\n now: new Date()\n };\n },\n methods: {\n updateDate() {\n this.now = new Date();\n }\n },\n mounted() {\n setInterval(() => {\n this.updateDate();\n }, 100);\n }\n};\n\u003C/script>\n",[676,57899,57900,57908,57923,57936,57948,57956,57964,57968,57976,57984,57993,57998,58003,58007,58013,58019,58029,58033,58037,58041,58048,58062,58066,58070,58076,58087,58097,58106,58110,58114],{"__ignoreMap":674},[679,57901,57902,57904,57906],{"class":681,"line":682},[679,57903,4505],{"class":693},[679,57905,45982],{"class":4508},[679,57907,4519],{"class":693},[679,57909,57910,57912,57914,57916,57918,57921],{"class":681,"line":790},[679,57911,11738],{"class":693},[679,57913,4509],{"class":4508},[679,57915,4512],{"class":880},[679,57917,686],{"class":693},[679,57919,57920],{"class":689},"\"hello\"",[679,57922,4519],{"class":693},[679,57924,57925,57927,57929,57932,57934],{"class":681,"line":892},[679,57926,4524],{"class":693},[679,57928,11859],{"class":4508},[679,57930,57931],{"class":693},">{{ msg }}\u003C/",[679,57933,11859],{"class":4508},[679,57935,4519],{"class":693},[679,57937,57938,57940,57942,57944,57946],{"class":681,"line":901},[679,57939,4524],{"class":693},[679,57941,651],{"class":4508},[679,57943,57610],{"class":693},[679,57945,651],{"class":4508},[679,57947,4519],{"class":693},[679,57949,57950,57952,57954],{"class":681,"line":909},[679,57951,11840],{"class":693},[679,57953,4509],{"class":4508},[679,57955,4519],{"class":693},[679,57957,57958,57960,57962],{"class":681,"line":918},[679,57959,4586],{"class":693},[679,57961,45982],{"class":4508},[679,57963,4519],{"class":693},[679,57965,57966],{"class":681,"line":935},[679,57967,889],{"emptyLinePlaceholder":797},[679,57969,57970,57972,57974],{"class":681,"line":944},[679,57971,4505],{"class":693},[679,57973,47668],{"class":4508},[679,57975,4519],{"class":693},[679,57977,57978,57980,57982],{"class":681,"line":959},[679,57979,29245],{"class":685},[679,57981,50460],{"class":685},[679,57983,884],{"class":693},[679,57985,57986,57988,57991],{"class":681,"line":964},[679,57987,49970],{"class":693},[679,57989,57990],{"class":689},"\"Hello Vue\"",[679,57992,12083],{"class":693},[679,57994,57995],{"class":681,"line":977},[679,57996,57997],{"class":693}," props: {\n",[679,57999,58000],{"class":681,"line":982},[679,58001,58002],{"class":693}," msg: String\n",[679,58004,58005],{"class":681,"line":988},[679,58006,28483],{"class":693},[679,58008,58009,58011],{"class":681,"line":993},[679,58010,49979],{"class":880},[679,58012,2667],{"class":693},[679,58014,58015,58017],{"class":681,"line":2129},[679,58016,21478],{"class":685},[679,58018,884],{"class":693},[679,58020,58021,58023,58025,58027],{"class":681,"line":2140},[679,58022,57544],{"class":693},[679,58024,8930],{"class":685},[679,58026,17511],{"class":880},[679,58028,17545],{"class":693},[679,58030,58031],{"class":681,"line":2145},[679,58032,50000],{"class":693},[679,58034,58035],{"class":681,"line":2154},[679,58036,28483],{"class":693},[679,58038,58039],{"class":681,"line":2159},[679,58040,50502],{"class":693},[679,58042,58043,58046],{"class":681,"line":2164},[679,58044,58045],{"class":880}," updateDate",[679,58047,2667],{"class":693},[679,58049,58050,58052,58054,58056,58058,58060],{"class":681,"line":3134},[679,58051,50514],{"class":931},[679,58053,57738],{"class":693},[679,58055,686],{"class":685},[679,58057,2054],{"class":685},[679,58059,17511],{"class":880},[679,58061,9317],{"class":693},[679,58063,58064],{"class":681,"line":3139},[679,58065,985],{"class":693},[679,58067,58068],{"class":681,"line":3144},[679,58069,28483],{"class":693},[679,58071,58072,58074],{"class":681,"line":3149},[679,58073,54604],{"class":880},[679,58075,2667],{"class":693},[679,58077,58078,58081,58083,58085],{"class":681,"line":3169},[679,58079,58080],{"class":880}," setInterval",[679,58082,55186],{"class":693},[679,58084,21350],{"class":685},[679,58086,884],{"class":693},[679,58088,58089,58091,58093,58095],{"class":681,"line":3185},[679,58090,50514],{"class":931},[679,58092,664],{"class":693},[679,58094,57781],{"class":880},[679,58096,9317],{"class":693},[679,58098,58099,58102,58104],{"class":681,"line":3194},[679,58100,58101],{"class":693}," }, ",[679,58103,57791],{"class":931},[679,58105,1208],{"class":693},[679,58107,58108],{"class":681,"line":3199},[679,58109,21405],{"class":693},[679,58111,58112],{"class":681,"line":3212},[679,58113,44055],{"class":693},[679,58115,58116,58118,58120],{"class":681,"line":3217},[679,58117,4586],{"class":693},[679,58119,47668],{"class":4508},[679,58121,4519],{"class":693},[651,58123,58124],{},"From the command line (or the integrated terminal) run your app using the following comand",[669,58126,58128],{"className":5851,"code":58127,"language":5853,"meta":674,"style":674},"npm run serve\n",[676,58129,58130],{"__ignoreMap":674},[679,58131,58132,58134,58136],{"class":681,"line":682},[679,58133,24568],{"class":880},[679,58135,16486],{"class":689},[679,58137,27433],{"class":689},[651,58139,58140,58141],{},"When your application starts the command line should tell you where it's running but by default, it should be at ",[812,58142,32924],{"href":32924,"rel":58143},[816],[651,58145,58146],{},[660,58147],{"alt":674,"src":58148},"/images/blog/2019/04/30/2019-04-30_14-23-52-74842c90-97ed-4a17-8d54-7a0b3a201d95.png",[651,58150,58151],{},"That was just a quick walkthrough of using the CLI but I hope you saw just how easy it was to create a new application that has a strong infrastructure and is ready to scale with you as your needs grow.",[4542,58153,58155],{"id":58154},"vue-ui","Vue UI",[651,58157,58158],{},"With the Vue CLI installed you can use the UI by running the following command from the command line",[669,58160,58162],{"className":5851,"code":58161,"language":5853,"meta":674,"style":674},"vue ui\n",[676,58163,58164],{"__ignoreMap":674},[679,58165,58166,58168],{"class":681,"line":682},[679,58167,44169],{"class":880},[679,58169,58170],{"class":689}," ui\n",[651,58172,58173,58174,58178],{},"This will open a new application at ",[812,58175,58176],{"href":58176,"rel":58177},"http://localhost:8000/dashboard",[816]," that looks like this",[651,58180,58181],{},[660,58182],{"alt":674,"src":58183},"/images/blog/2019/04/30/2019-04-30_14-29-06-774dbcf1-d680-4083-94e7-049799559548.png",[651,58185,58186],{},"You can create a new project using the Vue UI and it will give you the same options the command line gives you.",[651,58188,58189],{},[660,58190],{"alt":674,"src":58191},"/images/blog/2019/04/30/2019-04-30_14-31-34-fe1e08dd-9393-47ac-b0e7-589e1b3771f5.png",[651,58193,58194],{},"And when your project is done you can run it right from the UI using the Project Tasks.",[651,58196,58197],{},[660,58198],{"alt":674,"src":58199},"/images/blog/2019/04/30/2019-04-30_14-34-11-eb6a9994-3a24-4f23-b6c2-e7280a48c788.png",[4542,58201,58203],{"id":58202},"codesandbox","CodeSandbox",[651,58205,58206,58207,664],{},"While those are all great options to get up and running with Vue they also require you to have something of a development environment setup. If you haven't had a chance to give it a try I am here to tell you about an awesome online code editor for the web called ",[812,58208,58203],{"href":58209,"rel":58210},"https://codesandbox.io/",[816],[651,58212,58213],{},"The first thing you need to do is to sign up for a CodeSandbox account by sign in using your Github account. Once you're signed in click on create a sandbox and from there you can select from a large variety of templates. You can select Vue from the popular or client templates tab.",[651,58215,58216],{},[660,58217],{"alt":674,"src":58218},"/images/blog/2019/04/30/2019-04-27_10-52-25-6a3c9422-66c2-4de4-be7a-a8a7551f2317.png",[651,58220,58221],{},"In just seconds you have a new Vue application up and running! In the left pane, you have all of your project files, your editor in the middle and a browser preview of the application running.",[651,58223,58224],{},[660,58225],{"alt":674,"src":58226},"/images/blog/2019/04/30/2019-04-27_10-54-12-15e1c74a-803a-4d5b-ac61-b22b9422405d.png",[651,58228,57890,58229,57894],{},[676,58230,57893],{},[669,58232,58233],{"className":50297,"code":57897,"language":44169,"meta":674,"style":674},[676,58234,58235,58243,58257,58269,58281,58289,58297,58301,58309,58317,58325,58329,58333,58337,58343,58349,58359,58363,58367,58371,58377,58391,58395,58399,58405,58415,58425,58433,58437,58441],{"__ignoreMap":674},[679,58236,58237,58239,58241],{"class":681,"line":682},[679,58238,4505],{"class":693},[679,58240,45982],{"class":4508},[679,58242,4519],{"class":693},[679,58244,58245,58247,58249,58251,58253,58255],{"class":681,"line":790},[679,58246,11738],{"class":693},[679,58248,4509],{"class":4508},[679,58250,4512],{"class":880},[679,58252,686],{"class":693},[679,58254,57920],{"class":689},[679,58256,4519],{"class":693},[679,58258,58259,58261,58263,58265,58267],{"class":681,"line":892},[679,58260,4524],{"class":693},[679,58262,11859],{"class":4508},[679,58264,57931],{"class":693},[679,58266,11859],{"class":4508},[679,58268,4519],{"class":693},[679,58270,58271,58273,58275,58277,58279],{"class":681,"line":901},[679,58272,4524],{"class":693},[679,58274,651],{"class":4508},[679,58276,57610],{"class":693},[679,58278,651],{"class":4508},[679,58280,4519],{"class":693},[679,58282,58283,58285,58287],{"class":681,"line":909},[679,58284,11840],{"class":693},[679,58286,4509],{"class":4508},[679,58288,4519],{"class":693},[679,58290,58291,58293,58295],{"class":681,"line":918},[679,58292,4586],{"class":693},[679,58294,45982],{"class":4508},[679,58296,4519],{"class":693},[679,58298,58299],{"class":681,"line":935},[679,58300,889],{"emptyLinePlaceholder":797},[679,58302,58303,58305,58307],{"class":681,"line":944},[679,58304,4505],{"class":693},[679,58306,47668],{"class":4508},[679,58308,4519],{"class":693},[679,58310,58311,58313,58315],{"class":681,"line":959},[679,58312,29245],{"class":685},[679,58314,50460],{"class":685},[679,58316,884],{"class":693},[679,58318,58319,58321,58323],{"class":681,"line":964},[679,58320,49970],{"class":693},[679,58322,57990],{"class":689},[679,58324,12083],{"class":693},[679,58326,58327],{"class":681,"line":977},[679,58328,57997],{"class":693},[679,58330,58331],{"class":681,"line":982},[679,58332,58002],{"class":693},[679,58334,58335],{"class":681,"line":988},[679,58336,28483],{"class":693},[679,58338,58339,58341],{"class":681,"line":993},[679,58340,49979],{"class":880},[679,58342,2667],{"class":693},[679,58344,58345,58347],{"class":681,"line":2129},[679,58346,21478],{"class":685},[679,58348,884],{"class":693},[679,58350,58351,58353,58355,58357],{"class":681,"line":2140},[679,58352,57544],{"class":693},[679,58354,8930],{"class":685},[679,58356,17511],{"class":880},[679,58358,17545],{"class":693},[679,58360,58361],{"class":681,"line":2145},[679,58362,50000],{"class":693},[679,58364,58365],{"class":681,"line":2154},[679,58366,28483],{"class":693},[679,58368,58369],{"class":681,"line":2159},[679,58370,50502],{"class":693},[679,58372,58373,58375],{"class":681,"line":2164},[679,58374,58045],{"class":880},[679,58376,2667],{"class":693},[679,58378,58379,58381,58383,58385,58387,58389],{"class":681,"line":3134},[679,58380,50514],{"class":931},[679,58382,57738],{"class":693},[679,58384,686],{"class":685},[679,58386,2054],{"class":685},[679,58388,17511],{"class":880},[679,58390,9317],{"class":693},[679,58392,58393],{"class":681,"line":3139},[679,58394,985],{"class":693},[679,58396,58397],{"class":681,"line":3144},[679,58398,28483],{"class":693},[679,58400,58401,58403],{"class":681,"line":3149},[679,58402,54604],{"class":880},[679,58404,2667],{"class":693},[679,58406,58407,58409,58411,58413],{"class":681,"line":3169},[679,58408,58080],{"class":880},[679,58410,55186],{"class":693},[679,58412,21350],{"class":685},[679,58414,884],{"class":693},[679,58416,58417,58419,58421,58423],{"class":681,"line":3185},[679,58418,50514],{"class":931},[679,58420,664],{"class":693},[679,58422,57781],{"class":880},[679,58424,9317],{"class":693},[679,58426,58427,58429,58431],{"class":681,"line":3194},[679,58428,58101],{"class":693},[679,58430,57791],{"class":931},[679,58432,1208],{"class":693},[679,58434,58435],{"class":681,"line":3199},[679,58436,21405],{"class":693},[679,58438,58439],{"class":681,"line":3212},[679,58440,44055],{"class":693},[679,58442,58443,58445,58447],{"class":681,"line":3217},[679,58444,4586],{"class":693},[679,58446,47668],{"class":4508},[679,58448,4519],{"class":693},[651,58450,58451],{},"And you should see the same application you have been working with. The nice thing about CodeSandbox is now you can take this and share the applications with friends to just show something off or get some advice.",[651,58453,58454],{},[812,58455,58456],{"href":58456,"rel":58457},"https://codesandbox.io/s/62o36qkmrr?fontsize=14",[816],[651,58459,58460],{},[660,58461],{"alt":674,"src":58462},"/images/blog/2019/04/30/2019-04-30_14-40-58-1d15756a-fa86-4d24-b8e2-bc57c97a6630.png",[4542,58464,21931],{"id":21930},[651,58466,58467,58468,664],{},"These are a few of my favorite resources to help get you going. For an in-depth look at everything in the Vue ecosystem checkout ",[812,58469,58472],{"href":58470,"rel":58471},"https://github.com/vuejs/awesome-vue",[816],"Awesome Vue",[5316,58474,58475,58482,58489],{},[5332,58476,58477],{},[812,58478,58481],{"href":58479,"rel":58480},"https://github.com/vuejs",[816],"Vue Core Repos",[5332,58483,58484],{},[812,58485,58488],{"href":58486,"rel":58487},"https://medium.com/the-vue-point",[816],"The Vue Point (Official Blog)",[5332,58490,58491],{},[812,58492,58495],{"href":58493,"rel":58494},"https://dev.to/t/vue",[816],"DEV Community",[5909,58497,26378],{"id":39439},[5316,58499,58500,58505],{},[5332,58501,58502],{},[812,58503,43525],{"href":43523,"rel":58504},[816],[5332,58506,58507],{},[812,58508,43532],{"href":43530,"rel":58509},[816],[5909,58511,58513],{"id":58512},"frameworks","Frameworks",[5316,58515,58516,58523,58530],{},[5332,58517,58518],{},[812,58519,58522],{"href":58520,"rel":58521},"https://vuepress.vuejs.org/",[816],"VuePress",[5332,58524,58525],{},[812,58526,58529],{"href":58527,"rel":58528},"https://nuxtjs.org/",[816],"Nuxt",[5332,58531,58532],{},[812,58533,43848],{"href":43854,"rel":58534},[816],[4542,58536,58538],{"id":58537},"where-to-go-from-here","Where to go from here?",[651,58540,58541],{},"No matter what framework you're using you need to keep up on your JavaScript skills. The more time you can spend improving your JavaScript skills the better. This will make sure that you are able to use any framework or library thrown your way.",[651,58543,58544,58545,58549],{},"I mentioned the ",[812,58546,58548],{"href":56988,"rel":58547},[816],"guide documentation"," earlier but it's worth mentioning again. This is the first place I would start and read through as much of this as you can but to also to make sure you're practicing the examples as you go. Repetition is an important step in the learning process and something that can't be overlooked. While you're going through the docs I would focus in on the core concepts of Vue.",[5316,58551,58552,58555,58558,58561,58564,58567,58570,58573],{},[5332,58553,58554],{},"Component Basics",[5332,58556,58557],{},"Data Binding",[5332,58559,58560],{},"Event Handling",[5332,58562,58563],{},"Directives",[5332,58565,58566],{},"Methods",[5332,58568,58569],{},"Computed Properties",[5332,58571,58572],{},"Vue Component Lifecycle",[5332,58574,58575],{},"Props",[651,58577,58578],{},"Don't feel to build large applications at this point. Focus on building components and the rest will come in time.",[4542,58580,9042],{"id":9041},[651,58582,58583,58584,58588],{},"If you follow me on Twitter (if you're not, stop what you're doing and ",[812,58585,58587],{"href":51472,"rel":58586},[816],"follow me now",") you know that I am a huge fan of Vue. I love writing component-based applications and I think Vue makes it not only easy but it really makes writing code fun. If you have any questions on your journey into Vue.js please don't hesitate to reach out. As always friends...",[651,58590,41105,58591,41109],{},[41107,58592],{},[786,58594,58595],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":58597},[58598,58603,58606,58609,58610,58611,58615,58616],{"id":56953,"depth":790,"text":56954,"children":58599},[58600,58601,58602],{"id":56974,"depth":892,"text":56965},{"id":57105,"depth":892,"text":56968},{"id":57183,"depth":892,"text":56971},{"id":57203,"depth":790,"text":57204,"children":58604},[58605],{"id":57216,"depth":892,"text":57217},{"id":57815,"depth":790,"text":57067,"children":58607},[58608],{"id":57824,"depth":892,"text":57825},{"id":58154,"depth":790,"text":58155},{"id":58202,"depth":790,"text":58203},{"id":21930,"depth":790,"text":21931,"children":58612},[58613,58614],{"id":39439,"depth":892,"text":26378},{"id":58512,"depth":892,"text":58513},{"id":58537,"depth":790,"text":58538},{"id":9041,"depth":790,"text":9042},"In this article, I am going to tell you why I love Vue and give you 4 different ways you can write your first application.",{"slug":58619,"date":58620,"published":797,"author":798,"tags":58621,"cover":58622},"up-and-running-with-vue","2019-04-30T19:38:42.927Z",[44169,25134],"./up-and-running-with-vue-cover.png",{"title":498,"description":58617},"blog/2019/04/30/up-and-running-with-vue","cKyBdz09YlHUrGMHw3QwIo10JkR6fdJl2MylLP4bLv0",{"id":58627,"title":495,"body":58628,"description":60514,"extension":793,"meta":60515,"navigation":797,"path":496,"seo":60520,"stem":60521,"__hash__":60522},"content/blog/2019/05/02/gridsome-codesandbox-plugin.md",{"type":648,"value":58629,"toc":60501},[58630,58638,58642,58657,58660,58664,58667,58670,58675,58680,58688,58695,58698,58701,58706,58709,58715,58719,58722,58725,58728,58731,58734,58738,58745,58748,58762,58766,58785,58945,58956,58961,58964,59019,59023,59026,59055,59068,59091,59103,59295,59300,59311,59441,59444,59629,59635,59672,59675,59704,59713,60191,60195,60203,60209,60228,60231,60237,60254,60268,60272,60275,60489,60491,60494,60498],[651,58631,58632,58633,58637],{},"I have been a big fan of what ",[812,58634,58203],{"href":58635,"rel":58636},"https://codesandbox.io",[816]," has been doing for a while now. Lately, I have been using it a lot more and I am finding it to be one of the tools that I can't live without. In this article, I am going to introduce you to CodeSandbox and tell you a little bit about why I love it. Then I will walk you through the plugin I created so that I can embed a CodeSandbox in Markdown so that I could display them on my Gridsome powered blog.",[4542,58639,58641],{"id":58640},"what-is-codesandbox","What is CodeSandbox",[651,58643,58644,58647,58648,13517,58653,58656],{},[812,58645,58203],{"href":58635,"rel":58646},[816]," is an online editor that helps you create web applications, from prototype to deployment. Just as an example let's say that you wanted to ",[812,58649,58652],{"href":58650,"rel":58651},"https://www.danvega.dev/blog/2019/04/30/up-and-running-with-vue",[816],"create your first",[812,58654,43568],{"href":43587,"rel":58655},[816]," application. You could make sure you had Visual Studio Code installed locally with the right extensions, node & npm, the Vue CLI and then create your first application from the command line. If you understood all of that and you wanted to take the time to get your development environment setup that's great, but what if you didn't?",[651,58658,58659],{},"What if you just wanted to check it out and create a new project hassle free? What if you wanted to do this for a number of web projects like React, Vue, Gatsby, Gridsome, VuePress, Next, Nuxt and so on. What if you wanted to use it as a teaching tool where a single concept was more important than the whole story. This is where CodeSandbox comes in and really shines bright.",[5909,58661,58663],{"id":58662},"getting-started-with-codesandbox","Getting Started with CodeSandbox",[651,58665,58666],{},"To understand what it is and why you would want to use it I think it's one of those tools that you need to just go in and try for yourself. To get started you will need to signup using your Github account. From there you will be taken to your dashboard where you can create and organize new CodeSandboxes.",[651,58668,58669],{},"If you click on create sandbox you will be given a dialog that looks something like this. From here you can choose from a variety of templates. If you select Vue as your template it will spin up a new application using the Vue CLI.",[651,58671,58672],{},[660,58673],{"alt":674,"src":58674},"/images/blog/2019/05/02/2019-05-02_06-28-26-61e4a736-935d-42c1-973a-b0622a99a834.png",[651,58676,58677],{},[660,58678],{"alt":674,"src":58679},"/images/blog/2019/05/02/2019-05-02_06-32-24-f590aff3-0d4d-4c51-b494-5541e6f20e45.png",[651,58681,58682,58683,58687],{},"If you were to just exit out of here and go back to your dashboard nothing would have been saved for you. You can tell from the browser window preview URL that it's currently ",[812,58684,58685],{"href":58685,"rel":58686},"https://vue.codesandbox.io/",[816]," and not a unique address.",[651,58689,58690,58691,58694],{},"Once you click File > Save you should see that URL change to a unique one and now this is your CodeSandbox ready for you to play with. I also like to give this CodeSandbox a name so in the top header where it currently says ",[676,58692,58693],{},"My Sandbox / Vue Template",", click on Vue Template and change the name to whatever you're working on. In my case, I will change it to Hello VueSandbox.",[651,58696,58697],{},"If you're used to using Visual Studio Code than this should look pretty familiar. Yes, that is correct, this is Visual Studio Code (extensions and all) running in the browser. Take some time to open and edit some files and watch your changes trigger a reload of the preview. Have you noticed how smooth this online editing experience is?",[651,58699,58700],{},"If you need to install a dependency for your project simply click on the Add Dependency button in your project files and search for the npm package you want to install.",[651,58702,58703],{},[660,58704],{"alt":674,"src":58705},"/images/blog/2019/05/02/2019-05-02_06-39-54-3e52b93e-a9be-4593-9ece-d2039affd22d.png",[651,58707,58708],{},"It's just that simple to create a new project and now you have something that you can share with anyone. If you look in the upper right hand corner you will see a share button with a variety of ways to share your CodeSandbox. Here is a tutorial I followed along with on YouTube to learn about a using a draggable component.",[651,58710,58711],{},[812,58712,58713],{"href":58713,"rel":58714},"https://codesandbox.io/embed/wq3o75v4qk?fontsize=12",[816],[5909,58716,58718],{"id":58717},"why-codesandbox","Why CodeSandbox",[651,58720,58721],{},"When I first heard about CodeSandbox I was pretty skeptical because I just couldn't for the life of me understand why anyone would want to code online. I have this great development environment on my local machine with Node + NPM, Visual Studio Code and everything set up to my preference.",[651,58723,58724],{},"Not to mention that this has been tried before and performance has never been anything but undesirable. Well, I am here to tell you that all my doubts and concerns about such a product have been removed thanks to CodeSandbox.",[651,58726,58727],{},"If you spend anytime in the editor you will see that the performance is great. I don't even want to think about the engineering feats that went into making this possible but I sure am grateful. So now that it works what are some reasons that you might want to use it?",[651,58729,58730],{},"Do you remember the days of zipping up your code and uploading to your FTP site and sharing it with your friends? Well I'm old so I do but you live in a great time where Github allows you to share your code with anyone right? Sure, but they still need to clone your repo and have all of the tools setup locally to run your code. Not to mention that your code probably isn't tried and tested at this point and while it runs fine on your machine it might not work on mine.",[651,58732,58733],{},"This can be extremely frustrating when you just want to view a cool demo or try out a framework/library for the first time. These are great use cases to share your CodeSandbox with friends, readers, followers or even students.",[4542,58735,58737],{"id":58736},"creating-the-gridsome-plugin","Creating the Gridsome Plugin",[651,58739,58740,58741,58744],{},"Now that you know a little bit more about CodeSandbox I want to tell you how and why I created a plugin for Gridsome. If you have been following me you know that I moved my blog over to ",[812,58742,43848],{"href":43854,"rel":58743},[816]," this year. This allows me to write all of my blog posts in Markdown which is my preferred way of writing content.",[651,58746,58747],{},"This does comes with a challenge though when you need to add some sort of custom HTML, like in this case. When you are on a CodeSandbox you can hit the share button and you will be given an embed link or some HTML using an iframe that you can use to embed the CodeSandbox in your application.",[651,58749,58750,58751,58755,58756,58761],{},"Remark is the ",[812,58752,58754],{"href":44061,"rel":58753},[816],"Markdown processor used by Gridsome"," and that is the first thing you need to understand if you're going to create a Markdown plugin. I have written a ",[812,58757,58760],{"href":58758,"rel":58759},"https://github.com/danvega/gridsome-plugin-remark-twitter",[816],"Gridsome Remark plugin"," before that allows you to insert Twitter Cards so I have a little bit of an idea what I need to do here.",[4542,58763,58765],{"id":58764},"gridsome-plugin-first-steps","Gridsome plugin first steps",[651,58767,58768,58769,58774,58775,58778,58779,58781,58782,664],{},"If you have never written a Gridsome Plugin I would check out ",[812,58770,58773],{"href":58771,"rel":58772},"https://gridsome.org/docs/how-to-create-a-plugin",[816],"their documentation"," before moving on. The first step you need to do is to create a folder called ",[676,58776,58777],{},"gridsome-plugin-remark-codesandbox"," which follows the convention of other Gridsome Remark plugins. From there you will create a new ",[676,58780,55253],{}," by running the command ",[676,58783,58784],{},"npm init",[669,58786,58788],{"className":25132,"code":58787,"language":25134,"meta":674,"style":674},"{\n \"name\": \"gridsome-plugin-remark-codesandbox\",\n \"version\": \"0.1.0\",\n \"description\": \"This plugin will allow you to add a codesandbox embed link to your markdown files\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [\n \"gridsome\",\n \"gridsome-plugin\",\n \"markdown\",\n \"vuejs\",\n \"codesandbox\"\n ],\n \"author\": \"Dan Vega \u003Cdanvega@gmail.com>\",\n \"license\": \"MIT\",\n \"dependencies\": {\n\n }\n}\n",[676,58789,58790,58794,58805,58816,58827,58837,58843,58859,58863,58869,58876,58883,58890,58897,58902,58906,58917,58927,58933,58937,58941],{"__ignoreMap":674},[679,58791,58792],{"class":681,"line":682},[679,58793,28448],{"class":693},[679,58795,58796,58798,58800,58803],{"class":681,"line":790},[679,58797,44594],{"class":689},[679,58799,4282],{"class":693},[679,58801,58802],{"class":689},"\"gridsome-plugin-remark-codesandbox\"",[679,58804,12083],{"class":693},[679,58806,58807,58809,58811,58814],{"class":681,"line":892},[679,58808,44604],{"class":689},[679,58810,4282],{"class":693},[679,58812,58813],{"class":689},"\"0.1.0\"",[679,58815,12083],{"class":693},[679,58817,58818,58820,58822,58825],{"class":681,"line":901},[679,58819,44614],{"class":689},[679,58821,4282],{"class":693},[679,58823,58824],{"class":689},"\"This plugin will allow you to add a codesandbox embed link to your markdown files\"",[679,58826,12083],{"class":693},[679,58828,58829,58831,58833,58835],{"class":681,"line":909},[679,58830,44624],{"class":689},[679,58832,4282],{"class":693},[679,58834,45014],{"class":689},[679,58836,12083],{"class":693},[679,58838,58839,58841],{"class":681,"line":918},[679,58840,30870],{"class":689},[679,58842,28468],{"class":693},[679,58844,58845,58847,58849,58851,58853,58855,58857],{"class":681,"line":935},[679,58846,30913],{"class":689},[679,58848,4282],{"class":693},[679,58850,45031],{"class":689},[679,58852,44649],{"class":931},[679,58854,44652],{"class":689},[679,58856,44649],{"class":931},[679,58858,44657],{"class":689},[679,58860,58861],{"class":681,"line":944},[679,58862,28483],{"class":693},[679,58864,58865,58867],{"class":681,"line":959},[679,58866,44666],{"class":689},[679,58868,28491],{"class":693},[679,58870,58871,58874],{"class":681,"line":964},[679,58872,58873],{"class":689}," \"gridsome\"",[679,58875,12083],{"class":693},[679,58877,58878,58881],{"class":681,"line":977},[679,58879,58880],{"class":689}," \"gridsome-plugin\"",[679,58882,12083],{"class":693},[679,58884,58885,58888],{"class":681,"line":982},[679,58886,58887],{"class":689}," \"markdown\"",[679,58889,12083],{"class":693},[679,58891,58892,58895],{"class":681,"line":988},[679,58893,58894],{"class":689}," \"vuejs\"",[679,58896,12083],{"class":693},[679,58898,58899],{"class":681,"line":993},[679,58900,58901],{"class":689}," \"codesandbox\"\n",[679,58903,58904],{"class":681,"line":2129},[679,58905,28705],{"class":693},[679,58907,58908,58910,58912,58915],{"class":681,"line":2140},[679,58909,44692],{"class":689},[679,58911,4282],{"class":693},[679,58913,58914],{"class":689},"\"Dan Vega \u003Cdanvega@gmail.com>\"",[679,58916,12083],{"class":693},[679,58918,58919,58921,58923,58925],{"class":681,"line":2145},[679,58920,44702],{"class":689},[679,58922,4282],{"class":693},[679,58924,45461],{"class":689},[679,58926,12083],{"class":693},[679,58928,58929,58931],{"class":681,"line":2154},[679,58930,45075],{"class":689},[679,58932,28468],{"class":693},[679,58934,58935],{"class":681,"line":2159},[679,58936,889],{"emptyLinePlaceholder":797},[679,58938,58939],{"class":681,"line":2164},[679,58940,21405],{"class":693},[679,58942,58943],{"class":681,"line":3134},[679,58944,996],{"class":693},[651,58946,58947,58948,58951,58952,664],{},"One important note here is that you need to add the keyword ",[676,58949,58950],{},"gridsome-plugin"," if you want it to get picked up on the Gridsome Plugins Search at ",[812,58953,58954],{"href":58954,"rel":58955},"https://gridsome.org/plugins",[816],[651,58957,58958],{},[660,58959],{"alt":674,"src":58960},"/images/blog/2019/05/02/2019-05-02_09-58-34-6c0295ad-4e1e-46d3-9e9d-368d24373694.png",[651,58962,58963],{},"At this point I would create a new Github (or your favorite Git Host) Repository to store your plugin code in. After you create the Github Repository you can follow the instructions to make the initial commit and add your remote origin.",[669,58965,58967],{"className":5851,"code":58966,"language":5853,"meta":674,"style":674},"git init\ngit add .\ngit commit -m \"initial commit\"\ngit remote add origin https://github.com/danvega/gridsome-plugin-remark-codesandbox.git\ngit push -u origin master\n",[676,58968,58969,58975,58983,58994,59007],{"__ignoreMap":674},[679,58970,58971,58973],{"class":681,"line":682},[679,58972,44537],{"class":880},[679,58974,44250],{"class":689},[679,58976,58977,58979,58981],{"class":681,"line":790},[679,58978,44537],{"class":880},[679,58980,8872],{"class":689},[679,58982,45290],{"class":689},[679,58984,58985,58987,58989,58991],{"class":681,"line":892},[679,58986,44537],{"class":880},[679,58988,45297],{"class":689},[679,58990,45300],{"class":931},[679,58992,58993],{"class":689}," \"initial commit\"\n",[679,58995,58996,58998,59000,59002,59004],{"class":681,"line":901},[679,58997,44537],{"class":880},[679,58999,45310],{"class":689},[679,59001,8872],{"class":689},[679,59003,45315],{"class":689},[679,59005,59006],{"class":689}," https://github.com/danvega/gridsome-plugin-remark-codesandbox.git\n",[679,59008,59009,59011,59013,59015,59017],{"class":681,"line":909},[679,59010,44537],{"class":880},[679,59012,45325],{"class":689},[679,59014,45328],{"class":931},[679,59016,45315],{"class":689},[679,59018,45333],{"class":689},[5909,59020,59022],{"id":59021},"gridsome-markdown-remark-processing","Gridsome Markdown Remark Processing",[651,59024,59025],{},"Before you dive into the code it's good to have a plan of how you want your Markdown structured. When I want to embed a CodeSandbox I am just going to add the embed link on it's own line (paragraph). This is probably the easiest way to handle it because the parser can make its way through paragraphs and find links.",[669,59027,59030],{"className":59028,"code":59029,"language":43882,"meta":674,"style":674},"language-markdown shiki shiki-themes github-light github-dark github-light","# CodeSandbox Embed Demo\n\nThis is a really cool sortable demo\n\nhttps://codesandbox.io/embed/wq3o75v4qk?fontsize=12\n",[676,59031,59032,59037,59041,59046,59050],{"__ignoreMap":674},[679,59033,59034],{"class":681,"line":682},[679,59035,59036],{},"# CodeSandbox Embed Demo\n",[679,59038,59039],{"class":681,"line":790},[679,59040,889],{"emptyLinePlaceholder":797},[679,59042,59043],{"class":681,"line":892},[679,59044,59045],{},"This is a really cool sortable demo\n",[679,59047,59048],{"class":681,"line":901},[679,59049,889],{"emptyLinePlaceholder":797},[679,59051,59052],{"class":681,"line":909},[679,59053,59054],{},"https://codesandbox.io/embed/wq3o75v4qk?fontsize=12\n",[651,59056,59057,59058,59061,59062,59065,59066,664],{},"With all the infrastructure setup It's time to write some code. You can start by creating ",[676,59059,59060],{},"index.js"," in the root of your project. This project will only have a single dependency and you need to install it now by running the command ",[676,59063,59064],{},"npm install unist-util-visit"," and then requiring it at the top of ",[676,59067,59060],{},[669,59069,59071],{"className":25132,"code":59070,"language":25134,"meta":674,"style":674},"const visit = require(\"unist-util-visit\");\n",[676,59072,59073],{"__ignoreMap":674},[679,59074,59075,59077,59080,59082,59084,59086,59089],{"class":681,"line":682},[679,59076,45172],{"class":685},[679,59078,59079],{"class":931}," visit",[679,59081,6883],{"class":685},[679,59083,45180],{"class":880},[679,59085,745],{"class":693},[679,59087,59088],{"class":689},"\"unist-util-visit\"",[679,59090,1208],{"class":693},[651,59092,40060,59093,59098,59099,59102],{},[812,59094,59097],{"href":59095,"rel":59096},"https://www.npmjs.com/package/unist-util-visit",[816],"unist-util-visit"," package does all the heavy lifting for us and is really helpful for working with remark. Within the visit function we are moving down the tree looking for paragraphs. This means that the embed code needs to be on it's own line in it's own paragraph which is exactly how I want it. Next you will call a method called ",[676,59100,59101],{},"isCodeSandboxLink"," and pass it the current node. Each time you find one you will add it to the nodes array so that you can process them later.",[669,59104,59106],{"className":25132,"code":59105,"language":25134,"meta":674,"style":674},"const visit = require(\"unist-util-visit\");\n\nmodule.exports = options => {\n const debug = options.debug ? console.log : () => {};\n\n return tree => {\n const nodes = [];\n\n visit(tree, \"paragraph\", node => {\n debug(node);\n if (isCodeSandboxLink(node)) {\n debug(`\\nfound codesandbox link`, node.children[0].url);\n nodes.push([node, node.children[0].url]);\n }\n });\n };\n};\n",[676,59107,59108,59124,59128,59145,59170,59174,59185,59197,59201,59220,59228,59240,59262,59278,59282,59287,59291],{"__ignoreMap":674},[679,59109,59110,59112,59114,59116,59118,59120,59122],{"class":681,"line":682},[679,59111,45172],{"class":685},[679,59113,59079],{"class":931},[679,59115,6883],{"class":685},[679,59117,45180],{"class":880},[679,59119,745],{"class":693},[679,59121,59088],{"class":689},[679,59123,1208],{"class":693},[679,59125,59126],{"class":681,"line":790},[679,59127,889],{"emptyLinePlaceholder":797},[679,59129,59130,59132,59134,59136,59138,59141,59143],{"class":681,"line":892},[679,59131,27515],{"class":931},[679,59133,664],{"class":693},[679,59135,43903],{"class":931},[679,59137,6883],{"class":685},[679,59139,59140],{"class":2099}," options",[679,59142,44760],{"class":685},[679,59144,884],{"class":693},[679,59146,59147,59149,59152,59154,59157,59159,59162,59164,59166,59168],{"class":681,"line":901},[679,59148,46903],{"class":685},[679,59150,59151],{"class":931}," debug",[679,59153,6883],{"class":685},[679,59155,59156],{"class":693}," options.debug ",[679,59158,2381],{"class":685},[679,59160,59161],{"class":693}," console.log ",[679,59163,2391],{"class":685},[679,59165,55227],{"class":693},[679,59167,21350],{"class":685},[679,59169,6706],{"class":693},[679,59171,59172],{"class":681,"line":909},[679,59173,889],{"emptyLinePlaceholder":797},[679,59175,59176,59178,59181,59183],{"class":681,"line":918},[679,59177,44767],{"class":685},[679,59179,59180],{"class":2099}," tree",[679,59182,44760],{"class":685},[679,59184,884],{"class":693},[679,59186,59187,59189,59192,59194],{"class":681,"line":935},[679,59188,54021],{"class":685},[679,59190,59191],{"class":931}," nodes",[679,59193,6883],{"class":685},[679,59195,59196],{"class":693}," [];\n",[679,59198,59199],{"class":681,"line":944},[679,59200,889],{"emptyLinePlaceholder":797},[679,59202,59203,59206,59209,59212,59214,59216,59218],{"class":681,"line":959},[679,59204,59205],{"class":880}," visit",[679,59207,59208],{"class":693},"(tree, ",[679,59210,59211],{"class":689},"\"paragraph\"",[679,59213,2797],{"class":693},[679,59215,27318],{"class":2099},[679,59217,44760],{"class":685},[679,59219,884],{"class":693},[679,59221,59222,59225],{"class":681,"line":964},[679,59223,59224],{"class":880}," debug",[679,59226,59227],{"class":693},"(node);\n",[679,59229,59230,59233,59235,59237],{"class":681,"line":977},[679,59231,59232],{"class":685}," if",[679,59234,4193],{"class":693},[679,59236,59101],{"class":880},[679,59238,59239],{"class":693},"(node)) {\n",[679,59241,59242,59245,59247,59249,59251,59254,59257,59259],{"class":681,"line":982},[679,59243,59244],{"class":880}," debug",[679,59246,745],{"class":693},[679,59248,44388],{"class":689},[679,59250,56302],{"class":931},[679,59252,59253],{"class":689},"found codesandbox link`",[679,59255,59256],{"class":693},", node.children[",[679,59258,1060],{"class":931},[679,59260,59261],{"class":693},"].url);\n",[679,59263,59264,59267,59270,59273,59275],{"class":681,"line":988},[679,59265,59266],{"class":693}," nodes.",[679,59268,59269],{"class":880},"push",[679,59271,59272],{"class":693},"([node, node.children[",[679,59274,1060],{"class":931},[679,59276,59277],{"class":693},"].url]);\n",[679,59279,59280],{"class":681,"line":993},[679,59281,11804],{"class":693},[679,59283,59284],{"class":681,"line":2129},[679,59285,59286],{"class":693}," });\n",[679,59288,59289],{"class":681,"line":2140},[679,59290,53075],{"class":693},[679,59292,59293],{"class":681,"line":2145},[679,59294,44055],{"class":693},[651,59296,40060,59297,59299],{},[676,59298,59101],{}," function checks a couple of things",[5316,59301,59302,59305,59308],{},[5332,59303,59304],{},"The embed link should be on its own line by itself.",[5332,59306,59307],{},"It should be a link so just putting an id there won't work.",[5332,59309,59310],{},"It matches the Regular Expression defined to match an embed link.",[669,59312,59314],{"className":25132,"code":59313,"language":25134,"meta":674,"style":674},"const codeSandboxRegexp = /https:\\/\\/codesandbox\\.io\\/embed\\/.\\*/;\n\nconst isCodeSandboxLink = node => {\n return (\n node.children.length === 1 &&\n node.children[0].type === \"link\" &&\n codeSandboxRegexp.test(node.children[0].url)\n );\n};\n",[676,59315,59316,59358,59362,59378,59385,59400,59417,59432,59437],{"__ignoreMap":674},[679,59317,59318,59320,59323,59325,59328,59331,59334,59336,59339,59342,59345,59348,59350,59352,59354,59356],{"class":681,"line":682},[679,59319,45172],{"class":685},[679,59321,59322],{"class":931}," codeSandboxRegexp",[679,59324,6883],{"class":685},[679,59326,59327],{"class":689}," /",[679,59329,59330],{"class":4415},"https:",[679,59332,59333],{"class":4411},"\\/\\/",[679,59335,58202],{"class":4415},[679,59337,59338],{"class":4411},"\\.",[679,59340,59341],{"class":4415},"io",[679,59343,59344],{"class":4411},"\\/",[679,59346,59347],{"class":4415},"embed",[679,59349,59344],{"class":4411},[679,59351,664],{"class":931},[679,59353,13165],{"class":4411},[679,59355,4408],{"class":689},[679,59357,1186],{"class":693},[679,59359,59360],{"class":681,"line":790},[679,59361,889],{"emptyLinePlaceholder":797},[679,59363,59364,59366,59369,59371,59374,59376],{"class":681,"line":892},[679,59365,45172],{"class":685},[679,59367,59368],{"class":880}," isCodeSandboxLink",[679,59370,6883],{"class":685},[679,59372,59373],{"class":2099}," node",[679,59375,44760],{"class":685},[679,59377,884],{"class":693},[679,59379,59380,59382],{"class":681,"line":901},[679,59381,44767],{"class":685},[679,59383,59384],{"class":693}," (\n",[679,59386,59387,59390,59392,59395,59397],{"class":681,"line":909},[679,59388,59389],{"class":693}," node.children.",[679,59391,51959],{"class":931},[679,59393,59394],{"class":685}," ===",[679,59396,48606],{"class":931},[679,59398,59399],{"class":685}," &&\n",[679,59401,59402,59405,59407,59410,59412,59415],{"class":681,"line":918},[679,59403,59404],{"class":693}," node.children[",[679,59406,1060],{"class":931},[679,59408,59409],{"class":693},"].type ",[679,59411,51108],{"class":685},[679,59413,59414],{"class":689}," \"link\"",[679,59416,59399],{"class":685},[679,59418,59419,59422,59424,59427,59429],{"class":681,"line":935},[679,59420,59421],{"class":693}," codeSandboxRegexp.",[679,59423,44529],{"class":880},[679,59425,59426],{"class":693},"(node.children[",[679,59428,1060],{"class":931},[679,59430,59431],{"class":693},"].url)\n",[679,59433,59434],{"class":681,"line":944},[679,59435,59436],{"class":693}," );\n",[679,59438,59439],{"class":681,"line":959},[679,59440,44055],{"class":693},[651,59442,59443],{},"Now that you have an array of all CodeSandbox links in your Markdown file you need to process them. You will do this right after your visit function call and it looks something like this:",[669,59445,59447],{"className":25132,"code":59446,"language":25134,"meta":674,"style":674},"for (let i = 0; i \u003C nodes.length; i++) {\n const nt = nodes[i];\n const node = nt[0];\n const csLink = nt[1];\n debug(`\\nembeding codesandbox: ${csLink}`);\n try {\n const csEmbed = getEmbeddedCodeSandbox(csLink);\n node.type = \"html\";\n node.value = csEmbed;\n } catch (err) {\n debug(`\\nfailed to get iframe for ${csLink}\\n`, er);\n }\n}\n",[676,59448,59449,59479,59491,59506,59521,59542,59549,59564,59576,59586,59596,59621,59625],{"__ignoreMap":674},[679,59450,59451,59453,59455,59457,59459,59461,59463,59466,59468,59471,59473,59475,59477],{"class":681,"line":682},[679,59452,1466],{"class":685},[679,59454,4193],{"class":693},[679,59456,51795],{"class":685},[679,59458,36971],{"class":693},[679,59460,686],{"class":685},[679,59462,14987],{"class":931},[679,59464,59465],{"class":693},"; i ",[679,59467,4505],{"class":685},[679,59469,59470],{"class":693}," nodes.",[679,59472,51959],{"class":931},[679,59474,1560],{"class":693},[679,59476,1569],{"class":685},[679,59478,4390],{"class":693},[679,59480,59481,59483,59486,59488],{"class":681,"line":790},[679,59482,46903],{"class":685},[679,59484,59485],{"class":931}," nt",[679,59487,6883],{"class":685},[679,59489,59490],{"class":693}," nodes[i];\n",[679,59492,59493,59495,59497,59499,59502,59504],{"class":681,"line":892},[679,59494,46903],{"class":685},[679,59496,59373],{"class":931},[679,59498,6883],{"class":685},[679,59500,59501],{"class":693}," nt[",[679,59503,1060],{"class":931},[679,59505,47519],{"class":693},[679,59507,59508,59510,59513,59515,59517,59519],{"class":681,"line":901},[679,59509,46903],{"class":685},[679,59511,59512],{"class":931}," csLink",[679,59514,6883],{"class":685},[679,59516,59501],{"class":693},[679,59518,1557],{"class":931},[679,59520,47519],{"class":693},[679,59522,59523,59526,59528,59530,59532,59535,59538,59540],{"class":681,"line":909},[679,59524,59525],{"class":880}," debug",[679,59527,745],{"class":693},[679,59529,44388],{"class":689},[679,59531,56302],{"class":931},[679,59533,59534],{"class":689},"embeding codesandbox: ${",[679,59536,59537],{"class":693},"csLink",[679,59539,47580],{"class":689},[679,59541,1208],{"class":693},[679,59543,59544,59547],{"class":681,"line":918},[679,59545,59546],{"class":685}," try",[679,59548,884],{"class":693},[679,59550,59551,59553,59556,59558,59561],{"class":681,"line":935},[679,59552,54021],{"class":685},[679,59554,59555],{"class":931}," csEmbed",[679,59557,6883],{"class":685},[679,59559,59560],{"class":880}," getEmbeddedCodeSandbox",[679,59562,59563],{"class":693},"(csLink);\n",[679,59565,59566,59569,59571,59574],{"class":681,"line":944},[679,59567,59568],{"class":693}," node.type ",[679,59570,686],{"class":685},[679,59572,59573],{"class":689}," \"html\"",[679,59575,1186],{"class":693},[679,59577,59578,59581,59583],{"class":681,"line":959},[679,59579,59580],{"class":693}," node.value ",[679,59582,686],{"class":685},[679,59584,59585],{"class":693}," csEmbed;\n",[679,59587,59588,59591,59593],{"class":681,"line":964},[679,59589,59590],{"class":693}," } ",[679,59592,9394],{"class":685},[679,59594,59595],{"class":693}," (err) {\n",[679,59597,59598,59601,59603,59605,59607,59610,59612,59614,59616,59618],{"class":681,"line":977},[679,59599,59600],{"class":880}," debug",[679,59602,745],{"class":693},[679,59604,44388],{"class":689},[679,59606,56302],{"class":931},[679,59608,59609],{"class":689},"failed to get iframe for ${",[679,59611,59537],{"class":693},[679,59613,56311],{"class":689},[679,59615,56302],{"class":931},[679,59617,44388],{"class":689},[679,59619,59620],{"class":693},", er);\n",[679,59622,59623],{"class":681,"line":982},[679,59624,21405],{"class":693},[679,59626,59627],{"class":681,"line":988},[679,59628,996],{"class":693},[651,59630,59631,59632,664],{},"Finally you will need to return the HTML needed to embed it using an ",[676,59633,59634],{},"iframe",[669,59636,59638],{"className":25132,"code":59637,"language":25134,"meta":674,"style":674},"const getEmbeddedCodeSandbox = link => {\n return `\u003Ciframe src=\"${link}\" style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\" sandbox=\"allow-modals allow-forms allow-popups allow-scripts allow-same-origin\">\u003C/iframe>`;\n};\n",[676,59639,59640,59654,59668],{"__ignoreMap":674},[679,59641,59642,59644,59646,59648,59650,59652],{"class":681,"line":682},[679,59643,45172],{"class":685},[679,59645,59560],{"class":880},[679,59647,6883],{"class":685},[679,59649,45151],{"class":2099},[679,59651,44760],{"class":685},[679,59653,884],{"class":693},[679,59655,59656,59658,59661,59663,59666],{"class":681,"line":790},[679,59657,44767],{"class":685},[679,59659,59660],{"class":689}," `\u003Ciframe src=\"${",[679,59662,6409],{"class":693},[679,59664,59665],{"class":689},"}\" style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\" sandbox=\"allow-modals allow-forms allow-popups allow-scripts allow-same-origin\">\u003C/iframe>`",[679,59667,1186],{"class":693},[679,59669,59670],{"class":681,"line":892},[679,59671,44055],{"class":693},[651,59673,59674],{},"What I really like about this approach is you can customize each embed with it's own options.",[669,59676,59678],{"className":59028,"code":59677,"language":43882,"meta":674,"style":674},"// smaller font\nhttps://codesandbox.io/embed/wq3o75v4qk?fontsize=11\n\n// different view\nhttps://codesandbox.io/embed/wq3o75v4qk?fontsize=14&view=editor\n",[676,59679,59680,59685,59690,59694,59699],{"__ignoreMap":674},[679,59681,59682],{"class":681,"line":682},[679,59683,59684],{},"// smaller font\n",[679,59686,59687],{"class":681,"line":790},[679,59688,59689],{},"https://codesandbox.io/embed/wq3o75v4qk?fontsize=11\n",[679,59691,59692],{"class":681,"line":892},[679,59693,889],{"emptyLinePlaceholder":797},[679,59695,59696],{"class":681,"line":901},[679,59697,59698],{},"// different view\n",[679,59700,59701],{"class":681,"line":909},[679,59702,59703],{},"https://codesandbox.io/embed/wq3o75v4qk?fontsize=14&view=editor\n",[651,59705,59706,59707,59712],{},"If you're curious what options are available you can check out the ",[812,59708,59711],{"href":59709,"rel":59710},"https://codesandbox.io/docs/embedding#embed-options",[816],"CodeSandbox documentation",". If you're following along you should end up with a solution that looks something like this.",[669,59714,59716],{"className":25132,"code":59715,"language":25134,"meta":674,"style":674},"const visit = require(\"unist-util-visit\");\n\nconst codeSandboxRegexp = /https:\\/\\/codesandbox\\.io\\/embed\\/.*/;\n\nconst isCodeSandboxLink = node => {\n return (\n node.children.length === 1 &&\n node.children[0].type === \"link\" &&\n codeSandboxRegexp.test(node.children[0].url)\n );\n};\n\nconst getEmbeddedCodeSandbox = link => {\n return `\u003Ciframe src=\"${link}\" style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\" sandbox=\"allow-modals allow-forms allow-popups allow-scripts allow-same-origin\">\u003C/iframe>`;\n};\n\nmodule.exports = options => {\n const debug = options.debug ? console.log : () => {};\n\n return tree => {\n const nodes = [];\n\n visit(tree, \"paragraph\", node => {\n debug(node);\n if (isCodeSandboxLink(node)) {\n debug(`\\nfound codesandbox link`, node.children[0].url);\n nodes.push([node, node.children[0].url]);\n }\n });\n\n for (let i = 0; i \u003C nodes.length; i++) {\n const nt = nodes[i];\n const node = nt[0];\n const csLink = nt[1];\n debug(`\\nembeding codesandbox: ${csLink}`);\n try {\n const csEmbed = getEmbeddedCodeSandbox(csLink);\n node.type = \"html\";\n node.value = csEmbed;\n } catch (err) {\n debug(`\\nfailed to get iframe for ${csLink}\\n`, er);\n }\n }\n };\n};\n",[676,59717,59718,59734,59738,59772,59776,59790,59796,59808,59822,59834,59838,59842,59846,59860,59872,59876,59880,59896,59918,59922,59932,59942,59946,59962,59968,59978,59996,60008,60012,60016,60020,60049,60059,60073,60087,60105,60112,60124,60135,60144,60153,60175,60179,60183,60187],{"__ignoreMap":674},[679,59719,59720,59722,59724,59726,59728,59730,59732],{"class":681,"line":682},[679,59721,45172],{"class":685},[679,59723,59079],{"class":931},[679,59725,6883],{"class":685},[679,59727,45180],{"class":880},[679,59729,745],{"class":693},[679,59731,59088],{"class":689},[679,59733,1208],{"class":693},[679,59735,59736],{"class":681,"line":790},[679,59737,889],{"emptyLinePlaceholder":797},[679,59739,59740,59742,59744,59746,59748,59750,59752,59754,59756,59758,59760,59762,59764,59766,59768,59770],{"class":681,"line":892},[679,59741,45172],{"class":685},[679,59743,59322],{"class":931},[679,59745,6883],{"class":685},[679,59747,59327],{"class":689},[679,59749,59330],{"class":4415},[679,59751,59333],{"class":4411},[679,59753,58202],{"class":4415},[679,59755,59338],{"class":4411},[679,59757,59341],{"class":4415},[679,59759,59344],{"class":4411},[679,59761,59347],{"class":4415},[679,59763,59344],{"class":4411},[679,59765,664],{"class":931},[679,59767,4150],{"class":685},[679,59769,4408],{"class":689},[679,59771,1186],{"class":693},[679,59773,59774],{"class":681,"line":901},[679,59775,889],{"emptyLinePlaceholder":797},[679,59777,59778,59780,59782,59784,59786,59788],{"class":681,"line":909},[679,59779,45172],{"class":685},[679,59781,59368],{"class":880},[679,59783,6883],{"class":685},[679,59785,59373],{"class":2099},[679,59787,44760],{"class":685},[679,59789,884],{"class":693},[679,59791,59792,59794],{"class":681,"line":918},[679,59793,44767],{"class":685},[679,59795,59384],{"class":693},[679,59797,59798,59800,59802,59804,59806],{"class":681,"line":935},[679,59799,59389],{"class":693},[679,59801,51959],{"class":931},[679,59803,59394],{"class":685},[679,59805,48606],{"class":931},[679,59807,59399],{"class":685},[679,59809,59810,59812,59814,59816,59818,59820],{"class":681,"line":944},[679,59811,59404],{"class":693},[679,59813,1060],{"class":931},[679,59815,59409],{"class":693},[679,59817,51108],{"class":685},[679,59819,59414],{"class":689},[679,59821,59399],{"class":685},[679,59823,59824,59826,59828,59830,59832],{"class":681,"line":959},[679,59825,59421],{"class":693},[679,59827,44529],{"class":880},[679,59829,59426],{"class":693},[679,59831,1060],{"class":931},[679,59833,59431],{"class":693},[679,59835,59836],{"class":681,"line":964},[679,59837,59436],{"class":693},[679,59839,59840],{"class":681,"line":977},[679,59841,44055],{"class":693},[679,59843,59844],{"class":681,"line":982},[679,59845,889],{"emptyLinePlaceholder":797},[679,59847,59848,59850,59852,59854,59856,59858],{"class":681,"line":988},[679,59849,45172],{"class":685},[679,59851,59560],{"class":880},[679,59853,6883],{"class":685},[679,59855,45151],{"class":2099},[679,59857,44760],{"class":685},[679,59859,884],{"class":693},[679,59861,59862,59864,59866,59868,59870],{"class":681,"line":993},[679,59863,44767],{"class":685},[679,59865,59660],{"class":689},[679,59867,6409],{"class":693},[679,59869,59665],{"class":689},[679,59871,1186],{"class":693},[679,59873,59874],{"class":681,"line":2129},[679,59875,44055],{"class":693},[679,59877,59878],{"class":681,"line":2140},[679,59879,889],{"emptyLinePlaceholder":797},[679,59881,59882,59884,59886,59888,59890,59892,59894],{"class":681,"line":2145},[679,59883,27515],{"class":931},[679,59885,664],{"class":693},[679,59887,43903],{"class":931},[679,59889,6883],{"class":685},[679,59891,59140],{"class":2099},[679,59893,44760],{"class":685},[679,59895,884],{"class":693},[679,59897,59898,59900,59902,59904,59906,59908,59910,59912,59914,59916],{"class":681,"line":2154},[679,59899,46903],{"class":685},[679,59901,59151],{"class":931},[679,59903,6883],{"class":685},[679,59905,59156],{"class":693},[679,59907,2381],{"class":685},[679,59909,59161],{"class":693},[679,59911,2391],{"class":685},[679,59913,55227],{"class":693},[679,59915,21350],{"class":685},[679,59917,6706],{"class":693},[679,59919,59920],{"class":681,"line":2159},[679,59921,889],{"emptyLinePlaceholder":797},[679,59923,59924,59926,59928,59930],{"class":681,"line":2164},[679,59925,44767],{"class":685},[679,59927,59180],{"class":2099},[679,59929,44760],{"class":685},[679,59931,884],{"class":693},[679,59933,59934,59936,59938,59940],{"class":681,"line":3134},[679,59935,54021],{"class":685},[679,59937,59191],{"class":931},[679,59939,6883],{"class":685},[679,59941,59196],{"class":693},[679,59943,59944],{"class":681,"line":3139},[679,59945,889],{"emptyLinePlaceholder":797},[679,59947,59948,59950,59952,59954,59956,59958,59960],{"class":681,"line":3144},[679,59949,59205],{"class":880},[679,59951,59208],{"class":693},[679,59953,59211],{"class":689},[679,59955,2797],{"class":693},[679,59957,27318],{"class":2099},[679,59959,44760],{"class":685},[679,59961,884],{"class":693},[679,59963,59964,59966],{"class":681,"line":3149},[679,59965,59224],{"class":880},[679,59967,59227],{"class":693},[679,59969,59970,59972,59974,59976],{"class":681,"line":3169},[679,59971,59232],{"class":685},[679,59973,4193],{"class":693},[679,59975,59101],{"class":880},[679,59977,59239],{"class":693},[679,59979,59980,59982,59984,59986,59988,59990,59992,59994],{"class":681,"line":3185},[679,59981,59244],{"class":880},[679,59983,745],{"class":693},[679,59985,44388],{"class":689},[679,59987,56302],{"class":931},[679,59989,59253],{"class":689},[679,59991,59256],{"class":693},[679,59993,1060],{"class":931},[679,59995,59261],{"class":693},[679,59997,59998,60000,60002,60004,60006],{"class":681,"line":3194},[679,59999,59266],{"class":693},[679,60001,59269],{"class":880},[679,60003,59272],{"class":693},[679,60005,1060],{"class":931},[679,60007,59277],{"class":693},[679,60009,60010],{"class":681,"line":3199},[679,60011,11804],{"class":693},[679,60013,60014],{"class":681,"line":3212},[679,60015,59286],{"class":693},[679,60017,60018],{"class":681,"line":3217},[679,60019,889],{"emptyLinePlaceholder":797},[679,60021,60022,60025,60027,60029,60031,60033,60035,60037,60039,60041,60043,60045,60047],{"class":681,"line":3222},[679,60023,60024],{"class":685}," for",[679,60026,4193],{"class":693},[679,60028,51795],{"class":685},[679,60030,36971],{"class":693},[679,60032,686],{"class":685},[679,60034,14987],{"class":931},[679,60036,59465],{"class":693},[679,60038,4505],{"class":685},[679,60040,59470],{"class":693},[679,60042,51959],{"class":931},[679,60044,1560],{"class":693},[679,60046,1569],{"class":685},[679,60048,4390],{"class":693},[679,60050,60051,60053,60055,60057],{"class":681,"line":3227},[679,60052,47094],{"class":685},[679,60054,59485],{"class":931},[679,60056,6883],{"class":685},[679,60058,59490],{"class":693},[679,60060,60061,60063,60065,60067,60069,60071],{"class":681,"line":3232},[679,60062,47094],{"class":685},[679,60064,59373],{"class":931},[679,60066,6883],{"class":685},[679,60068,59501],{"class":693},[679,60070,1060],{"class":931},[679,60072,47519],{"class":693},[679,60074,60075,60077,60079,60081,60083,60085],{"class":681,"line":3499},[679,60076,47094],{"class":685},[679,60078,59512],{"class":931},[679,60080,6883],{"class":685},[679,60082,59501],{"class":693},[679,60084,1557],{"class":931},[679,60086,47519],{"class":693},[679,60088,60089,60091,60093,60095,60097,60099,60101,60103],{"class":681,"line":3509},[679,60090,59224],{"class":880},[679,60092,745],{"class":693},[679,60094,44388],{"class":689},[679,60096,56302],{"class":931},[679,60098,59534],{"class":689},[679,60100,59537],{"class":693},[679,60102,47580],{"class":689},[679,60104,1208],{"class":693},[679,60106,60107,60110],{"class":681,"line":3516},[679,60108,60109],{"class":685}," try",[679,60111,884],{"class":693},[679,60113,60114,60116,60118,60120,60122],{"class":681,"line":3531},[679,60115,47128],{"class":685},[679,60117,59555],{"class":931},[679,60119,6883],{"class":685},[679,60121,59560],{"class":880},[679,60123,59563],{"class":693},[679,60125,60126,60129,60131,60133],{"class":681,"line":3536},[679,60127,60128],{"class":693}," node.type ",[679,60130,686],{"class":685},[679,60132,59573],{"class":689},[679,60134,1186],{"class":693},[679,60136,60137,60140,60142],{"class":681,"line":3541},[679,60138,60139],{"class":693}," node.value ",[679,60141,686],{"class":685},[679,60143,59585],{"class":693},[679,60145,60146,60149,60151],{"class":681,"line":3546},[679,60147,60148],{"class":693}," } ",[679,60150,9394],{"class":685},[679,60152,59595],{"class":693},[679,60154,60155,60157,60159,60161,60163,60165,60167,60169,60171,60173],{"class":681,"line":3551},[679,60156,59244],{"class":880},[679,60158,745],{"class":693},[679,60160,44388],{"class":689},[679,60162,56302],{"class":931},[679,60164,59609],{"class":689},[679,60166,59537],{"class":693},[679,60168,56311],{"class":689},[679,60170,56302],{"class":931},[679,60172,44388],{"class":689},[679,60174,59620],{"class":693},[679,60176,60177],{"class":681,"line":3557},[679,60178,11804],{"class":693},[679,60180,60181],{"class":681,"line":3567},[679,60182,985],{"class":693},[679,60184,60185],{"class":681,"line":3574},[679,60186,53075],{"class":693},[679,60188,60189],{"class":681,"line":3589},[679,60190,44055],{"class":693},[5909,60192,60194],{"id":60193},"npm-package-testing-and-publishing","NPM Package Testing and Publishing",[651,60196,60197,60198,60202],{},"I don't want too much time on this because I actually wrote a whole article title \"",[812,60199,537],{"href":60200,"rel":60201},"https://www.danvega.dev/blog/2019/02/10/creating-your-first-npm-package",[816],"\" that goes through all of this but I do want to mention it.",[651,60204,60205,60206,60208],{},"When you're developing the plugin you need a way to test it without installing it from NPM because it isn't there yet. In the plugin project you can run the command ",[676,60207,45109],{}," which will take your package and create a symbolic link in the npm global folder to it.",[669,60210,60212],{"className":5851,"code":60211,"language":5853,"meta":674,"style":674},"/Users/vega/.nvm/versions/node/v10.12.0/lib/node_modules/gridsome-plugin-remark-codesandbox ->\n/Users/vega/dev/npm/gridsome-plugin-remark-codesandbox\n",[676,60213,60214,60223],{"__ignoreMap":674},[679,60215,60216,60219,60221],{"class":681,"line":682},[679,60217,60218],{"class":880},"/Users/vega/.nvm/versions/node/v10.12.0/lib/node_modules/gridsome-plugin-remark-codesandbox",[679,60220,35355],{"class":693},[679,60222,4519],{"class":685},[679,60224,60225],{"class":681,"line":790},[679,60226,60227],{"class":880},"/Users/vega/dev/npm/gridsome-plugin-remark-codesandbox\n",[651,60229,60230],{},"Then from the project that you wish to use it in (for me it was my Gridsome blog) run the following command:",[669,60232,60235],{"className":60233,"code":60234,"language":11464},[16247],"npm link gridsome-plugin-remark-codesandbox\n",[676,60236,60234],{"__ignoreMap":674},[651,60238,60239,60240,60243,60244,60247,60248,60253],{},"Which will add it to your ",[676,60241,60242],{},"node_modules"," folder and you are ready to go. When the plugin is ready to go you can publish it using ",[676,60245,60246],{},"npm publish",". Once the ",[812,60249,60252],{"href":60250,"rel":60251},"https://www.npmjs.com/package/gridsome-plugin-remark-codesandbox",[816],"package is on NPM"," you can install it just like any other package:",[669,60255,60257],{"className":5851,"code":60256,"language":5853,"meta":674,"style":674},"npm install gridsome-plugin-remark-codesandbox\n",[676,60258,60259],{"__ignoreMap":674},[679,60260,60261,60263,60265],{"class":681,"line":682},[679,60262,24568],{"class":880},[679,60264,24571],{"class":689},[679,60266,60267],{"class":689}," gridsome-plugin-remark-codesandbox\n",[5909,60269,60271],{"id":60270},"using-the-plugin-in-gridsome","Using the plugin in Gridsome",[651,60273,60274],{},"However you have the plugin installed in your project there is one more step to making it work. Where you are defining your remark plugins you need to add this one to the plugins array:",[669,60276,60278],{"className":25132,"code":60277,"language":25134,"meta":674,"style":674},"plugins: [{\n use: '@gridsome/source-filesystem',\n options: {\n path: 'blog/**/*.md',\n typeName: 'Post',\n route: '/blog/:year/:month/:day/:slug',\n refs: {\n tags: {\n typeName: 'Tag',\n route: '/tag/:slug',\n create: true\n }\n },\n resolveAbsolutePaths: true,\n remark: {\n autolinkClassName: 'fas fa-hashtag',\n externalLinksTarget: '_blank',\n externalLinksRel: ['nofollow', 'noopener', 'noreferrer'],\n plugins: [\n ['gridsome-plugin-remark-shiki', {\n theme: 'nord'\n }],\n ['gridsome-plugin-remark-twitter', {}],\n ['gridsome-plugin-remark-codesandbox', {}]\n ]\n }\n }\n},\n",[676,60279,60280,60286,60295,60300,60309,60318,60327,60332,60337,60346,60355,60362,60366,60370,60379,60384,60394,60404,60424,60429,60439,60447,60452,60462,60472,60477,60481,60485],{"__ignoreMap":674},[679,60281,60282,60284],{"class":681,"line":682},[679,60283,52315],{"class":880},[679,60285,52318],{"class":693},[679,60287,60288,60291,60293],{"class":681,"line":790},[679,60289,60290],{"class":693}," use: ",[679,60292,52325],{"class":689},[679,60294,12083],{"class":693},[679,60296,60297],{"class":681,"line":892},[679,60298,60299],{"class":693}," options: {\n",[679,60301,60302,60305,60307],{"class":681,"line":901},[679,60303,60304],{"class":693}," path: ",[679,60306,52338],{"class":689},[679,60308,12083],{"class":693},[679,60310,60311,60314,60316],{"class":681,"line":909},[679,60312,60313],{"class":693}," typeName: ",[679,60315,52347],{"class":689},[679,60317,12083],{"class":693},[679,60319,60320,60323,60325],{"class":681,"line":918},[679,60321,60322],{"class":693}," route: ",[679,60324,52356],{"class":689},[679,60326,12083],{"class":693},[679,60328,60329],{"class":681,"line":935},[679,60330,60331],{"class":693}," refs: {\n",[679,60333,60334],{"class":681,"line":944},[679,60335,60336],{"class":693}," tags: {\n",[679,60338,60339,60341,60344],{"class":681,"line":959},[679,60340,43946],{"class":693},[679,60342,60343],{"class":689},"'Tag'",[679,60345,12083],{"class":693},[679,60347,60348,60350,60353],{"class":681,"line":964},[679,60349,43956],{"class":693},[679,60351,60352],{"class":689},"'/tag/:slug'",[679,60354,12083],{"class":693},[679,60356,60357,60360],{"class":681,"line":977},[679,60358,60359],{"class":693}," create: ",[679,60361,5134],{"class":931},[679,60363,60364],{"class":681,"line":982},[679,60365,11804],{"class":693},[679,60367,60368],{"class":681,"line":988},[679,60369,28763],{"class":693},[679,60371,60372,60375,60377],{"class":681,"line":993},[679,60373,60374],{"class":693}," resolveAbsolutePaths: ",[679,60376,3441],{"class":931},[679,60378,12083],{"class":693},[679,60380,60381],{"class":681,"line":2129},[679,60382,60383],{"class":693}," remark: {\n",[679,60385,60386,60389,60392],{"class":681,"line":2140},[679,60387,60388],{"class":693}," autolinkClassName: ",[679,60390,60391],{"class":689},"'fas fa-hashtag'",[679,60393,12083],{"class":693},[679,60395,60396,60399,60402],{"class":681,"line":2145},[679,60397,60398],{"class":693}," externalLinksTarget: ",[679,60400,60401],{"class":689},"'_blank'",[679,60403,12083],{"class":693},[679,60405,60406,60409,60412,60414,60417,60419,60422],{"class":681,"line":2154},[679,60407,60408],{"class":693}," externalLinksRel: [",[679,60410,60411],{"class":689},"'nofollow'",[679,60413,2797],{"class":693},[679,60415,60416],{"class":689},"'noopener'",[679,60418,2797],{"class":693},[679,60420,60421],{"class":689},"'noreferrer'",[679,60423,44016],{"class":693},[679,60425,60426],{"class":681,"line":2159},[679,60427,60428],{"class":693}," plugins: [\n",[679,60430,60431,60434,60437],{"class":681,"line":2164},[679,60432,60433],{"class":693}," [",[679,60435,60436],{"class":689},"'gridsome-plugin-remark-shiki'",[679,60438,56322],{"class":693},[679,60440,60441,60444],{"class":681,"line":3134},[679,60442,60443],{"class":693}," theme: ",[679,60445,60446],{"class":689},"'nord'\n",[679,60448,60449],{"class":681,"line":3139},[679,60450,60451],{"class":693}," }],\n",[679,60453,60454,60456,60459],{"class":681,"line":3144},[679,60455,60433],{"class":693},[679,60457,60458],{"class":689},"'gridsome-plugin-remark-twitter'",[679,60460,60461],{"class":693},", {}],\n",[679,60463,60464,60466,60469],{"class":681,"line":3149},[679,60465,60433],{"class":693},[679,60467,60468],{"class":689},"'gridsome-plugin-remark-codesandbox'",[679,60470,60471],{"class":693},", {}]\n",[679,60473,60474],{"class":681,"line":3169},[679,60475,60476],{"class":693}," ]\n",[679,60478,60479],{"class":681,"line":3185},[679,60480,985],{"class":693},[679,60482,60483],{"class":681,"line":3194},[679,60484,21405],{"class":693},[679,60486,60487],{"class":681,"line":3199},[679,60488,6481],{"class":693},[4542,60490,9042],{"id":9041},[651,60492,60493],{},"If you follow this blog at all you probably already know this but I love Gridsome and adding CodeSandbox shares to my Markdown really makes me happy. Are there any plugins that you would love to see added to Gridsome? What are you using CodeSandbox for? As always friends...",[651,60495,41105,60496,41109],{},[41107,60497],{},[786,60499,60500],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sLcMm, html code.shiki .sLcMm{--shiki-default:#032F62;--shiki-dark:#DBEDFF;--shiki-sepia:#032F62}html pre.shiki code .sIS5D, html code.shiki .sIS5D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold;--shiki-sepia:#22863A;--shiki-sepia-font-weight:bold}",{"title":674,"searchDepth":790,"depth":790,"links":60502},[60503,60507,60508,60513],{"id":58640,"depth":790,"text":58641,"children":60504},[60505,60506],{"id":58662,"depth":892,"text":58663},{"id":58717,"depth":892,"text":58718},{"id":58736,"depth":790,"text":58737},{"id":58764,"depth":790,"text":58765,"children":60509},[60510,60511,60512],{"id":59021,"depth":892,"text":59022},{"id":60193,"depth":892,"text":60194},{"id":60270,"depth":892,"text":60271},{"id":9041,"depth":790,"text":9042},"In this article, I am going to introduce you to CodeSandbox and tell you a little bit about why I love it so much",{"slug":60516,"date":60517,"published":797,"author":798,"tags":60518,"cover":60519},"gridsome-codesandbox-plugin","2019-05-02T15:30:43.839Z",[44169,43847,58202],"./gridsome-codesandbox-plugin-cover.png",{"title":495,"description":60514},"blog/2019/05/02/gridsome-codesandbox-plugin","xTRlHs9ZYmnaB-bJTOG0fAq8-OQnaD9t4zRCj95jG6A",{"id":60524,"title":492,"body":60525,"description":60678,"extension":793,"meta":60679,"navigation":797,"path":493,"seo":60684,"stem":60685,"__hash__":60686},"content/blog/2019/05/08/codesandbox-custom-theme.md",{"type":648,"value":60526,"toc":60673},[60527,60540,60544,60547,60558,60563,60572,60583,60586,60591,60594,60598,60605,60610,60613,60620,60625,60628,60645,60656,60661,60664,60666,60669],[651,60528,60529,60530,60535,60536,60539],{},"This is going to be a short tutorial today but it's a goodie. In my ",[812,60531,60534],{"href":60532,"rel":60533},"https://www.danvega.dev/blog/2019/05/02/gridsome-codesandbox-plugin",[816],"last article",", I talked about what ",[812,60537,58203],{"href":58209,"rel":60538},[816]," is and why I love it so much. In this article, I am going to give you a quick tip on how to customize the VSCode Theme in CodeSandbox.",[4542,60541,60543],{"id":60542},"visual-studio-code-themes","Visual Studio Code Themes",[651,60545,60546],{},"I think one of the first things developers like to do when they start using VSCode is to customize the theme. VSCode comes with a lot of great built-in themes and my more in the extension marketplace.",[651,60548,52667,60549,60552,60553,664],{},[812,60550,44088],{"href":51472,"rel":60551},[816]," you might already know this but for those of you who don't 🤔 I have a new favorite VSCode Theme and it's called ",[812,60554,60557],{"href":60555,"rel":60556},"https://github.com/robb0wen/synthwave-vscode",[816],"Synthwave 84",[651,60559,60560],{},[660,60561],{"alt":674,"src":60562},"/images/blog/2019/05/08/banner-2dbb41d1-cf07-49a7-8400-fd7c36a1c903.png",[651,60564,60565,60566,60571],{},"This a quick description of where the author (",[812,60567,60570],{"href":60568,"rel":60569},"https://github.com/robb0wen",[816],"Robb Owen",") got his inspiration from",[1004,60573,60574],{},[651,60575,60576,60577,60582],{},"This colour scheme is influenced by the music and the cover artwork of modern Synthwave bands like FM-84, Timecop 1983 and The Midnight. By association, that means I've also taken heavy influence from the excellent retro-tinged artwork of ",[812,60578,60581],{"href":60579,"rel":60580},"https://signalnoise.com/",[816],"James White"," (check out his work, it's awesome).",[651,60584,60585],{},"I have been using it now for almost 2 weeks and I have really fallen in love with it. For some reason it just puts me in a calm/good mood. Here is a quick screenshot of what editing my blog in this theme looks like.",[651,60587,60588],{},[660,60589],{"alt":674,"src":60590},"/images/blog/2019/05/08/2019-05-07_21-32-19-dac6471f-8b43-44e3-9d48-850033cf3769.png",[651,60592,60593],{},"Now that I have this theme on all of my machines it's time to move this over to CodeSandbox. It uses VSCode so this should just work right?",[4542,60595,60597],{"id":60596},"custom-vscode-theme-in-codesandbox","Custom VSCode Theme in CodeSandbox",[651,60599,60600,60601,60604],{},"When I first started using CodeSandbox I thought I was limited to whatever themes were preloaded in the editor. If you open a project in CodeSandbox and go to ",[676,60602,60603],{},"File > Preferences > Color Theme"," you will be presented with a list of Themes.",[651,60606,60607],{},[660,60608],{"alt":674,"src":60609},"/images/blog/2019/05/08/2019-05-07_21-10-20-2c7ccf1f-d37d-4c3f-8d3b-a18a3629e842.png",[651,60611,60612],{},"After scrolling through the list I quickly found out that my new favorite theme was not available. What was I going to do? How would I ever be productive without it? I am joking of course but us developers love to have consistency across our development environments.",[651,60614,60615,60616,60619],{},"After doing some digging I found out that it was possible to bring Synthwave 84 over to CodeSandbox and here is how you do it. From the editor go to ",[676,60617,60618],{},"File > Preferences > CodeSandbox Settings",". You will notice under Editor Theme there are instructions to use a Custom VSCode Theme.",[651,60621,60622],{},[660,60623],{"alt":674,"src":60624},"/images/blog/2019/05/08/2019-05-07_21-18-45-cf7286cc-ca3e-4619-adac-f41efc3b63b8.png",[651,60626,60627],{},"If that is a little hard to read from the screenshot here are the instructions. You can use your own theme from VSCode directly:",[27665,60629,60630,60633,60636,60642],{},[5332,60631,60632],{},"Open VSCode",[5332,60634,60635],{},"Press (CMD or CTRL) + SHIFT + P",[5332,60637,60638,60639],{},"Enter: ",[676,60640,60641],{},"> Developer: Generate Color Scheme From Current Settings",[5332,60643,60644],{},"Copy the contents and paste them here.",[651,60646,60647,60648,60651,60652,60655],{},"After that is done you can close that dialog by just clicking outside of it. To activate the theme go back to your list of Color Themes ",[676,60649,60650],{},"File > Preferences > Color Themes"," and select Custom Theme. If you don't see ",[676,60653,60654],{},"Custom Theme"," as an option you might need to refresh CodeSandbox.",[651,60657,60658],{},[660,60659],{"alt":674,"src":60660},"/images/blog/2019/05/08/2019-05-07_21-27-34-ea6a37f1-d158-4440-aa51-a82e8fdad59d.png",[651,60662,60663],{},"With that my new favorite theme is in CodeSandbox and I can get back to being a semi-productive developer!",[4542,60665,9042],{"id":9041},[651,60667,60668],{},"If you can't tell already I am really loving everything about CodeSandbox right now. I am using it more and more personally and I have introduced it as a tool that we can take advantage of at work and the whole team is on board. Are you using CodeSandbox and if so for what? Also please let me know what some of your favorite themes are. As always...",[651,60670,41105,60671,41109],{},[41107,60672],{},{"title":674,"searchDepth":790,"depth":790,"links":60674},[60675,60676,60677],{"id":60542,"depth":790,"text":60543},{"id":60596,"depth":790,"text":60597},{"id":9041,"depth":790,"text":9042},"In this article, I am going to give you a quick tip on how to customize the VSCode Theme in CodeSandbox.",{"slug":60680,"date":60681,"published":797,"author":798,"tags":60682,"cover":60683},"codesandbox-custom-theme","2019-05-08T01:56:43.680Z",[58202],"./codesandbox-custom-theme-cover.png",{"title":492,"description":60678},"blog/2019/05/08/codesandbox-custom-theme","0o-5zuhbjzTOVtGc3ATY3jvDwqWwEh2BnqbfuPEh9Oc",{"id":60688,"title":489,"body":60689,"description":61217,"extension":793,"meta":61218,"navigation":797,"path":490,"seo":61226,"stem":61227,"__hash__":61228},"content/blog/2019/05/10/what-you-can-learn-from-live-coders.md",{"type":648,"value":60690,"toc":61201},[60691,60694,60697,60721,60725,60728,60731,60734,60737,60740,60743,60747,60756,60759,60762,60765,60769,60772,60775,60778,60782,60785,60788,60791,60795,60798,60801,60805,60808,60811,60815,60818,60822,60825,61150,61162,61166,61169,61172,61181,61185,61192,61194,61197],[651,60692,60693],{},"2019 seems to be the year of the live coder. In this article I am going to tell you what you can learn from live coders. When I was younger I used to love playing video games. I actually do still enjoy them now but if I have free time these days I would much rather spend it working on code. 🤓",[651,60695,60696],{},"I remember the first Nintendo I got for Christmas and I thought it was the coolest thing ever. Every day after school my friend Mike and I use to play whatever game we were trying to conquer at the time. When we got stuck we couldn't turn to Google for the answers (Yes, I am that old). We had to wait for Nintendo Power, or a guide dedicated to that game or as a last result we would just ask our friends the next day at school.",[1031,60698,60699,60707],{},[1034,60700,60701],{},[1037,60702,60703,60705],{},[1040,60704],{},[1040,60706],{},[1050,60708,60709],{},[1037,60710,60711,60716],{},[1055,60712,60713],{},[660,60714],{"alt":674,"src":60715},"/images/blog/2019/05/10/ea16e9bb12986a0b89f8bd5eefeb2d0f-14c63fb2-1532-44e2-a377-551c0ddc5222.jpg",[1055,60717,60718],{},[660,60719],{"alt":674,"src":60720},"/images/blog/2019/05/10/SMguide-ae13d10d-69b3-412a-951c-b6487d447a59.jpg",[4542,60722,60724],{"id":60723},"live-streaming-gamers","Live Streaming Gamers",[651,60726,60727],{},"I bring this up because we are in the middle of an era of gamers who are live streaming. Twitch is the most popular platform for gamers and they saw 2.2 million broadcasters and 15 million daily views in 2018. Some of the top streamers on the platform earn upwards of $300,000 a year while the top earner pulled in over $3.5 million for 2018.",[651,60729,60730],{},"These numbers are just mind blowing to me and shows just how little faith I had in the idea when I first heard about it. My initial reaction was \"Why would anyone want to watch someone else play a video game\"? It's funny to think about my reaction because if I start to think about it I did the same thing when I was younger but on a much smaller scale.",[651,60732,60733],{},"When I first got into video games we didn't have the ability to play with others online. If we wanted to test our skills against the best we had to go to the arcade at the mall. I was really into Street Fighter and Mortal Kombat and used to think I was so good until I got to the mall and found out there were some much better players out there.",[651,60735,60736],{},"After I got beat though I used to stand around the big crowds and watch the really good players play. Not because I didn't have anything better to do but because I wanted to learn why they were so good. What strategies were they using to dominate anyone who played against them.",[651,60738,60739],{},"This was the big picture point I was missing when I first dismissed the idea of live streaming. I am sure there are other reasons to watch these days as a lot of the big earners are just fun to listen to and there is a lot of entertainment value there.",[651,60741,60742],{},"You can probably see where I am going with this but I thought it would be good to relate it to this experience.",[4542,60744,60746],{"id":60745},"live-streaming-coders","Live Streaming Coders",[651,60748,60749,60750,60755],{},"While 99% (not a real % just feels that way) of Twitch is using the platform to live stream gaming there is a small percentage of coders that have been using it to write code. YouTube & Facebook also give us the ability to go live in front of audience with the push of a button. There are also services like ",[812,60751,60754],{"href":60752,"rel":60753},"http://restream.io",[816],"Restream.io"," that allow you to stream to multiple platforms at once.",[651,60757,60758],{},"This is not anything new as there have been developers who have done this before but it just feels like 2019 is where this has really started to take off. There are a handful of developers that I have been watching this year and I have really enjoyed it.",[651,60760,60761],{},"I started telling friends and people at work about this and the immediate reaction I got was \"Why would you want to watch someone else live code?\". Does that sound familiar? Most of the time it's not as dismissive as my initial gaming comments but there is usually an eye roll involved.",[651,60763,60764],{},"The biggest argument I get is why wouldn't these developers just create a video and edit out their mistakes and upload this to YouTube like we have been doing for years now.",[5909,60766,60768],{"id":60767},"problem-solvers","Problem Solvers",[651,60770,60771],{},"Problem solving is a key skill that all developers will need as they get started and it's something they will improve on throughout their career. The ability to break down a problem into smaller problems to solve the overall problem is something I do on a daily basis.",[651,60773,60774],{},"So when I hear comments like \"I would rather just see the finished product\" I have to disagree. There is so much that goes into watching another developer talk through his or her thought process when they are trying to solve a problem.",[651,60776,60777],{},"There is a reason that this is a part of the interview process. You might not always have all the answers but what we are looking for there is your ability to break down a problem into smaller problems and solve those one at a time.",[5909,60779,60781],{"id":60780},"rock-star-developers-they-are-like-us","Rock Star Developers (They are like us)",[651,60783,60784],{},"The other part of watching someone live code is understanding that we all have the same struggles. This isn't always the case but usually the developers who are live coding have a lot of experience under their belt.",[651,60786,60787],{},"I think we hold some developers to a higher standard and think that they don't have the same struggles as we do. So when we see some of these rockstar developers that we all look up to doing the same things that we do, it really helps give us some confidence in our own abilities.",[651,60789,60790],{},"When I see someone unsure of a particular API or not sure how to do something and watch them Google for the answer I think it makes all of us realize that we are all in the same boat. Sure there are probably some really brilliant engineers out there who can code for hours without looking anything up but the rest of us just don't work like that.",[5909,60792,60794],{"id":60793},"tips-and-tricks","Tips and Tricks",[651,60796,60797],{},"I think another underrated feature of watching live coders is picking up little tips and tricks that developers take for granted. You might start watching because of the headlines but you will stay for the tips and tricks. While a developer might have a live session to build a new app from start to finish I always find the small things really helpful.",[651,60799,60800],{},"I like learning about the different tools in a developers toolbox. Even if its a tool I have heard of or even used before seeing how someone else uses it might inspire me to use it in a way I wouldn't of thought of. I also love seeing how developers setup their IDEs or what themes and extensions they are using.",[5909,60802,60804],{"id":60803},"live-or-recorded","Live or Recorded",[651,60806,60807],{},"All of these benefits can also be gained through watching a recording. I need to make sure that I am very clear about this. I don't mean that you need to watch them as they are live but there is a big advantage to watching them live and that is the interaction. A lot of live coders are very interactive with the audience and use them to drive tutorials and discussions so don't be afraid to speak of.",[651,60809,60810],{},"When it comes to live coding you can certainly watch a recording but all I am referring to is that the developer didn't edit down the video to be perfect. When I am talking about live coding I want to see developers talk through problems and stumble just like we all do.",[4542,60812,60814],{"id":60813},"live-coding","Live Coding",[651,60816,60817],{},"Now that you know why I love watching developers live code I want to leave you with some next steps. First we are going to start with some developers who are live coding regularly and then I will leave you with some resources if you're interested in live streaming yourself.",[5909,60819,60821],{"id":60820},"developers-to-follow","Developers to follow",[651,60823,60824],{},"This is a list of developers that I have either watched at one point or watch on a regular basis. This list is in no particular order and I think everyone below is really amazing and I appreciate all of their efforts. These developers take time out of their day to help others so if you have a chance reach out to them and thank them for everything that they do.",[1031,60826,60827,60840],{},[1034,60828,60829],{},[1037,60830,60831,60833,60835,60838],{},[1040,60832,23058],{},[1040,60834,51474],{},[1040,60836,60837],{},"Twitch",[1040,60839,15432],{},[1050,60841,60842,60861,60880,60903,60922,60941,60960,60979,60998,61017,61036,61055,61074,61093,61112,61131],{},[1037,60843,60844,60847,60853,60859],{},[1055,60845,60846],{},"Chris Sevilleja",[1055,60848,60849],{},[812,60850,60851],{"href":60851,"rel":60852},"https://twitter.com/chrisoncode",[816],[1055,60854,60855],{},[812,60856,60857],{"href":60857,"rel":60858},"https://www.twitch.tv/chrisoncode",[816],[1055,60860],{},[1037,60862,60863,60866,60872,60878],{},[1055,60864,60865],{},"Brian Clark",[1055,60867,60868],{},[812,60869,60870],{"href":60870,"rel":60871},"https://twitter.com/_clarkio",[816],[1055,60873,60874],{},[812,60875,60876],{"href":60876,"rel":60877},"https://www.twitch.tv/clarkio",[816],[1055,60879],{},[1037,60881,60882,60885,60891,60897],{},[1055,60883,60884],{},"Fun Fun Function",[1055,60886,60887],{},[812,60888,60889],{"href":60889,"rel":60890},"https://twitter.com/mpjme",[816],[1055,60892,60893],{},[812,60894,60895],{"href":60895,"rel":60896},"https://www.twitch.tv/funfunfunction",[816],[1055,60898,60899],{},[812,60900,60901],{"href":60901,"rel":60902},"https://www.youtube.com/funfunfunction",[816],[1037,60904,60905,60908,60914,60920],{},[1055,60906,60907],{},"Suz Hinton",[1055,60909,60910],{},[812,60911,60912],{"href":60912,"rel":60913},"https://twitter.com/noopkat",[816],[1055,60915,60916],{},[812,60917,60918],{"href":60918,"rel":60919},"https://www.twitch.tv/noopkat",[816],[1055,60921],{},[1037,60923,60924,60927,60933,60939],{},[1055,60925,60926],{},"Aaron Frost",[1055,60928,60929],{},[812,60930,60931],{"href":60931,"rel":60932},"https://twitter.com/aaronfrost",[816],[1055,60934,60935],{},[812,60936,60937],{"href":60937,"rel":60938},"https://www.twitch.tv/frostydev",[816],[1055,60940],{},[1037,60942,60943,60946,60952,60954],{},[1055,60944,60945],{},"Daniel Shiffman",[1055,60947,60948],{},[812,60949,60950],{"href":60950,"rel":60951},"https://twitter.com/shiffman",[816],[1055,60953],{},[1055,60955,60956],{},[812,60957,60958],{"href":60958,"rel":60959},"https://www.youtube.com/user/shiffman",[816],[1037,60961,60962,60965,60971,60977],{},[1055,60963,60964],{},"Jeff Fritz",[1055,60966,60967],{},[812,60968,60969],{"href":60969,"rel":60970},"https://twitter.com/csharpfritz",[816],[1055,60972,60973],{},[812,60974,60975],{"href":60975,"rel":60976},"https://www.twitch.tv/csharpfritz",[816],[1055,60978],{},[1037,60980,60981,60984,60990,60996],{},[1055,60982,60983],{},"Steve Smith",[1055,60985,60986],{},[812,60987,60988],{"href":60988,"rel":60989},"https://twitter.com/ardalis",[816],[1055,60991,60992],{},[812,60993,60994],{"href":60994,"rel":60995},"https://www.twitch.tv/ardalis",[816],[1055,60997],{},[1037,60999,61000,61003,61009,61015],{},[1055,61001,61002],{},"Brendan Enrick",[1055,61004,61005],{},[812,61006,61007],{"href":61007,"rel":61008},"https://twitter.com/brendoneus",[816],[1055,61010,61011],{},[812,61012,61013],{"href":61013,"rel":61014},"https://www.twitch.tv/DevChatter",[816],[1055,61016],{},[1037,61018,61019,61022,61028,61034],{},[1055,61020,61021],{},"Ted Young",[1055,61023,61024],{},[812,61025,61026],{"href":61026,"rel":61027},"https://twitter.com/jitterted",[816],[1055,61029,61030],{},[812,61031,61032],{"href":61032,"rel":61033},"https://www.twitch.tv/jitterted",[816],[1055,61035],{},[1037,61037,61038,61041,61047,61049],{},[1055,61039,61040],{},"Kent C Dodds",[1055,61042,61043],{},[812,61044,61045],{"href":61045,"rel":61046},"https://twitter.com/kentcdodds",[816],[1055,61048],{},[1055,61050,61051],{},[812,61052,61053],{"href":61053,"rel":61054},"https://www.youtube.com/channel/UCz-BYvuntVRt_VpfR6FKXJw",[816],[1037,61056,61057,61060,61066,61068],{},[1055,61058,61059],{},"Scott Tolinkski",[1055,61061,61062],{},[812,61063,61064],{"href":61064,"rel":61065},"https://twitter.com/stolinski",[816],[1055,61067],{},[1055,61069,61070],{},[812,61071,61072],{"href":61072,"rel":61073},"https://www.youtube.com/c/leveluptuts",[816],[1037,61075,61076,61079,61085,61091],{},[1055,61077,61078],{},"Ed Charbeneau",[1055,61080,61081],{},[812,61082,61083],{"href":61083,"rel":61084},"https://twitter.com/EdCharbeneau",[816],[1055,61086,61087],{},[812,61088,61089],{"href":61089,"rel":61090},"https://www.twitch.tv/edcharbeneau",[816],[1055,61092],{},[1037,61094,61095,61098,61104,61110],{},[1055,61096,61097],{},"Alec Dilanchian",[1055,61099,61100],{},[812,61101,61102],{"href":61102,"rel":61103},"https://twitter.com/alec_dilanchian",[816],[1055,61105,61106],{},[812,61107,61108],{"href":61108,"rel":61109},"https://www.twitch.tv/pixelogicdev",[816],[1055,61111],{},[1037,61113,61114,61117,61123,61129],{},[1055,61115,61116],{},"Nick Larsen",[1055,61118,61119],{},[812,61120,61121],{"href":61121,"rel":61122},"https://twitter.com/fody",[816],[1055,61124,61125],{},[812,61126,61127],{"href":61127,"rel":61128},"https://www.twitch.tv/nick_larsen",[816],[1055,61130],{},[1037,61132,61133,61136,61142,61144],{},[1055,61134,61135],{},"Coding Garden (CJ)",[1055,61137,61138],{},[812,61139,61140],{"href":61140,"rel":61141},"https://twitter.com/coding_garden",[816],[1055,61143],{},[1055,61145,61146],{},[812,61147,61148],{"href":61148,"rel":61149},"https://www.youtube.com/c/CodingGardenWithCJ",[816],[651,61151,61152,61153,61155,61156,61161],{},"This is ",[2939,61154,10922],{}," a full list so if you think I have left someone off who should be recognized please reach out and I will get this updated. I would also like to mention the ",[812,61157,61160],{"href":61158,"rel":61159},"https://www.twitch.tv/team/livecoders",[816],"Live Coders Twitch Group"," which has a really nice collection of developers who are live streaming so check them out.",[4542,61163,61165],{"id":61164},"live-streaming-setup","Live Streaming Setup",[651,61167,61168],{},"Live streaming is one of those things that I wish I had more time to do. I have a list of about 10 things that I would love to walk through not to mention I have a ton of blog posts that could transfer to video.",[651,61170,61171],{},"I have done a total of 1 live stream so I am not the best person to ask for live streaming setup advice but I do have a couple resources for you.",[651,61173,61174,61175,61180],{},"Suz Hinton (who is on the list above) is an amazing developer who does live streaming on Twitch. She put together an amazing article on her ",[812,61176,61179],{"href":61177,"rel":61178},"https://medium.com/@suzhinton/my-twitch-live-coding-setup-b2516672fb21",[816],"Live Coding Setup"," that is a must read if you're looking to get into this space.",[5909,61182,61184],{"id":61183},"ecamm-live","Ecamm Live",[651,61186,61187,61188,61191],{},"I also want to mention ",[812,61189,61184],{"href":43604,"rel":61190},[816]," which is the all in one live streaming production platform for the mac. If you're on a mac this an amazing piece of software with some really great features. I used it for the 1 broadcast I did but you can also use it for recording videos. If you're interested in hearing more about this reach out to me and let me know. I would love to do a detailed write up on Ecamm Live and the awesome team behind it.",[4542,61193,9042],{"id":9041},[651,61195,61196],{},"If you can't tell from this article I really love everything you can learn from live coders. I am curious about your thoughts on developers live streaming. What do you get out of it? Who are some of your favorites? If I were to start live streaming what would you like to see from me? Until next time friends...",[651,61198,41105,61199,41109],{},[41107,61200],{},{"title":674,"searchDepth":790,"depth":790,"links":61202},[61203,61204,61210,61213,61216],{"id":60723,"depth":790,"text":60724},{"id":60745,"depth":790,"text":60746,"children":61205},[61206,61207,61208,61209],{"id":60767,"depth":892,"text":60768},{"id":60780,"depth":892,"text":60781},{"id":60793,"depth":892,"text":60794},{"id":60803,"depth":892,"text":60804},{"id":60813,"depth":790,"text":60814,"children":61211},[61212],{"id":60820,"depth":892,"text":60821},{"id":61164,"depth":790,"text":61165,"children":61214},[61215],{"id":61183,"depth":892,"text":61184},{"id":9041,"depth":790,"text":9042},"2019 seems to be the year of the live coder. In this article I am going to tell you what you can learn from live coders. ",{"slug":61219,"date":61220,"modified":61221,"published":797,"author":798,"tags":61222,"cover":61225},"what-you-can-learn-from-live-coders","2019-05-10T18:42:50.066Z","2019-05-10T19:40:00.066Z",[61223,61224],"twitch","youtube","./live-coders-cover.png",{"title":489,"description":61217},"blog/2019/05/10/what-you-can-learn-from-live-coders","Sa_h3l7cNytQougd_G_cbiaiJyt0zYxMD9TvEgn7QUQ",{"id":61230,"title":486,"body":61231,"description":61929,"extension":793,"meta":61930,"navigation":797,"path":487,"seo":61936,"stem":61937,"__hash__":61938},"content/blog/2019/05/15/run-vue-visual-studio-code.md",{"type":648,"value":61232,"toc":61919},[61233,61239,61244,61248,61260,61540,61542,61548,61552,61555,61558,61562,61565,61571,61677,61680,61696,61703,61718,61802,61806,61824,61829,61832,61837,61849,61854,61857,61862,61867,61871,61874,61880,61883,61899,61902,61907,61909,61912,61916],[651,61234,61235,61236,61238],{},"In this tutorial, I am going to show you 3 ways to run your VueJS applications from Visual Studio Code. This will also work for any NPM project that has a ",[676,61237,55253],{}," but I am going to focus on Vue for this article. I have found that developers who are new to both NPM & Vue have some difficulty getting started so hopefully this will help.",[651,61240,61241],{},[660,61242],{"alt":674,"src":61243},"/images/blog/2019/05/15/2019-05-15_07-33-46-8ad84b96-bc16-4d6e-902b-aa83d9fac58e.png",[4542,61245,61247],{"id":61246},"node-npm-packagejson","Node, NPM & package.json",[651,61249,61250,61251,61253,61254,61256,61257,664],{},"If you're new to Node & NPM the good news is you don't need to know everything to get up and running. Whenever you see a ",[676,61252,55253],{}," just know that this file is there to help you manage your project. In this file, you will find metadata about the project, packages that this project depends on and scripts that are available to run. Here is a simple ",[676,61255,55253],{}," of a project created using the ",[812,61258,57067],{"href":57065,"rel":61259},[816],[669,61261,61263],{"className":25132,"code":61262,"language":25134,"meta":674,"style":674},"{\n \"name\": \"hello-world\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"serve\": \"vue-cli-service serve\",\n \"build\": \"vue-cli-service build\",\n \"lint\": \"vue-cli-service lint\",\n \"test:e2e\": \"vue-cli-service test:e2e\",\n \"test:unit\": \"vue-cli-service test:unit\"\n },\n \"dependencies\": {\n \"core-js\": \"^2.6.5\",\n \"vue\": \"^2.6.10\"\n },\n \"devDependencies\": {\n \"@vue/cli-plugin-babel\": \"^3.7.0\",\n \"@vue/cli-plugin-e2e-cypress\": \"^3.7.0\",\n \"@vue/cli-plugin-eslint\": \"^3.7.0\",\n \"@vue/cli-plugin-unit-mocha\": \"^3.7.0\",\n \"@vue/cli-service\": \"^3.7.0\",\n \"@vue/test-utils\": \"1.0.0-beta.29\",\n \"babel-eslint\": \"^10.0.1\",\n \"chai\": \"^4.1.2\",\n \"eslint\": \"^5.16.0\",\n \"eslint-plugin-vue\": \"^5.0.0\",\n \"vue-template-compiler\": \"^2.5.21\"\n }\n}\n",[676,61264,61265,61269,61280,61290,61301,61307,61319,61330,61341,61353,61363,61367,61373,61385,61395,61399,61406,61418,61429,61440,61451,61462,61474,61486,61498,61510,61522,61532,61536],{"__ignoreMap":674},[679,61266,61267],{"class":681,"line":682},[679,61268,28448],{"class":693},[679,61270,61271,61273,61275,61278],{"class":681,"line":790},[679,61272,44594],{"class":689},[679,61274,4282],{"class":693},[679,61276,61277],{"class":689},"\"hello-world\"",[679,61279,12083],{"class":693},[679,61281,61282,61284,61286,61288],{"class":681,"line":892},[679,61283,44604],{"class":689},[679,61285,4282],{"class":693},[679,61287,58813],{"class":689},[679,61289,12083],{"class":693},[679,61291,61292,61295,61297,61299],{"class":681,"line":901},[679,61293,61294],{"class":689}," \"private\"",[679,61296,4282],{"class":693},[679,61298,3441],{"class":931},[679,61300,12083],{"class":693},[679,61302,61303,61305],{"class":681,"line":909},[679,61304,30870],{"class":689},[679,61306,28468],{"class":693},[679,61308,61309,61312,61314,61317],{"class":681,"line":918},[679,61310,61311],{"class":689}," \"serve\"",[679,61313,4282],{"class":693},[679,61315,61316],{"class":689},"\"vue-cli-service serve\"",[679,61318,12083],{"class":693},[679,61320,61321,61323,61325,61328],{"class":681,"line":935},[679,61322,30901],{"class":689},[679,61324,4282],{"class":693},[679,61326,61327],{"class":689},"\"vue-cli-service build\"",[679,61329,12083],{"class":693},[679,61331,61332,61334,61336,61339],{"class":681,"line":944},[679,61333,30925],{"class":689},[679,61335,4282],{"class":693},[679,61337,61338],{"class":689},"\"vue-cli-service lint\"",[679,61340,12083],{"class":693},[679,61342,61343,61346,61348,61351],{"class":681,"line":959},[679,61344,61345],{"class":689}," \"test:e2e\"",[679,61347,4282],{"class":693},[679,61349,61350],{"class":689},"\"vue-cli-service test:e2e\"",[679,61352,12083],{"class":693},[679,61354,61355,61358,61360],{"class":681,"line":964},[679,61356,61357],{"class":689}," \"test:unit\"",[679,61359,4282],{"class":693},[679,61361,61362],{"class":689},"\"vue-cli-service test:unit\"\n",[679,61364,61365],{"class":681,"line":977},[679,61366,28483],{"class":693},[679,61368,61369,61371],{"class":681,"line":982},[679,61370,45075],{"class":689},[679,61372,28468],{"class":693},[679,61374,61375,61378,61380,61383],{"class":681,"line":988},[679,61376,61377],{"class":689}," \"core-js\"",[679,61379,4282],{"class":693},[679,61381,61382],{"class":689},"\"^2.6.5\"",[679,61384,12083],{"class":693},[679,61386,61387,61390,61392],{"class":681,"line":993},[679,61388,61389],{"class":689}," \"vue\"",[679,61391,4282],{"class":693},[679,61393,61394],{"class":689},"\"^2.6.10\"\n",[679,61396,61397],{"class":681,"line":2129},[679,61398,28483],{"class":693},[679,61400,61401,61404],{"class":681,"line":2140},[679,61402,61403],{"class":689}," \"devDependencies\"",[679,61405,28468],{"class":693},[679,61407,61408,61411,61413,61416],{"class":681,"line":2145},[679,61409,61410],{"class":689}," \"@vue/cli-plugin-babel\"",[679,61412,4282],{"class":693},[679,61414,61415],{"class":689},"\"^3.7.0\"",[679,61417,12083],{"class":693},[679,61419,61420,61423,61425,61427],{"class":681,"line":2154},[679,61421,61422],{"class":689}," \"@vue/cli-plugin-e2e-cypress\"",[679,61424,4282],{"class":693},[679,61426,61415],{"class":689},[679,61428,12083],{"class":693},[679,61430,61431,61434,61436,61438],{"class":681,"line":2159},[679,61432,61433],{"class":689}," \"@vue/cli-plugin-eslint\"",[679,61435,4282],{"class":693},[679,61437,61415],{"class":689},[679,61439,12083],{"class":693},[679,61441,61442,61445,61447,61449],{"class":681,"line":2164},[679,61443,61444],{"class":689}," \"@vue/cli-plugin-unit-mocha\"",[679,61446,4282],{"class":693},[679,61448,61415],{"class":689},[679,61450,12083],{"class":693},[679,61452,61453,61456,61458,61460],{"class":681,"line":3134},[679,61454,61455],{"class":689}," \"@vue/cli-service\"",[679,61457,4282],{"class":693},[679,61459,61415],{"class":689},[679,61461,12083],{"class":693},[679,61463,61464,61467,61469,61472],{"class":681,"line":3139},[679,61465,61466],{"class":689}," \"@vue/test-utils\"",[679,61468,4282],{"class":693},[679,61470,61471],{"class":689},"\"1.0.0-beta.29\"",[679,61473,12083],{"class":693},[679,61475,61476,61479,61481,61484],{"class":681,"line":3144},[679,61477,61478],{"class":689}," \"babel-eslint\"",[679,61480,4282],{"class":693},[679,61482,61483],{"class":689},"\"^10.0.1\"",[679,61485,12083],{"class":693},[679,61487,61488,61491,61493,61496],{"class":681,"line":3149},[679,61489,61490],{"class":689}," \"chai\"",[679,61492,4282],{"class":693},[679,61494,61495],{"class":689},"\"^4.1.2\"",[679,61497,12083],{"class":693},[679,61499,61500,61503,61505,61508],{"class":681,"line":3169},[679,61501,61502],{"class":689}," \"eslint\"",[679,61504,4282],{"class":693},[679,61506,61507],{"class":689},"\"^5.16.0\"",[679,61509,12083],{"class":693},[679,61511,61512,61515,61517,61520],{"class":681,"line":3185},[679,61513,61514],{"class":689}," \"eslint-plugin-vue\"",[679,61516,4282],{"class":693},[679,61518,61519],{"class":689},"\"^5.0.0\"",[679,61521,12083],{"class":693},[679,61523,61524,61527,61529],{"class":681,"line":3194},[679,61525,61526],{"class":689}," \"vue-template-compiler\"",[679,61528,4282],{"class":693},[679,61530,61531],{"class":689},"\"^2.5.21\"\n",[679,61533,61534],{"class":681,"line":3199},[679,61535,21405],{"class":693},[679,61537,61538],{"class":681,"line":3212},[679,61539,996],{"class":693},[4542,61541,41445],{"id":42277},[651,61543,61544,61545,61547],{},"Now that you have a brief introduction to what information the ",[676,61546,55253],{}," contains it's time to learn how to run our VueJS applications from Visual Studio Code.",[5909,61549,61551],{"id":61550},"using-the-integrated-terminal","Using the integrated terminal",[651,61553,61554],{},"The first way to run your VueJS applications from Visual Studio Code is the one you might have already learned about and that is by using the integrated terminal. If you're opening up a new terminal/command prompt to run your projects this will speed things up and bring everything back to Visual Studio Code.",[651,61556,61557],{},"With your project open in Visual Studio Code hit the keyboard shortcut (cmd/ctrl) + `. The backtick is located right above the tab key on your keyboard. This will open the integrated terminal and from there you can run any script for your project.",[5909,61559,61561],{"id":61560},"what-commands-can-i-run","What commands can I run?",[651,61563,61564],{},"Now this is often a point of confusion for those new to VueJS and NPM in general. What is the command that I type to start my application? What is the command that I type to run my tests? After you have been working in Vue for a while these will become second nature but there are cases where you might inherit a project that has custom scripts.",[651,61566,61567,61568,61570],{},"The easiest way to find out what scripts are available is to open the ",[676,61569,55253],{}," and look in the scripts block.",[669,61572,61574],{"className":25132,"code":61573,"language":25134,"meta":674,"style":674},"{\n \"name\": \"hello-world\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"serve\": \"vue-cli-service serve\",\n \"build\": \"vue-cli-service build\",\n \"lint\": \"vue-cli-service lint\",\n \"test:e2e\": \"vue-cli-service test:e2e\",\n \"test:unit\": \"vue-cli-service test:unit\"\n },\n ...\n}\n",[676,61575,61576,61580,61590,61600,61610,61616,61626,61636,61646,61656,61664,61668,61673],{"__ignoreMap":674},[679,61577,61578],{"class":681,"line":682},[679,61579,28448],{"class":693},[679,61581,61582,61584,61586,61588],{"class":681,"line":790},[679,61583,44594],{"class":689},[679,61585,4282],{"class":693},[679,61587,61277],{"class":689},[679,61589,12083],{"class":693},[679,61591,61592,61594,61596,61598],{"class":681,"line":892},[679,61593,44604],{"class":689},[679,61595,4282],{"class":693},[679,61597,58813],{"class":689},[679,61599,12083],{"class":693},[679,61601,61602,61604,61606,61608],{"class":681,"line":901},[679,61603,61294],{"class":689},[679,61605,4282],{"class":693},[679,61607,3441],{"class":931},[679,61609,12083],{"class":693},[679,61611,61612,61614],{"class":681,"line":909},[679,61613,30870],{"class":689},[679,61615,28468],{"class":693},[679,61617,61618,61620,61622,61624],{"class":681,"line":918},[679,61619,61311],{"class":689},[679,61621,4282],{"class":693},[679,61623,61316],{"class":689},[679,61625,12083],{"class":693},[679,61627,61628,61630,61632,61634],{"class":681,"line":935},[679,61629,30901],{"class":689},[679,61631,4282],{"class":693},[679,61633,61327],{"class":689},[679,61635,12083],{"class":693},[679,61637,61638,61640,61642,61644],{"class":681,"line":944},[679,61639,30925],{"class":689},[679,61641,4282],{"class":693},[679,61643,61338],{"class":689},[679,61645,12083],{"class":693},[679,61647,61648,61650,61652,61654],{"class":681,"line":959},[679,61649,61345],{"class":689},[679,61651,4282],{"class":693},[679,61653,61350],{"class":689},[679,61655,12083],{"class":693},[679,61657,61658,61660,61662],{"class":681,"line":964},[679,61659,61357],{"class":689},[679,61661,4282],{"class":693},[679,61663,61362],{"class":689},[679,61665,61666],{"class":681,"line":977},[679,61667,28483],{"class":693},[679,61669,61670],{"class":681,"line":982},[679,61671,61672],{"class":685}," ...\n",[679,61674,61675],{"class":681,"line":988},[679,61676,996],{"class":693},[651,61678,61679],{},"Looking at this I can tell right away that the 5 scripts I have available to me are:",[5316,61681,61682,61685,61687,61690,61693],{},[5332,61683,61684],{},"serve",[5332,61686,23612],{},[5332,61688,61689],{},"lint",[5332,61691,61692],{},"test:e2e",[5332,61694,61695],{},"test:unit",[651,61697,61698,61699,61702],{},"So if I want to run any of these I simply type ",[676,61700,61701],{},"npm run serve"," or the name of the script you want to run. The serve script is the one that will start your application up in development mode. The scripts block above is what a typical VueJS project will look when you create it using the Vue CLI and selecting both Unit & End to End testing.",[651,61704,61705,61706,61710,61711,61714,61715,664],{},"There is a chance that if you're working on an existing project there could be a bunch of custom scripts. ",[812,61707,61709],{"href":56818,"rel":61708},[816],"In a recent article",", I documented the process that I used to create a new post generator that I can run to add a new blog post. In that case, I have a ",[676,61712,61713],{},"newpost"," script so to run that I just run the command ",[676,61716,61717],{},"npm run newpost",[669,61719,61721],{"className":25132,"code":61720,"language":25134,"meta":674,"style":674},"{\n \"name\": \"danvega-dev\",\n \"private\": true,\n \"scripts\": {\n \"build\": \"gridsome build\",\n \"develop\": \"gridsome develop\",\n \"explore\": \"gridsome explore\",\n \"newpost\": \"node ./scripts/newpost.js\"\n },\n",[676,61722,61723,61727,61738,61748,61754,61765,61777,61789,61798],{"__ignoreMap":674},[679,61724,61725],{"class":681,"line":682},[679,61726,28448],{"class":693},[679,61728,61729,61731,61733,61736],{"class":681,"line":790},[679,61730,44594],{"class":689},[679,61732,4282],{"class":693},[679,61734,61735],{"class":689},"\"danvega-dev\"",[679,61737,12083],{"class":693},[679,61739,61740,61742,61744,61746],{"class":681,"line":892},[679,61741,61294],{"class":689},[679,61743,4282],{"class":693},[679,61745,3441],{"class":931},[679,61747,12083],{"class":693},[679,61749,61750,61752],{"class":681,"line":901},[679,61751,30870],{"class":689},[679,61753,28468],{"class":693},[679,61755,61756,61758,61760,61763],{"class":681,"line":909},[679,61757,30901],{"class":689},[679,61759,4282],{"class":693},[679,61761,61762],{"class":689},"\"gridsome build\"",[679,61764,12083],{"class":693},[679,61766,61767,61770,61772,61775],{"class":681,"line":918},[679,61768,61769],{"class":689}," \"develop\"",[679,61771,4282],{"class":693},[679,61773,61774],{"class":689},"\"gridsome develop\"",[679,61776,12083],{"class":693},[679,61778,61779,61782,61784,61787],{"class":681,"line":935},[679,61780,61781],{"class":689}," \"explore\"",[679,61783,4282],{"class":693},[679,61785,61786],{"class":689},"\"gridsome explore\"",[679,61788,12083],{"class":693},[679,61790,61791,61794,61796],{"class":681,"line":944},[679,61792,61793],{"class":689}," \"newpost\"",[679,61795,4282],{"class":693},[679,61797,55268],{"class":689},[679,61799,61800],{"class":681,"line":959},[679,61801,28483],{"class":693},[5909,61803,61805],{"id":61804},"tasks-run-task","Tasks: Run Task",[651,61807,61808,61809,61812,61813,51393,61816,61819,61820,61823],{},"So that's how we run scripts from the command line but not everyone loves typing out commands every time they want to run a project. With your project open the command palette by using the menu item ",[676,61810,61811],{},"View > Command Palette"," or by using the keyboard shortcut ",[676,61814,61815],{},"Shift + CMD + P",[676,61817,61818],{},"Shift + CTRL + P"," on Windows. From there type ",[676,61821,61822],{},"Tasks"," and click on the Run Task command.",[651,61825,61826],{},[660,61827],{"alt":674,"src":61828},"/images/blog/2019/05/15/2019-05-15_09-41-08-caa0581d-abfc-4bf2-9693-907857c6bb79.png",[651,61830,61831],{},"This will examine your project and give you a list of the available scripts to run.",[651,61833,61834],{},[660,61835],{"alt":674,"src":61836},"/images/blog/2019/05/15/2019-05-15_09-44-31-d411391e-3095-46a9-bdbc-4842f3950d12.png",[651,61838,61839,61840,61843,61844,664],{},"You can click on ",[676,61841,61842],{},"npm:serve"," or you can start typing the word serve and hit enter when it's selected to run it without using your mouse. If you see the following options you can go ",[812,61845,61848],{"href":61846,"rel":61847},"https://code.visualstudio.com/docs/editor/tasks#vscode",[816],"here to learn more about scanning the task output",[651,61850,61851],{},[660,61852],{"alt":674,"src":61853},"/images/blog/2019/05/15/2019-05-15_09-46-03-ff4e4351-a423-4916-80e6-b2d561204651.png",[651,61855,61856],{},"If you hit continue without scanning the task output Visual Studio Code will run your task. This opens up a terminal for you and runs the script.",[651,61858,61859],{},[660,61860],{"alt":674,"src":61861},"/images/blog/2019/05/15/2019-05-15_09-49-10-c43e1838-3da5-4b05-9dc8-0b11cf078a4f.png",[651,61863,61864,664],{},[7300,61865,61866],{},"The next time you run the task you won't be asked about scanning the output and this process becomes very quick",[5909,61868,61870],{"id":61869},"npm-scripts-explorer","NPM Scripts Explorer",[651,61872,61873],{},"If you follow me on Twitter I put this tweet out promising a tip that you might not know existed.",[651,61875,61876],{},[812,61877,61878],{"href":61878,"rel":61879},"https://twitter.com/therealdanvega/status/1128628100898619392",[816],[651,61881,61882],{},"To enable this go into your Visual Studio Code settings and add the following setting",[669,61884,61886],{"className":25132,"code":61885,"language":25134,"meta":674,"style":674},"\"npm.enableScriptExplorer\": true,\n",[676,61887,61888],{"__ignoreMap":674},[679,61889,61890,61893,61895,61897],{"class":681,"line":682},[679,61891,61892],{"class":689},"\"npm.enableScriptExplorer\"",[679,61894,4282],{"class":693},[679,61896,3441],{"class":931},[679,61898,12083],{"class":693},[651,61900,61901],{},"With your project open you will now have a NPM Scripts Explorer in the sidebar. If you click on the play icon next to the script name it will run the script for you, how awesome is that!",[651,61903,61904],{},[660,61905],{"alt":674,"src":61906},"/images/blog/2019/05/15/2019-05-15_09-57-14-84d9686f-8bf7-4263-b31a-ed5d7af74d62.png",[4542,61908,9042],{"id":9041},[651,61910,61911],{},"I hope this article was helpful in identifying and running your VueJS applications from Visual Studio Code. There is usually more than one way to accomplish a task and not all of us have the same preferences. As always friends...",[651,61913,41105,61914,41109],{},[41107,61915],{},[786,61917,61918],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}",{"title":674,"searchDepth":790,"depth":790,"links":61920},[61921,61922,61928],{"id":61246,"depth":790,"text":61247},{"id":42277,"depth":790,"text":41445,"children":61923},[61924,61925,61926,61927],{"id":61550,"depth":892,"text":61551},{"id":61560,"depth":892,"text":61561},{"id":61804,"depth":892,"text":61805},{"id":61869,"depth":892,"text":61870},{"id":9041,"depth":790,"text":9042},"In this tutorial, I am going to show you 3 ways to run your VueJS applications from Visual Studio Code. ",{"slug":61931,"date":61932,"published":797,"author":798,"tags":61933,"cover":61935},"run-vue-visual-studio-code","2019-05-15T14:19:36.459Z",[44169,24568,61934,25134],"vscode","./run-vue-visual-studio-code-cover.png",{"title":486,"description":61929},"blog/2019/05/15/run-vue-visual-studio-code","scIE7fIFfHrd6xWIX6AralLxSAspBDu1vX_oAwLyl8c",{"id":61940,"title":483,"body":61941,"description":62673,"extension":793,"meta":62674,"navigation":797,"path":484,"seo":62680,"stem":62681,"__hash__":62682},"content/blog/2019/05/17/vue-cli-error.md",{"type":648,"value":61942,"toc":62668},[61943,61946,61950,61964,62431,62448,62454,62458,62466,62479,62516,62519,62600,62610,62648,62656,62658,62661,62665],[651,61944,61945],{},"This morning I ran into an issue creating a new Vue project using the Vue CLI. The strange thing about this is that I just created a new project yesterday and it ran fine. I did some digging around and this seems to be affecting a lot of users so I figured I would throw together a quick post about.",[4542,61947,61949],{"id":61948},"creating-running-a-new-vuejs-project","Creating & Running a new VueJS Project",[651,61951,61952,61953,61956,61957,61959,61960,61963],{},"I started out my morning by creating a new project using the ",[812,61954,57067],{"href":57065,"rel":61955},[816],". After I created the project I changed into that directory and typed the command ",[676,61958,61701],{}," which calls ",[676,61961,61962],{},"vue-cli-service serve",". A few seconds after trying to startup I received the following error in the terminal.",[669,61965,61967],{"className":5851,"code":61966,"language":5853,"meta":674,"style":674}," INFO Starting development server...\n ERROR ValidationError: webpack Dev Server Invalid Options\n\noptions.clientLogLevel should be {String} and equal to one of the allowed values\n\n [ 'info', 'warn', 'error', 'debug', 'trace', 'silent' ]\n\n (https://webpack.js.org/configuration/dev-server/#devserverclientloglevel)\n\nValidationError: webpack Dev Server Invalid Options\n\noptions.clientLogLevel should be {String} and equal to one of the allowed values\n\n [ 'info', 'warn', 'error', 'debug', 'trace', 'silent' ]\n\n (https://webpack.js.org/configuration/dev-server/#devserverclientloglevel)\n\n at validateOptions (/Users/vega/dev/vue/hello-vue-cli/node_modules/schema-utils/src/validateOptions.js:32:11)\n at new Server (/Users/vega/dev/vue/hello-vue-cli/node_modules/webpack-dev-server/lib/Server.js:71:5)\n at serve (/Users/vega/dev/vue/hello-vue-cli/node_modules/@vue/cli-service/lib/commands/serve.js:138:20)\n at process._tickCallback (internal/process/next_tick.js:68:7)\n at Function.Module.runMain (internal/modules/cjs/loader.js:745:11)\n at startup (internal/bootstrap/node.js:283:19)\n at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)\nnpm ERR! code ELIFECYCLE\nnpm ERR! errno 1\nnpm ERR! hello-vue-cli@0.1.0 serve: `vue-cli-service serve`\nnpm ERR! Exit status 1\nnpm ERR!\nnpm ERR! Failed at the hello-vue-cli@0.1.0 serve script.\nnpm ERR! This is probably not a problem with npm. There is likely additional logging output above.\n\nnpm ERR! A complete log of this run can be found in:\nnpm ERR! /Users/vega/.npm/_logs/2019-05-17T15_05_54_552Z-debug.log\n",[676,61968,61969,61983,62005,62009,62043,62047,62083,62087,62096,62100,62115,62119,62145,62149,62177,62181,62189,62193,62203,62214,62223,62233,62242,62250,62258,62270,62281,62301,62315,62322,62342,62388,62392,62422],{"__ignoreMap":674},[679,61970,61971,61974,61977,61980],{"class":681,"line":682},[679,61972,61973],{"class":880}," INFO",[679,61975,61976],{"class":689}," Starting",[679,61978,61979],{"class":689}," development",[679,61981,61982],{"class":689}," server...\n",[679,61984,61985,61987,61990,61993,61996,61999,62002],{"class":681,"line":790},[679,61986,34585],{"class":880},[679,61988,61989],{"class":689}," ValidationError:",[679,61991,61992],{"class":689}," webpack",[679,61994,61995],{"class":689}," Dev",[679,61997,61998],{"class":689}," Server",[679,62000,62001],{"class":689}," Invalid",[679,62003,62004],{"class":689}," Options\n",[679,62006,62007],{"class":681,"line":892},[679,62008,889],{"emptyLinePlaceholder":797},[679,62010,62011,62014,62017,62020,62023,62025,62028,62030,62033,62035,62037,62040],{"class":681,"line":901},[679,62012,62013],{"class":880},"options.clientLogLevel",[679,62015,62016],{"class":689}," should",[679,62018,62019],{"class":689}," be",[679,62021,62022],{"class":689}," {String}",[679,62024,22346],{"class":689},[679,62026,62027],{"class":689}," equal",[679,62029,21703],{"class":689},[679,62031,62032],{"class":689}," one",[679,62034,19757],{"class":689},[679,62036,22351],{"class":689},[679,62038,62039],{"class":689}," allowed",[679,62041,62042],{"class":689}," values\n",[679,62044,62045],{"class":681,"line":909},[679,62046,889],{"emptyLinePlaceholder":797},[679,62048,62049,62052,62055,62057,62060,62062,62065,62067,62070,62072,62075,62077,62080],{"class":681,"line":918},[679,62050,62051],{"class":693}," [ ",[679,62053,62054],{"class":689},"'info'",[679,62056,2797],{"class":693},[679,62058,62059],{"class":689},"'warn'",[679,62061,2797],{"class":693},[679,62063,62064],{"class":689},"'error'",[679,62066,2797],{"class":693},[679,62068,62069],{"class":689},"'debug'",[679,62071,2797],{"class":693},[679,62073,62074],{"class":689},"'trace'",[679,62076,2797],{"class":693},[679,62078,62079],{"class":689},"'silent'",[679,62081,62082],{"class":693}," ]\n",[679,62084,62085],{"class":681,"line":935},[679,62086,889],{"emptyLinePlaceholder":797},[679,62088,62089,62091,62094],{"class":681,"line":944},[679,62090,4193],{"class":693},[679,62092,62093],{"class":880},"https://webpack.js.org/configuration/dev-server/#devserverclientloglevel",[679,62095,1339],{"class":693},[679,62097,62098],{"class":681,"line":959},[679,62099,889],{"emptyLinePlaceholder":797},[679,62101,62102,62105,62107,62109,62111,62113],{"class":681,"line":964},[679,62103,62104],{"class":880},"ValidationError:",[679,62106,61992],{"class":689},[679,62108,61995],{"class":689},[679,62110,61998],{"class":689},[679,62112,62001],{"class":689},[679,62114,62004],{"class":689},[679,62116,62117],{"class":681,"line":977},[679,62118,889],{"emptyLinePlaceholder":797},[679,62120,62121,62123,62125,62127,62129,62131,62133,62135,62137,62139,62141,62143],{"class":681,"line":982},[679,62122,62013],{"class":880},[679,62124,62016],{"class":689},[679,62126,62019],{"class":689},[679,62128,62022],{"class":689},[679,62130,22346],{"class":689},[679,62132,62027],{"class":689},[679,62134,21703],{"class":689},[679,62136,62032],{"class":689},[679,62138,19757],{"class":689},[679,62140,22351],{"class":689},[679,62142,62039],{"class":689},[679,62144,62042],{"class":689},[679,62146,62147],{"class":681,"line":988},[679,62148,889],{"emptyLinePlaceholder":797},[679,62150,62151,62153,62155,62157,62159,62161,62163,62165,62167,62169,62171,62173,62175],{"class":681,"line":993},[679,62152,62051],{"class":693},[679,62154,62054],{"class":689},[679,62156,2797],{"class":693},[679,62158,62059],{"class":689},[679,62160,2797],{"class":693},[679,62162,62064],{"class":689},[679,62164,2797],{"class":693},[679,62166,62069],{"class":689},[679,62168,2797],{"class":693},[679,62170,62074],{"class":689},[679,62172,2797],{"class":693},[679,62174,62079],{"class":689},[679,62176,62082],{"class":693},[679,62178,62179],{"class":681,"line":2129},[679,62180,889],{"emptyLinePlaceholder":797},[679,62182,62183,62185,62187],{"class":681,"line":2140},[679,62184,4193],{"class":693},[679,62186,62093],{"class":880},[679,62188,1339],{"class":693},[679,62190,62191],{"class":681,"line":2145},[679,62192,889],{"emptyLinePlaceholder":797},[679,62194,62195,62197,62200],{"class":681,"line":2154},[679,62196,8024],{"class":880},[679,62198,62199],{"class":689}," validateOptions",[679,62201,62202],{"class":693}," (/Users/vega/dev/vue/hello-vue-cli/node_modules/schema-utils/src/validateOptions.js:32:11)\n",[679,62204,62205,62207,62209,62211],{"class":681,"line":2159},[679,62206,8024],{"class":880},[679,62208,2054],{"class":689},[679,62210,61998],{"class":689},[679,62212,62213],{"class":693}," (/Users/vega/dev/vue/hello-vue-cli/node_modules/webpack-dev-server/lib/Server.js:71:5)\n",[679,62215,62216,62218,62220],{"class":681,"line":2164},[679,62217,8024],{"class":880},[679,62219,37382],{"class":689},[679,62221,62222],{"class":693}," (/Users/vega/dev/vue/hello-vue-cli/node_modules/@vue/cli-service/lib/commands/serve.js:138:20)\n",[679,62224,62225,62227,62230],{"class":681,"line":3134},[679,62226,8024],{"class":880},[679,62228,62229],{"class":689}," process._tickCallback",[679,62231,62232],{"class":693}," (internal/process/next_tick.js:68:7)\n",[679,62234,62235,62237,62239],{"class":681,"line":3139},[679,62236,8024],{"class":880},[679,62238,49139],{"class":689},[679,62240,62241],{"class":693}," (internal/modules/cjs/loader.js:745:11)\n",[679,62243,62244,62246,62248],{"class":681,"line":3144},[679,62245,8024],{"class":880},[679,62247,34608],{"class":689},[679,62249,49151],{"class":693},[679,62251,62252,62254,62256],{"class":681,"line":3149},[679,62253,8024],{"class":880},[679,62255,49158],{"class":689},[679,62257,49161],{"class":693},[679,62259,62260,62262,62265,62267],{"class":681,"line":3169},[679,62261,24568],{"class":880},[679,62263,62264],{"class":689}," ERR!",[679,62266,42353],{"class":689},[679,62268,62269],{"class":689}," ELIFECYCLE\n",[679,62271,62272,62274,62276,62279],{"class":681,"line":3185},[679,62273,24568],{"class":880},[679,62275,62264],{"class":689},[679,62277,62278],{"class":689}," errno",[679,62280,52035],{"class":931},[679,62282,62283,62285,62287,62290,62293,62295,62298],{"class":681,"line":3194},[679,62284,24568],{"class":880},[679,62286,62264],{"class":689},[679,62288,62289],{"class":689}," hello-vue-cli@0.1.0",[679,62291,62292],{"class":689}," serve:",[679,62294,44336],{"class":689},[679,62296,62297],{"class":880},"vue-cli-service",[679,62299,62300],{"class":689}," serve`\n",[679,62302,62303,62305,62307,62310,62313],{"class":681,"line":3199},[679,62304,24568],{"class":880},[679,62306,62264],{"class":689},[679,62308,62309],{"class":689}," Exit",[679,62311,62312],{"class":689}," status",[679,62314,52035],{"class":931},[679,62316,62317,62319],{"class":681,"line":3212},[679,62318,24568],{"class":880},[679,62320,62321],{"class":689}," ERR!\n",[679,62323,62324,62326,62328,62331,62333,62335,62337,62339],{"class":681,"line":3217},[679,62325,24568],{"class":880},[679,62327,62264],{"class":689},[679,62329,62330],{"class":689}," Failed",[679,62332,36759],{"class":689},[679,62334,22351],{"class":689},[679,62336,62289],{"class":689},[679,62338,37382],{"class":689},[679,62340,62341],{"class":689}," script.\n",[679,62343,62344,62346,62348,62350,62352,62355,62358,62360,62363,62365,62368,62371,62373,62376,62379,62382,62385],{"class":681,"line":3222},[679,62345,24568],{"class":880},[679,62347,62264],{"class":689},[679,62349,44476],{"class":689},[679,62351,29045],{"class":689},[679,62353,62354],{"class":689}," probably",[679,62356,62357],{"class":689}," not",[679,62359,21697],{"class":689},[679,62361,62362],{"class":689}," problem",[679,62364,44505],{"class":689},[679,62366,62367],{"class":689}," npm.",[679,62369,62370],{"class":689}," There",[679,62372,29045],{"class":689},[679,62374,62375],{"class":689}," likely",[679,62377,62378],{"class":689}," additional",[679,62380,62381],{"class":689}," logging",[679,62383,62384],{"class":689}," output",[679,62386,62387],{"class":689}," above.\n",[679,62389,62390],{"class":681,"line":3227},[679,62391,889],{"emptyLinePlaceholder":797},[679,62393,62394,62396,62398,62401,62404,62406,62408,62410,62412,62415,62417,62419],{"class":681,"line":3232},[679,62395,24568],{"class":880},[679,62397,62264],{"class":689},[679,62399,62400],{"class":689}," A",[679,62402,62403],{"class":689}," complete",[679,62405,56466],{"class":689},[679,62407,19757],{"class":689},[679,62409,21353],{"class":689},[679,62411,16486],{"class":689},[679,62413,62414],{"class":689}," can",[679,62416,62019],{"class":689},[679,62418,8011],{"class":689},[679,62420,62421],{"class":689}," in:\n",[679,62423,62424,62426,62428],{"class":681,"line":3499},[679,62425,24568],{"class":880},[679,62427,62264],{"class":689},[679,62429,62430],{"class":689}," /Users/vega/.npm/_logs/2019-05-17T15_05_54_552Z-debug.log\n",[651,62432,62433,62434,62437,62438,62443,62444,62447],{},"I checked the version of the Vue CLI by running the command ",[676,62435,62436],{},"vue -V"," and I was ",[812,62439,62442],{"href":62440,"rel":62441},"https://github.com/vuejs/vue-cli/releases",[816],"running the latest 3.7.0",". I thought maybe I did something wrong so removed the project by running ",[676,62445,62446],{},"rm -Rf hello-vue-cli"," and tried again only to run into the same error.",[651,62449,62450,62451,62453],{},"I next wondered if this was affecting all of my projects or just new ones. I went into an existing project I had and ran ",[676,62452,61701],{}," and it started up just fine. This was adding to my head scratching because I didn't update NodeJS, NPM or Vue this morning so what the heck was going on.",[4542,62455,62457],{"id":62456},"vue-cli-issues-on-github","Vue CLI Issues on Github",[651,62459,62460,62461,62465],{},"At this point, I was pretty confused so my next step was to head over to ",[812,62462,17458],{"href":62463,"rel":62464},"https://github.com/vuejs/vue-cli/issues",[816]," and see if anyone else was seeing similar issues. Sure enough, the first 2 posts looked very similar to what I was experiencing.",[651,62467,62468,62469,62474,62475,62478],{},"Shout out to ",[812,62470,62473],{"href":62471,"rel":62472},"https://github.com/vuejs/vue-cli/issues/4017#issuecomment-493481614",[816],"dland512"," for providing some clarity as to what was happening. It appears that the problem is with the generated webpack configuration ",[676,62476,62477],{},"node_modules/@vue/cli-service/lib/commands/serve.js"," which has the following:",[669,62480,62482],{"className":25132,"code":62481,"language":25134,"meta":674,"style":674},"const server = new WebpackDevServer(compiler, Object.assign({\n clientLogLevel: 'none',\n",[676,62483,62484,62506],{"__ignoreMap":674},[679,62485,62486,62488,62491,62493,62495,62498,62501,62504],{"class":681,"line":682},[679,62487,45172],{"class":685},[679,62489,62490],{"class":931}," server",[679,62492,6883],{"class":685},[679,62494,2054],{"class":685},[679,62496,62497],{"class":880}," WebpackDevServer",[679,62499,62500],{"class":693},"(compiler, Object.",[679,62502,62503],{"class":880},"assign",[679,62505,21218],{"class":693},[679,62507,62508,62511,62514],{"class":681,"line":790},[679,62509,62510],{"class":693}," clientLogLevel: ",[679,62512,62513],{"class":689},"'none'",[679,62515,12083],{"class":693},[651,62517,62518],{},"If you look back at the original error it said:",[669,62520,62522],{"className":5851,"code":62521,"language":5853,"meta":674,"style":674},"ValidationError: webpack Dev Server Invalid Options\n\noptions.clientLogLevel should be {String} and equal to one of the allowed values\n\n [ 'info', 'warn', 'error', 'debug', 'trace', 'silent' ]\n",[676,62523,62524,62538,62542,62568,62572],{"__ignoreMap":674},[679,62525,62526,62528,62530,62532,62534,62536],{"class":681,"line":682},[679,62527,62104],{"class":880},[679,62529,61992],{"class":689},[679,62531,61995],{"class":689},[679,62533,61998],{"class":689},[679,62535,62001],{"class":689},[679,62537,62004],{"class":689},[679,62539,62540],{"class":681,"line":790},[679,62541,889],{"emptyLinePlaceholder":797},[679,62543,62544,62546,62548,62550,62552,62554,62556,62558,62560,62562,62564,62566],{"class":681,"line":892},[679,62545,62013],{"class":880},[679,62547,62016],{"class":689},[679,62549,62019],{"class":689},[679,62551,62022],{"class":689},[679,62553,22346],{"class":689},[679,62555,62027],{"class":689},[679,62557,21703],{"class":689},[679,62559,62032],{"class":689},[679,62561,19757],{"class":689},[679,62563,22351],{"class":689},[679,62565,62039],{"class":689},[679,62567,62042],{"class":689},[679,62569,62570],{"class":681,"line":901},[679,62571,889],{"emptyLinePlaceholder":797},[679,62573,62574,62576,62578,62580,62582,62584,62586,62588,62590,62592,62594,62596,62598],{"class":681,"line":909},[679,62575,62051],{"class":693},[679,62577,62054],{"class":689},[679,62579,2797],{"class":693},[679,62581,62059],{"class":689},[679,62583,2797],{"class":693},[679,62585,62064],{"class":689},[679,62587,2797],{"class":693},[679,62589,62069],{"class":689},[679,62591,2797],{"class":693},[679,62593,62074],{"class":689},[679,62595,2797],{"class":693},[679,62597,62079],{"class":689},[679,62599,62082],{"class":693},[651,62601,62602,62603,62605,62606,62609],{},"Still not sure what has caused this but it could be the ",[676,62604,62297],{}," itself. The workaround is to create a new file in the root of your project ",[676,62607,62608],{},"vue.config.js"," that contains the following:",[669,62611,62613],{"className":25132,"code":62612,"language":25134,"meta":674,"style":674},"module.exports = {\n devServer: {\n clientLogLevel: 'info'\n }\n};\n",[676,62614,62615,62627,62632,62640,62644],{"__ignoreMap":674},[679,62616,62617,62619,62621,62623,62625],{"class":681,"line":682},[679,62618,27515],{"class":931},[679,62620,664],{"class":693},[679,62622,43903],{"class":931},[679,62624,6883],{"class":685},[679,62626,884],{"class":693},[679,62628,62629],{"class":681,"line":790},[679,62630,62631],{"class":693}," devServer: {\n",[679,62633,62634,62637],{"class":681,"line":892},[679,62635,62636],{"class":693}," clientLogLevel: ",[679,62638,62639],{"class":689},"'info'\n",[679,62641,62642],{"class":681,"line":901},[679,62643,985],{"class":693},[679,62645,62646],{"class":681,"line":909},[679,62647,44055],{"class":693},[651,62649,62650,62651,664],{},"After adding this configuration I was able to run the project with no errors. If you want to read more about the Vue CLI global configuration you can check out the ",[812,62652,62655],{"href":62653,"rel":62654},"https://cli.vuejs.org/config/#global-cli-config",[816],"documentation here",[4542,62657,9042],{"id":9041},[651,62659,62660],{},"I am guessing by the time this article gets indexed it will no longer be an issue but I thought it was important to write up. This is one of those errors that would have completely derailed a day or even a week for me 15 years ago. With experience and a lot of patience, I have learned how to deal with issues like this that will ultimately come up. I hope this article either fixed this issue for you or just gave you some insight into my thought process of working through a frustrating issue. As always ...",[651,62662,41105,62663,41109],{},[41107,62664],{},[786,62666,62667],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}",{"title":674,"searchDepth":790,"depth":790,"links":62669},[62670,62671,62672],{"id":61948,"depth":790,"text":61949},{"id":62456,"depth":790,"text":62457},{"id":9041,"depth":790,"text":9042},"In this article, I will tell you about an error I received while trying to run and new VueJS project this morning and how I was able to fix it.",{"slug":62675,"date":62676,"published":797,"author":798,"tags":62677,"cover":62679},"vue-cli-error","2019-05-17T15:25:36.871Z",[44169,24568,62678,25134],"nodejs","vue-cli-validation-error.png",{"title":483,"description":62673},"blog/2019/05/17/vue-cli-error","WRhdkSy13Kz6ECf7raNNSZj91VZeN5GeiTlclw946UY",{"id":62684,"title":480,"body":62685,"description":62952,"extension":793,"meta":62953,"navigation":797,"path":481,"seo":62958,"stem":62959,"__hash__":62960},"content/blog/2019/05/31/escape-backtick-markdown.md",{"type":648,"value":62686,"toc":62947},[62687,62690,62694,62697,62700,62767,62770,62775,62778,62782,62785,62788,62817,62820,62837,62840,62886,62889,62936,62938,62941,62945],[651,62688,62689],{},"This is going to be a pretty short tutorial but I was so excited that I had to share it with all of you. It isn't often but there are times where I need to share a markdown code block in a blog post or in a Github comment and I never know how to do it. In this article, I am going to show you how to escape a backtick ` within a code block in Markdown",[4542,62691,62693],{"id":62692},"fenced-code-blocks","Fenced Code Blocks",[651,62695,62696],{},"If you have made it this far I am going to assume you're pretty familiar with Markdown. You can create fenced code blocks by placing triple backtick ``` before and after the code block. Whatever Markdown processor you're using will have a plugin that takes that code and the language you defined and add some highlighting to it.",[651,62698,62699],{},"In the following example I am using VuePress and in my markdown template I want to display text and some code to a user.",[669,62701,62703],{"className":59028,"code":62702,"language":43882,"meta":674,"style":674},"## Inside an Existing Project\n\nIf you have an existing project and would like to keep documentation inside the project, you should install VuePress as a local dependency. This setup also allows you to use CI or services like Netlify for automatic deployment on push.\n\n```bash\n# install as a local dependency\nyarn add -D vuepress@next # OR npm install -D vuepress@next\n\n# create a docs directory\nmkdir docs\n# create a markdown file\necho '# Hello VuePress' > docs/README.md\n```\n",[676,62704,62705,62710,62714,62719,62723,62728,62733,62738,62742,62747,62752,62757,62762],{"__ignoreMap":674},[679,62706,62707],{"class":681,"line":682},[679,62708,62709],{},"## Inside an Existing Project\n",[679,62711,62712],{"class":681,"line":790},[679,62713,889],{"emptyLinePlaceholder":797},[679,62715,62716],{"class":681,"line":892},[679,62717,62718],{},"If you have an existing project and would like to keep documentation inside the project, you should install VuePress as a local dependency. This setup also allows you to use CI or services like Netlify for automatic deployment on push.\n",[679,62720,62721],{"class":681,"line":901},[679,62722,889],{"emptyLinePlaceholder":797},[679,62724,62725],{"class":681,"line":909},[679,62726,62727],{},"```bash\n",[679,62729,62730],{"class":681,"line":918},[679,62731,62732],{},"# install as a local dependency\n",[679,62734,62735],{"class":681,"line":935},[679,62736,62737],{},"yarn add -D vuepress@next # OR npm install -D vuepress@next\n",[679,62739,62740],{"class":681,"line":944},[679,62741,889],{"emptyLinePlaceholder":797},[679,62743,62744],{"class":681,"line":959},[679,62745,62746],{},"# create a docs directory\n",[679,62748,62749],{"class":681,"line":964},[679,62750,62751],{},"mkdir docs\n",[679,62753,62754],{"class":681,"line":977},[679,62755,62756],{},"# create a markdown file\n",[679,62758,62759],{"class":681,"line":982},[679,62760,62761],{},"echo '# Hello VuePress' > docs/README.md\n",[679,62763,62764],{"class":681,"line":988},[679,62765,62766],{},"```\n",[651,62768,62769],{},"When that is rendered to the page it looks like this:",[651,62771,62772],{},[660,62773],{"alt":674,"src":62774},"/images/blog/2019/05/31/output-1a5ea040-ca04-47a1-aff6-a5e4467bfb1a.png",[651,62776,62777],{},"Anything in between the triple backtick ``` is processed by the Markdown processor (in this case Markdown-it) and turned into the nice looking code block you see above complete with syntax highlighting. So knowing that the markdown process will take that code and turn it into a nice looking code block how can I display the actual code to you on this blog post or in a Github comment?",[4542,62779,62781],{"id":62780},"escaping-the-backtick","Escaping the backtick",[651,62783,62784],{},"The answer is of course that you need to escape the backtick. Seems like something that would be really easy to do but after a lot of searching I kept coming up empty. I'm happy to tell you that I have found an answer to this but it might differ for you depending on the markdown processor you're using.",[651,62786,62787],{},"The first solution to our problem is to use more backticks. You will need to place 4 of them around your code block that contains 3. Notice that I have also specified the language as markdown which will allow you to get some syntax highlighting for your code.",[669,62789,62791],{"className":59028,"code":62790,"language":43882,"meta":674,"style":674},"````markdown\n```js\nconsole.log('Hello, World!');\n```\n````\n",[676,62792,62793,62798,62803,62808,62812],{"__ignoreMap":674},[679,62794,62795],{"class":681,"line":682},[679,62796,62797],{},"````markdown\n",[679,62799,62800],{"class":681,"line":790},[679,62801,62802],{},"```js\n",[679,62804,62805],{"class":681,"line":892},[679,62806,62807],{},"console.log('Hello, World!');\n",[679,62809,62810],{"class":681,"line":901},[679,62811,62766],{},[679,62813,62814],{"class":681,"line":909},[679,62815,62816],{},"````\n",[651,62818,62819],{},"Will render the following instead of displaying the console.log inside of a rendered code block.",[669,62821,62823],{"className":59028,"code":62822,"language":43882,"meta":674,"style":674},"```js\nconsole.log('Hello, World!');\n```\n",[676,62824,62825,62829,62833],{"__ignoreMap":674},[679,62826,62827],{"class":681,"line":682},[679,62828,62802],{},[679,62830,62831],{"class":681,"line":790},[679,62832,62807],{},[679,62834,62835],{"class":681,"line":892},[679,62836,62766],{},[651,62838,62839],{},"If you're going to have multiple blocks you don't need to surround each of them with 4 backticks. You can just put this around all of the text that you need to be escaped.",[669,62841,62843],{"className":59028,"code":62842,"language":43882,"meta":674,"style":674},"````markdown\nThis is some text\n```js\nconsole.log('Hello, World!');\n```\n\n```js\nconsole.log('Hello, World!');\n```\n````\n",[676,62844,62845,62849,62854,62858,62862,62866,62870,62874,62878,62882],{"__ignoreMap":674},[679,62846,62847],{"class":681,"line":682},[679,62848,62797],{},[679,62850,62851],{"class":681,"line":790},[679,62852,62853],{},"This is some text\n",[679,62855,62856],{"class":681,"line":892},[679,62857,62802],{},[679,62859,62860],{"class":681,"line":901},[679,62861,62807],{},[679,62863,62864],{"class":681,"line":909},[679,62865,62766],{},[679,62867,62868],{"class":681,"line":918},[679,62869,889],{"emptyLinePlaceholder":797},[679,62871,62872],{"class":681,"line":935},[679,62873,62802],{},[679,62875,62876],{"class":681,"line":944},[679,62877,62807],{},[679,62879,62880],{"class":681,"line":959},[679,62881,62766],{},[679,62883,62884],{"class":681,"line":964},[679,62885,62816],{},[651,62887,62888],{},"If you don't want to add more backticks or your markdown processor doesn't support it you can also solve this problem by using the tilde character. Another really good use case for 2 different options is this blog post. If I didn't have 2 options I wouldn't be able to escape the escape. This is getting very Inception like so I am going to stop here 🤯",[669,62890,62892],{"className":59028,"code":62891,"language":43882,"meta":674,"style":674},"~~~markdown\nThis is some text\n```js\nconsole.log('Hello, World!');\n```\n\n```js\nconsole.log('Hello, World!');\n```\n~~~\n",[676,62893,62894,62899,62903,62907,62911,62915,62919,62923,62927,62931],{"__ignoreMap":674},[679,62895,62896],{"class":681,"line":682},[679,62897,62898],{},"~~~markdown\n",[679,62900,62901],{"class":681,"line":790},[679,62902,62853],{},[679,62904,62905],{"class":681,"line":892},[679,62906,62802],{},[679,62908,62909],{"class":681,"line":901},[679,62910,62807],{},[679,62912,62913],{"class":681,"line":909},[679,62914,62766],{},[679,62916,62917],{"class":681,"line":918},[679,62918,889],{"emptyLinePlaceholder":797},[679,62920,62921],{"class":681,"line":935},[679,62922,62802],{},[679,62924,62925],{"class":681,"line":944},[679,62926,62807],{},[679,62928,62929],{"class":681,"line":959},[679,62930,62766],{},[679,62932,62933],{"class":681,"line":964},[679,62934,62935],{},"~~~\n",[4542,62937,9042],{"id":9041},[651,62939,62940],{},"I hope this short tutorial saved your desk from your head banging on it 😉and as always friends...",[651,62942,41105,62943,41109],{},[41107,62944],{},[786,62946,24464],{},{"title":674,"searchDepth":790,"depth":790,"links":62948},[62949,62950,62951],{"id":62692,"depth":790,"text":62693},{"id":62780,"depth":790,"text":62781},{"id":9041,"depth":790,"text":9042},"In this article I will show you how to escape the triple backtick in a markdown code block so that you can display it in a post or a Github comment. ",{"slug":62954,"date":62955,"published":797,"author":798,"tags":62956,"cover":62957},"escape-backtick-markdown","2019-05-31T18:32:41.710Z",[43882,43724],"./escape-backtick-markdown-cover.png",{"title":480,"description":62952},"blog/2019/05/31/escape-backtick-markdown","9D3eiW8qEry78vHBwgwv2YojvftI_SVaLNWAA9nbE1A",{"id":62962,"title":477,"body":62963,"description":64147,"extension":793,"meta":64148,"navigation":797,"path":478,"seo":64154,"stem":64155,"__hash__":64156},"content/blog/2019/06/05/triggering-events-router-vue.md",{"type":648,"value":62964,"toc":64139},[62965,62968,62971,62974,62984,62987,62993,63000,63004,63011,63015,63018,63023,63035,63184,63187,63301,63308,63313,63316,63320,63329,63498,63512,63528,63819,63831,64101,64103,64111,64117,64119,64132,64136],[651,62966,62967],{},"Last week I was working on tightening up our documentation on Vue Router and I was presented with an interesting challenge. In a short period of time our students are able to build some really cool applications using Vue. We teach them how components communicate with each other and introduce them to Vue Router.",[651,62969,62970],{},"The problem comes into play when you want to start triggering events from your views or even components within those views. In the real world you would start to see this as an obvious pain and look to see what others are using to solve this problem.",[651,62972,62973],{},"When this problem shows up you would probably reach for something like Vuex. In our situation we just don't have the time to include that in our curriculum so when it comes time for them to build their final capstone projects they need a way to trigger events in their views.",[651,62975,62976,62977,62980,62981,62983],{},"I put together a quick demo and had an a ha moment. When we want to trigger an event in any component we just emit one and listen for it on the parent component. In this case the view is just a component and the parent component is ",[676,62978,62979],{},"\u003Crouter-view>",". It's easy to forget but ",[676,62982,62979],{}," is just a component.",[651,62985,62986],{},"I was pretty excited about this so I tweeted this out:",[651,62988,62989],{},[812,62990,62991],{"href":62991,"rel":62992},"https://twitter.com/therealdanvega/status/1134550365049937920",[816],[651,62994,62995,62999],{},[812,62996,62998],{"href":57136,"rel":62997},[816],"Damian Dulisz"," was quick to respond and remind me that because it was a component it can also accept props. That was the exact discussion we had at work and I was happy to hear we weren't alone in thinking that.",[4542,63001,63003],{"id":63002},"triggering-events-from-vue-router-demo","Triggering events from Vue Router Demo",[651,63005,63006,63007,664],{},"I'm not able to use the exact project that triggered this article but I was able to put together a simple example that should help explain the solution. If you want to skip ahead you can jump into the final solution on ",[812,63008,58203],{"href":63009,"rel":63010},"https://codesandbox.io/s/trigger-event-views-p9oyt?fontsize=14",[816],[5909,63012,63014],{"id":63013},"demo-introduction","Demo Introduction",[651,63016,63017],{},"In our sample application we have a footer component that is constant throughout our application and it contains a status message.",[651,63019,63020],{},[660,63021],{"alt":674,"src":63022},"/images/blog/2019/06/05/2019-06-05_12-13-15-1bacd64d-69d7-48c7-a7ac-b90cc67baea5.png",[651,63024,63025,63026,63028,63029,63031,63032,664],{},"In ",[676,63027,49477],{}," there is a ",[676,63030,62979],{}," component that will display each of our views and below that you will include a component ",[676,63033,63034],{},"TheFooter.vue",[669,63036,63038],{"className":4496,"code":63037,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Cul class=\"nav\">\n \u003Crouter-link to=\"/\">Home\u003C/router-link\n >|\n \u003Crouter-link to=\"/admin\">Admin\u003C/router-link>\n \u003C/ul>\n \u003Crouter-view @updateStatus=\"updateStatus\" class=\"content\" />\n \u003Cthe-footer :status=\"status\" />\n \u003C/div>\n\u003C/template>\n",[676,63039,63040,63048,63062,63077,63096,63101,63121,63129,63152,63168,63176],{"__ignoreMap":674},[679,63041,63042,63044,63046],{"class":681,"line":682},[679,63043,4505],{"class":693},[679,63045,45982],{"class":4508},[679,63047,4519],{"class":693},[679,63049,63050,63052,63054,63056,63058,63060],{"class":681,"line":790},[679,63051,11738],{"class":693},[679,63053,4509],{"class":4508},[679,63055,5578],{"class":880},[679,63057,686],{"class":693},[679,63059,28626],{"class":689},[679,63061,4519],{"class":693},[679,63063,63064,63066,63068,63070,63072,63075],{"class":681,"line":892},[679,63065,4524],{"class":693},[679,63067,5316],{"class":4508},[679,63069,4512],{"class":880},[679,63071,686],{"class":693},[679,63073,63074],{"class":689},"\"nav\"",[679,63076,4519],{"class":693},[679,63078,63079,63081,63084,63086,63088,63090,63093],{"class":681,"line":901},[679,63080,46026],{"class":693},[679,63082,63083],{"class":4508},"router-link",[679,63085,21703],{"class":880},[679,63087,686],{"class":693},[679,63089,10032],{"class":689},[679,63091,63092],{"class":693},">Home\u003C/",[679,63094,63095],{"class":4508},"router-link\n",[679,63097,63098],{"class":681,"line":909},[679,63099,63100],{"class":693}," >|\n",[679,63102,63103,63105,63107,63109,63111,63114,63117,63119],{"class":681,"line":918},[679,63104,46026],{"class":693},[679,63106,63083],{"class":4508},[679,63108,21703],{"class":880},[679,63110,686],{"class":693},[679,63112,63113],{"class":689},"\"/admin\"",[679,63115,63116],{"class":693},">Admin\u003C/",[679,63118,63083],{"class":4508},[679,63120,4519],{"class":693},[679,63122,63123,63125,63127],{"class":681,"line":935},[679,63124,4577],{"class":693},[679,63126,5316],{"class":4508},[679,63128,4519],{"class":693},[679,63130,63131,63133,63136,63139,63141,63144,63146,63148,63150],{"class":681,"line":944},[679,63132,4524],{"class":693},[679,63134,63135],{"class":4508},"router-view",[679,63137,63138],{"class":880}," @updateStatus",[679,63140,686],{"class":693},[679,63142,63143],{"class":689},"\"updateStatus\"",[679,63145,4512],{"class":880},[679,63147,686],{"class":693},[679,63149,46885],{"class":689},[679,63151,5387],{"class":693},[679,63153,63154,63156,63159,63162,63164,63166],{"class":681,"line":959},[679,63155,4524],{"class":693},[679,63157,63158],{"class":4508},"the-footer",[679,63160,63161],{"class":880}," :status",[679,63163,686],{"class":693},[679,63165,40135],{"class":689},[679,63167,5387],{"class":693},[679,63169,63170,63172,63174],{"class":681,"line":964},[679,63171,11840],{"class":693},[679,63173,4509],{"class":4508},[679,63175,4519],{"class":693},[679,63177,63178,63180,63182],{"class":681,"line":977},[679,63179,4586],{"class":693},[679,63181,45982],{"class":4508},[679,63183,4519],{"class":693},[651,63185,63186],{},"The code for the footer component is pretty minimal.",[669,63188,63190],{"className":4496,"code":63189,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"footer\">\n \u003Cp>{{ status }}\u003C/p>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\n export default {\n name: \"the-footer\",\n props: {\n status: String\n }\n };\n\u003C/script>\n",[676,63191,63192,63200,63215,63228,63236,63244,63248,63256,63265,63275,63280,63285,63289,63293],{"__ignoreMap":674},[679,63193,63194,63196,63198],{"class":681,"line":682},[679,63195,4505],{"class":693},[679,63197,45982],{"class":4508},[679,63199,4519],{"class":693},[679,63201,63202,63204,63206,63208,63210,63213],{"class":681,"line":790},[679,63203,11738],{"class":693},[679,63205,4509],{"class":4508},[679,63207,4512],{"class":880},[679,63209,686],{"class":693},[679,63211,63212],{"class":689},"\"footer\"",[679,63214,4519],{"class":693},[679,63216,63217,63219,63221,63224,63226],{"class":681,"line":892},[679,63218,4524],{"class":693},[679,63220,651],{"class":4508},[679,63222,63223],{"class":693},">{{ status }}\u003C/",[679,63225,651],{"class":4508},[679,63227,4519],{"class":693},[679,63229,63230,63232,63234],{"class":681,"line":901},[679,63231,11840],{"class":693},[679,63233,4509],{"class":4508},[679,63235,4519],{"class":693},[679,63237,63238,63240,63242],{"class":681,"line":909},[679,63239,4586],{"class":693},[679,63241,45982],{"class":4508},[679,63243,4519],{"class":693},[679,63245,63246],{"class":681,"line":918},[679,63247,889],{"emptyLinePlaceholder":797},[679,63249,63250,63252,63254],{"class":681,"line":935},[679,63251,4505],{"class":693},[679,63253,47668],{"class":4508},[679,63255,4519],{"class":693},[679,63257,63258,63261,63263],{"class":681,"line":944},[679,63259,63260],{"class":685}," export",[679,63262,50460],{"class":685},[679,63264,884],{"class":693},[679,63266,63267,63270,63273],{"class":681,"line":959},[679,63268,63269],{"class":693}," name: ",[679,63271,63272],{"class":689},"\"the-footer\"",[679,63274,12083],{"class":693},[679,63276,63277],{"class":681,"line":964},[679,63278,63279],{"class":693}," props: {\n",[679,63281,63282],{"class":681,"line":977},[679,63283,63284],{"class":693}," status: String\n",[679,63286,63287],{"class":681,"line":982},[679,63288,985],{"class":693},[679,63290,63291],{"class":681,"line":988},[679,63292,53075],{"class":693},[679,63294,63295,63297,63299],{"class":681,"line":993},[679,63296,4586],{"class":693},[679,63298,47668],{"class":4508},[679,63300,4519],{"class":693},[651,63302,63303,63304,63307],{},"What we want is the ability to change that status message from a component. The catch here is that the component (",[676,63305,63306],{},"StatusUpdate.vue",") is being used in another view called admin.",[651,63309,63310],{},[660,63311],{"alt":674,"src":63312},"/images/blog/2019/06/05/2019-06-05_12-15-38-7d19d6b2-654c-4187-8d61-fa1a6b4f88aa.png",[651,63314,63315],{},"So to make this work you will need to trigger the event from the admin page up to the main App component and then pass that into the footer as a prop.",[5909,63317,63319],{"id":63318},"trigger-events-in-router-views","Trigger events in router views",[651,63321,63322,63323,63325,63326],{},"If you have worked with events before you know that you can emit an event so that any parent components are able to listen for it. To update the status you will type in the new status in the input field and click the button. When the button is clicked in our ",[676,63324,63306],{}," component it will emit an event called ",[676,63327,63328],{},"updateStatus()",[669,63330,63332],{"className":4496,"code":63331,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"update-status\">\n \u003Cp>Please enter a message below to update the status message.\u003C/p>\n \u003Cinput type=\"text\" v-model=\"status\" />\n \u003Cbutton @click=\"$emit('updateStatus',status)\">Update Status\u003C/button>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\n export default {\n name: \"status-message\",\n data() {\n return {\n status: \"\"\n };\n }\n };\n\u003C/script>\n",[676,63333,63334,63342,63357,63370,63391,63412,63420,63428,63432,63440,63448,63457,63464,63471,63478,63482,63486,63490],{"__ignoreMap":674},[679,63335,63336,63338,63340],{"class":681,"line":682},[679,63337,4505],{"class":693},[679,63339,45982],{"class":4508},[679,63341,4519],{"class":693},[679,63343,63344,63346,63348,63350,63352,63355],{"class":681,"line":790},[679,63345,11738],{"class":693},[679,63347,4509],{"class":4508},[679,63349,4512],{"class":880},[679,63351,686],{"class":693},[679,63353,63354],{"class":689},"\"update-status\"",[679,63356,4519],{"class":693},[679,63358,63359,63361,63363,63366,63368],{"class":681,"line":892},[679,63360,4524],{"class":693},[679,63362,651],{"class":4508},[679,63364,63365],{"class":693},">Please enter a message below to update the status message.\u003C/",[679,63367,651],{"class":4508},[679,63369,4519],{"class":693},[679,63371,63372,63374,63376,63378,63380,63382,63385,63387,63389],{"class":681,"line":901},[679,63373,4524],{"class":693},[679,63375,27722],{"class":4508},[679,63377,27725],{"class":880},[679,63379,686],{"class":693},[679,63381,54445],{"class":689},[679,63383,63384],{"class":880}," v-model",[679,63386,686],{"class":693},[679,63388,40135],{"class":689},[679,63390,5387],{"class":693},[679,63392,63393,63395,63397,63400,63402,63405,63408,63410],{"class":681,"line":909},[679,63394,4524],{"class":693},[679,63396,54258],{"class":4508},[679,63398,63399],{"class":880}," @click",[679,63401,686],{"class":693},[679,63403,63404],{"class":689},"\"$emit('updateStatus',status)\"",[679,63406,63407],{"class":693},">Update Status\u003C/",[679,63409,54258],{"class":4508},[679,63411,4519],{"class":693},[679,63413,63414,63416,63418],{"class":681,"line":918},[679,63415,11840],{"class":693},[679,63417,4509],{"class":4508},[679,63419,4519],{"class":693},[679,63421,63422,63424,63426],{"class":681,"line":935},[679,63423,4586],{"class":693},[679,63425,45982],{"class":4508},[679,63427,4519],{"class":693},[679,63429,63430],{"class":681,"line":944},[679,63431,889],{"emptyLinePlaceholder":797},[679,63433,63434,63436,63438],{"class":681,"line":959},[679,63435,4505],{"class":693},[679,63437,47668],{"class":4508},[679,63439,4519],{"class":693},[679,63441,63442,63444,63446],{"class":681,"line":964},[679,63443,63260],{"class":685},[679,63445,50460],{"class":685},[679,63447,884],{"class":693},[679,63449,63450,63452,63455],{"class":681,"line":977},[679,63451,63269],{"class":693},[679,63453,63454],{"class":689},"\"status-message\"",[679,63456,12083],{"class":693},[679,63458,63459,63462],{"class":681,"line":982},[679,63460,63461],{"class":880}," data",[679,63463,2667],{"class":693},[679,63465,63466,63469],{"class":681,"line":988},[679,63467,63468],{"class":685}," return",[679,63470,884],{"class":693},[679,63472,63473,63476],{"class":681,"line":993},[679,63474,63475],{"class":693}," status: ",[679,63477,56244],{"class":689},[679,63479,63480],{"class":681,"line":2129},[679,63481,19700],{"class":693},[679,63483,63484],{"class":681,"line":2140},[679,63485,985],{"class":693},[679,63487,63488],{"class":681,"line":2145},[679,63489,53075],{"class":693},[679,63491,63492,63494,63496],{"class":681,"line":2154},[679,63493,4586],{"class":693},[679,63495,47668],{"class":4508},[679,63497,4519],{"class":693},[651,63499,63500,63501,63504,63505,63508,63509,63511],{},"The parent component is a view in ",[676,63502,63503],{},"src/views/"," called ",[676,63506,63507],{},"Admin.vue",". On the admin page you will import the status message component and display it on screen. When you emit an event it's only available in the components parent so to listen for it in ",[676,63510,49477],{}," you will need to bubble it up from Admin to App.",[651,63513,63514,63515,63518,63519,63522,63523,63525,63526,664],{},"To make this work add an event listener to listen for the ",[676,63516,63517],{},"updateStatus"," event that will be emitted from the ",[676,63520,63521],{},"UpdateStatus.vue"," component. This will turn around and emit the ",[676,63524,63517],{}," event to the parent component of Admin, which is our ",[676,63527,62979],{},[669,63529,63531],{"className":4496,"code":63530,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"admin\">\n \u003Ch1>Admin Page\u003C/h1>\n \u003Cp>\n Siphon white macchiato arabica frappuccino breve as, affogato that acerbic\n dark sweet. Qui brewed single shot white bar plunger pot single shot\n cinnamon. Lungo brewed turkish white aged skinny french press. Aged french\n press white medium, brewed and ut seasonal single origin. Single origin,\n aroma, robusta trifecta cup frappuccino cup skinny.\n \u003C/p>\n \u003Cp>\n Arabica and percolator blue mountain to go mug frappuccino white medium\n brewed single shot. Skinny redeye aromatic, java flavour mazagran blue\n mountain robusta milk. Trifecta single shot strong single origin caffeine\n cream cinnamon cream extra. Turkish, caramelization so, cultivar brewed,\n cream mocha plunger pot white robusta saucer. Caffeine dark, brewed\n carajillo pumpkin spice mocha caffeine.\n \u003C/p>\n\n \u003Cstatus-message @updateStatus=\"updateStatus\" />\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\n import StatusMessage from \"@/components/StatusMessage.vue\";\n\n export default {\n name: \"admin\",\n components: {\n StatusMessage\n },\n data() {\n return {};\n },\n methods: {\n updateStatus(status) {\n this.$emit(\"updateStatus\", status);\n }\n }\n };\n\u003C/script>\n",[676,63532,63533,63541,63556,63569,63577,63582,63587,63592,63597,63602,63610,63618,63623,63628,63633,63638,63643,63648,63656,63660,63675,63683,63691,63695,63703,63718,63722,63730,63738,63743,63748,63752,63758,63764,63768,63772,63783,63799,63803,63807,63811],{"__ignoreMap":674},[679,63534,63535,63537,63539],{"class":681,"line":682},[679,63536,4505],{"class":693},[679,63538,45982],{"class":4508},[679,63540,4519],{"class":693},[679,63542,63543,63545,63547,63549,63551,63554],{"class":681,"line":790},[679,63544,11738],{"class":693},[679,63546,4509],{"class":4508},[679,63548,4512],{"class":880},[679,63550,686],{"class":693},[679,63552,63553],{"class":689},"\"admin\"",[679,63555,4519],{"class":693},[679,63557,63558,63560,63562,63565,63567],{"class":681,"line":892},[679,63559,4524],{"class":693},[679,63561,11859],{"class":4508},[679,63563,63564],{"class":693},">Admin Page\u003C/",[679,63566,11859],{"class":4508},[679,63568,4519],{"class":693},[679,63570,63571,63573,63575],{"class":681,"line":901},[679,63572,4524],{"class":693},[679,63574,651],{"class":4508},[679,63576,4519],{"class":693},[679,63578,63579],{"class":681,"line":909},[679,63580,63581],{"class":693}," Siphon white macchiato arabica frappuccino breve as, affogato that acerbic\n",[679,63583,63584],{"class":681,"line":918},[679,63585,63586],{"class":693}," dark sweet. Qui brewed single shot white bar plunger pot single shot\n",[679,63588,63589],{"class":681,"line":935},[679,63590,63591],{"class":693}," cinnamon. Lungo brewed turkish white aged skinny french press. Aged french\n",[679,63593,63594],{"class":681,"line":944},[679,63595,63596],{"class":693}," press white medium, brewed and ut seasonal single origin. Single origin,\n",[679,63598,63599],{"class":681,"line":959},[679,63600,63601],{"class":693}," aroma, robusta trifecta cup frappuccino cup skinny.\n",[679,63603,63604,63606,63608],{"class":681,"line":964},[679,63605,4577],{"class":693},[679,63607,651],{"class":4508},[679,63609,4519],{"class":693},[679,63611,63612,63614,63616],{"class":681,"line":977},[679,63613,4524],{"class":693},[679,63615,651],{"class":4508},[679,63617,4519],{"class":693},[679,63619,63620],{"class":681,"line":982},[679,63621,63622],{"class":693}," Arabica and percolator blue mountain to go mug frappuccino white medium\n",[679,63624,63625],{"class":681,"line":988},[679,63626,63627],{"class":693}," brewed single shot. Skinny redeye aromatic, java flavour mazagran blue\n",[679,63629,63630],{"class":681,"line":993},[679,63631,63632],{"class":693}," mountain robusta milk. Trifecta single shot strong single origin caffeine\n",[679,63634,63635],{"class":681,"line":2129},[679,63636,63637],{"class":693}," cream cinnamon cream extra. Turkish, caramelization so, cultivar brewed,\n",[679,63639,63640],{"class":681,"line":2140},[679,63641,63642],{"class":693}," cream mocha plunger pot white robusta saucer. Caffeine dark, brewed\n",[679,63644,63645],{"class":681,"line":2145},[679,63646,63647],{"class":693}," carajillo pumpkin spice mocha caffeine.\n",[679,63649,63650,63652,63654],{"class":681,"line":2154},[679,63651,4577],{"class":693},[679,63653,651],{"class":4508},[679,63655,4519],{"class":693},[679,63657,63658],{"class":681,"line":2159},[679,63659,889],{"emptyLinePlaceholder":797},[679,63661,63662,63664,63667,63669,63671,63673],{"class":681,"line":2164},[679,63663,4524],{"class":693},[679,63665,63666],{"class":4508},"status-message",[679,63668,63138],{"class":880},[679,63670,686],{"class":693},[679,63672,63143],{"class":689},[679,63674,5387],{"class":693},[679,63676,63677,63679,63681],{"class":681,"line":3134},[679,63678,11840],{"class":693},[679,63680,4509],{"class":4508},[679,63682,4519],{"class":693},[679,63684,63685,63687,63689],{"class":681,"line":3139},[679,63686,4586],{"class":693},[679,63688,45982],{"class":4508},[679,63690,4519],{"class":693},[679,63692,63693],{"class":681,"line":3144},[679,63694,889],{"emptyLinePlaceholder":797},[679,63696,63697,63699,63701],{"class":681,"line":3149},[679,63698,4505],{"class":693},[679,63700,47668],{"class":4508},[679,63702,4519],{"class":693},[679,63704,63705,63708,63711,63713,63716],{"class":681,"line":3169},[679,63706,63707],{"class":685}," import",[679,63709,63710],{"class":693}," StatusMessage ",[679,63712,28887],{"class":685},[679,63714,63715],{"class":689}," \"@/components/StatusMessage.vue\"",[679,63717,1186],{"class":693},[679,63719,63720],{"class":681,"line":3185},[679,63721,889],{"emptyLinePlaceholder":797},[679,63723,63724,63726,63728],{"class":681,"line":3194},[679,63725,63260],{"class":685},[679,63727,50460],{"class":685},[679,63729,884],{"class":693},[679,63731,63732,63734,63736],{"class":681,"line":3199},[679,63733,63269],{"class":693},[679,63735,63553],{"class":689},[679,63737,12083],{"class":693},[679,63739,63740],{"class":681,"line":3212},[679,63741,63742],{"class":693}," components: {\n",[679,63744,63745],{"class":681,"line":3217},[679,63746,63747],{"class":693}," StatusMessage\n",[679,63749,63750],{"class":681,"line":3222},[679,63751,28763],{"class":693},[679,63753,63754,63756],{"class":681,"line":3227},[679,63755,63461],{"class":880},[679,63757,2667],{"class":693},[679,63759,63760,63762],{"class":681,"line":3232},[679,63761,63468],{"class":685},[679,63763,6706],{"class":693},[679,63765,63766],{"class":681,"line":3499},[679,63767,28763],{"class":693},[679,63769,63770],{"class":681,"line":3509},[679,63771,57724],{"class":693},[679,63773,63774,63777,63779,63781],{"class":681,"line":3516},[679,63775,63776],{"class":880}," updateStatus",[679,63778,745],{"class":693},[679,63780,24114],{"class":2099},[679,63782,4390],{"class":693},[679,63784,63785,63787,63789,63792,63794,63796],{"class":681,"line":3531},[679,63786,7862],{"class":931},[679,63788,664],{"class":693},[679,63790,63791],{"class":880},"$emit",[679,63793,745],{"class":693},[679,63795,63143],{"class":689},[679,63797,63798],{"class":693},", status);\n",[679,63800,63801],{"class":681,"line":3536},[679,63802,11804],{"class":693},[679,63804,63805],{"class":681,"line":3541},[679,63806,985],{"class":693},[679,63808,63809],{"class":681,"line":3546},[679,63810,53075],{"class":693},[679,63812,63813,63815,63817],{"class":681,"line":3551},[679,63814,4586],{"class":693},[679,63816,47668],{"class":4508},[679,63818,4519],{"class":693},[651,63820,63821,63822,63824,63825,63827,63828,63830],{},"Now in ",[676,63823,49477],{}," you can listen for ",[676,63826,63517],{}," event on the ",[676,63829,62979],{}," component. As I mentioned before it's just a component so this will work. You can now update the status and it will get passed into your footer using props.",[669,63832,63834],{"className":4496,"code":63833,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Cul class=\"nav\">\n \u003Crouter-link to=\"/\">Home\u003C/router-link\n >|\n \u003Crouter-link to=\"/admin\">Admin\u003C/router-link>\n \u003C/ul>\n \u003Crouter-view @updateStatus=\"updateStatus\" class=\"content\" />\n \u003Cthe-footer :status=\"status\" />\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\n import TheFooter from \"./components/TheFooter\";\n\n export default {\n name: \"App\",\n components: {\n TheFooter\n },\n data() {\n return {\n status: \"This is the default status message\"\n };\n },\n methods: {\n updateStatus(status) {\n this.status = status;\n }\n }\n };\n\u003C/script>\n",[676,63835,63836,63844,63858,63872,63888,63892,63910,63918,63938,63952,63960,63968,63972,63980,63994,63998,64006,64015,64019,64024,64028,64034,64040,64047,64051,64055,64059,64069,64081,64085,64089,64093],{"__ignoreMap":674},[679,63837,63838,63840,63842],{"class":681,"line":682},[679,63839,4505],{"class":693},[679,63841,45982],{"class":4508},[679,63843,4519],{"class":693},[679,63845,63846,63848,63850,63852,63854,63856],{"class":681,"line":790},[679,63847,11738],{"class":693},[679,63849,4509],{"class":4508},[679,63851,5578],{"class":880},[679,63853,686],{"class":693},[679,63855,28626],{"class":689},[679,63857,4519],{"class":693},[679,63859,63860,63862,63864,63866,63868,63870],{"class":681,"line":892},[679,63861,4524],{"class":693},[679,63863,5316],{"class":4508},[679,63865,4512],{"class":880},[679,63867,686],{"class":693},[679,63869,63074],{"class":689},[679,63871,4519],{"class":693},[679,63873,63874,63876,63878,63880,63882,63884,63886],{"class":681,"line":901},[679,63875,46026],{"class":693},[679,63877,63083],{"class":4508},[679,63879,21703],{"class":880},[679,63881,686],{"class":693},[679,63883,10032],{"class":689},[679,63885,63092],{"class":693},[679,63887,63095],{"class":4508},[679,63889,63890],{"class":681,"line":909},[679,63891,63100],{"class":693},[679,63893,63894,63896,63898,63900,63902,63904,63906,63908],{"class":681,"line":918},[679,63895,46026],{"class":693},[679,63897,63083],{"class":4508},[679,63899,21703],{"class":880},[679,63901,686],{"class":693},[679,63903,63113],{"class":689},[679,63905,63116],{"class":693},[679,63907,63083],{"class":4508},[679,63909,4519],{"class":693},[679,63911,63912,63914,63916],{"class":681,"line":935},[679,63913,4577],{"class":693},[679,63915,5316],{"class":4508},[679,63917,4519],{"class":693},[679,63919,63920,63922,63924,63926,63928,63930,63932,63934,63936],{"class":681,"line":944},[679,63921,4524],{"class":693},[679,63923,63135],{"class":4508},[679,63925,63138],{"class":880},[679,63927,686],{"class":693},[679,63929,63143],{"class":689},[679,63931,4512],{"class":880},[679,63933,686],{"class":693},[679,63935,46885],{"class":689},[679,63937,5387],{"class":693},[679,63939,63940,63942,63944,63946,63948,63950],{"class":681,"line":959},[679,63941,4524],{"class":693},[679,63943,63158],{"class":4508},[679,63945,63161],{"class":880},[679,63947,686],{"class":693},[679,63949,40135],{"class":689},[679,63951,5387],{"class":693},[679,63953,63954,63956,63958],{"class":681,"line":964},[679,63955,11840],{"class":693},[679,63957,4509],{"class":4508},[679,63959,4519],{"class":693},[679,63961,63962,63964,63966],{"class":681,"line":977},[679,63963,4586],{"class":693},[679,63965,45982],{"class":4508},[679,63967,4519],{"class":693},[679,63969,63970],{"class":681,"line":982},[679,63971,889],{"emptyLinePlaceholder":797},[679,63973,63974,63976,63978],{"class":681,"line":988},[679,63975,4505],{"class":693},[679,63977,47668],{"class":4508},[679,63979,4519],{"class":693},[679,63981,63982,63984,63987,63989,63992],{"class":681,"line":993},[679,63983,63707],{"class":685},[679,63985,63986],{"class":693}," TheFooter ",[679,63988,28887],{"class":685},[679,63990,63991],{"class":689}," \"./components/TheFooter\"",[679,63993,1186],{"class":693},[679,63995,63996],{"class":681,"line":2129},[679,63997,889],{"emptyLinePlaceholder":797},[679,63999,64000,64002,64004],{"class":681,"line":2140},[679,64001,63260],{"class":685},[679,64003,50460],{"class":685},[679,64005,884],{"class":693},[679,64007,64008,64010,64013],{"class":681,"line":2145},[679,64009,63269],{"class":693},[679,64011,64012],{"class":689},"\"App\"",[679,64014,12083],{"class":693},[679,64016,64017],{"class":681,"line":2154},[679,64018,63742],{"class":693},[679,64020,64021],{"class":681,"line":2159},[679,64022,64023],{"class":693}," TheFooter\n",[679,64025,64026],{"class":681,"line":2164},[679,64027,28763],{"class":693},[679,64029,64030,64032],{"class":681,"line":3134},[679,64031,63461],{"class":880},[679,64033,2667],{"class":693},[679,64035,64036,64038],{"class":681,"line":3139},[679,64037,63468],{"class":685},[679,64039,884],{"class":693},[679,64041,64042,64044],{"class":681,"line":3144},[679,64043,63475],{"class":693},[679,64045,64046],{"class":689},"\"This is the default status message\"\n",[679,64048,64049],{"class":681,"line":3149},[679,64050,19700],{"class":693},[679,64052,64053],{"class":681,"line":3169},[679,64054,28763],{"class":693},[679,64056,64057],{"class":681,"line":3185},[679,64058,57724],{"class":693},[679,64060,64061,64063,64065,64067],{"class":681,"line":3194},[679,64062,63776],{"class":880},[679,64064,745],{"class":693},[679,64066,24114],{"class":2099},[679,64068,4390],{"class":693},[679,64070,64071,64073,64076,64078],{"class":681,"line":3199},[679,64072,7862],{"class":931},[679,64074,64075],{"class":693},".status ",[679,64077,686],{"class":685},[679,64079,64080],{"class":693}," status;\n",[679,64082,64083],{"class":681,"line":3212},[679,64084,11804],{"class":693},[679,64086,64087],{"class":681,"line":3217},[679,64088,985],{"class":693},[679,64090,64091],{"class":681,"line":3222},[679,64092,53075],{"class":693},[679,64094,64095,64097,64099],{"class":681,"line":3227},[679,64096,4586],{"class":693},[679,64098,47668],{"class":4508},[679,64100,4519],{"class":693},[4542,64102,58203],{"id":58202},[651,64104,64105,64106,64110],{},"If you're interested in checking out the final code for this project you can ",[812,64107,64109],{"href":63009,"rel":64108},[816],"click here"," or use the CodeSandbox embed below.",[651,64112,64113],{},[812,64114,64115],{"href":64115,"rel":64116},"https://codesandbox.io/embed/trigger-event-views-p9oyt?fontsize=14&view=editor",[816],[4542,64118,9042],{"id":9041},[651,64120,64121,64122,64125,64126,23212,64129,64131],{},"I think the important thing to remember here is that your components inside of the ",[676,64123,64124],{},"/views"," folder along with ",[676,64127,64128],{},"\u003Crouter-link>",[676,64130,62979],{}," are all just Single File Components at the end of the day. It's because of this that they can trigger and listen for events or act like any other component would. I hope this little tip helped make your day an easier one and as always....",[651,64133,41105,64134,41109],{},[41107,64135],{},[786,64137,64138],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":64140},[64141,64145,64146],{"id":63002,"depth":790,"text":63003,"children":64142},[64143,64144],{"id":63013,"depth":892,"text":63014},{"id":63318,"depth":892,"text":63319},{"id":58202,"depth":790,"text":58203},{"id":9041,"depth":790,"text":9042},"In this article I will show you how to trigger events from views using the Router View component.",{"slug":64149,"date":64150,"published":797,"author":798,"tags":64151,"cover":64152,"video":64153},"triggering-events-router-vue","2019-06-05T20:13:17.445Z",[44169],"./triggering-events-router-vue-cover.png","https://www.youtube.com/embed/JwccQYpsE2Q",{"title":477,"description":64147},"blog/2019/06/05/triggering-events-router-vue","-yxPP7EguPFjglZ9sKD8B-qQ09WWrNk8FZNlrbPXvyY",{"id":64158,"title":474,"body":64159,"description":65929,"extension":793,"meta":65930,"navigation":797,"path":475,"seo":65936,"stem":65937,"__hash__":65938},"content/blog/2019/06/13/vuepress-cookies.md",{"type":648,"value":64160,"toc":65918},[64161,64164,64167,64170,64174,64177,64180,64311,64314,64317,64321,64326,64329,64332,64336,64342,64356,64363,64466,64472,64476,64483,64547,64553,64563,64690,64697,64701,64710,64713,64785,64791,64794,64798,64801,64812,64864,64867,65069,65072,65556,65560,65563,65622,65629,65906,65908,65911,65915],[651,64162,64163],{},"Lately, I have been working on a large migration of documentation from Gitbook to VuePress. If you're curious about how to get started with VuePress or the process I used to determine if it would work for us please reach out to me and let me know.",[651,64165,64166],{},"This article is going to assume you have some experience working with VuePress so we can skip over some getting started stuff and move right into the problem at hand. We are going to cover this in detail but in short, we are going to learn How to use cookies in VuePress.",[651,64168,64169],{},"I had a few requirements that I needed to make sure VuePress could handle. In this article, I am going to talk about one of those requirements, the problems I ran into and how I eventually solved it.",[4542,64171,64173],{"id":64172},"gitbook-migration","Gitbook Migration",[651,64175,64176],{},"Before we dive into some code we need to talk about the requirements and the problems I ran into. In the current version of the documentation we actually built multiple versions based on the programming language. We might have a concept that we explain and then show off code sample in any number of languages.",[651,64178,64179],{},"In Gitbook you can create a variable, set a default and then use that variable within your markdown templates like this:",[669,64181,64183],{"className":59028,"code":64182,"language":43882,"meta":674,"style":674},"{% if book.language === \"JavaScript\" %}\n\n```js\nclass Greeter {\n constructor(message) {\n this.message = message;\n }\n greet() {\n return `Hello, ${this.message}`;\n }\n}\n```\n\n{% elif book.language === 'TypeScript' %}\n\n```ts\nclass Greeter {\n greeting: string;\n constructor(message: string) {\n this.greeting = message;\n }\n greet() {\n return \"Hello, \" + this.greeting;\n }\n}\n```\n\n{% endif %}\n",[676,64184,64185,64190,64194,64198,64203,64208,64213,64217,64222,64227,64231,64235,64239,64243,64248,64252,64257,64261,64266,64271,64276,64280,64285,64290,64294,64298,64302,64306],{"__ignoreMap":674},[679,64186,64187],{"class":681,"line":682},[679,64188,64189],{},"{% if book.language === \"JavaScript\" %}\n",[679,64191,64192],{"class":681,"line":790},[679,64193,889],{"emptyLinePlaceholder":797},[679,64195,64196],{"class":681,"line":892},[679,64197,62802],{},[679,64199,64200],{"class":681,"line":901},[679,64201,64202],{},"class Greeter {\n",[679,64204,64205],{"class":681,"line":909},[679,64206,64207],{}," constructor(message) {\n",[679,64209,64210],{"class":681,"line":918},[679,64211,64212],{}," this.message = message;\n",[679,64214,64215],{"class":681,"line":935},[679,64216,21405],{},[679,64218,64219],{"class":681,"line":944},[679,64220,64221],{}," greet() {\n",[679,64223,64224],{"class":681,"line":959},[679,64225,64226],{}," return `Hello, ${this.message}`;\n",[679,64228,64229],{"class":681,"line":964},[679,64230,21405],{},[679,64232,64233],{"class":681,"line":977},[679,64234,996],{},[679,64236,64237],{"class":681,"line":982},[679,64238,62766],{},[679,64240,64241],{"class":681,"line":988},[679,64242,889],{"emptyLinePlaceholder":797},[679,64244,64245],{"class":681,"line":993},[679,64246,64247],{},"{% elif book.language === 'TypeScript' %}\n",[679,64249,64250],{"class":681,"line":2129},[679,64251,889],{"emptyLinePlaceholder":797},[679,64253,64254],{"class":681,"line":2140},[679,64255,64256],{},"```ts\n",[679,64258,64259],{"class":681,"line":2145},[679,64260,64202],{},[679,64262,64263],{"class":681,"line":2154},[679,64264,64265],{}," greeting: string;\n",[679,64267,64268],{"class":681,"line":2159},[679,64269,64270],{}," constructor(message: string) {\n",[679,64272,64273],{"class":681,"line":2164},[679,64274,64275],{}," this.greeting = message;\n",[679,64277,64278],{"class":681,"line":3134},[679,64279,985],{},[679,64281,64282],{"class":681,"line":3139},[679,64283,64284],{}," greet() {\n",[679,64286,64287],{"class":681,"line":3144},[679,64288,64289],{}," return \"Hello, \" + this.greeting;\n",[679,64291,64292],{"class":681,"line":3149},[679,64293,985],{},[679,64295,64296],{"class":681,"line":3169},[679,64297,996],{},[679,64299,64300],{"class":681,"line":3185},[679,64301,62766],{},[679,64303,64304],{"class":681,"line":3194},[679,64305,889],{"emptyLinePlaceholder":797},[679,64307,64308],{"class":681,"line":3199},[679,64309,64310],{},"{% endif %}\n",[651,64312,64313],{},"When you visited the generated HTML we would only show the version you were interested in seeing. This could have been solved using tabs but there were cases where we conditionally would show entire sections so it wasn't just code.",[651,64315,64316],{},"When you ran the build you would pass in the language as an argument and build the documentation for that language. This worked but having multiple versions of the same documentation meant slow build times and unneeded duplication in production.",[4542,64318,64320],{"id":64319},"mmmmmmmmmm-cookies","MMMMMMMMMM Cookies",[651,64322,64323],{},[660,64324],{"alt":674,"src":64325},"/images/blog/2019/06/13/AdobeStock_113771716-12be091c-ec88-49e5-b21f-6fe1a147485c.jpeg",[651,64327,64328],{},"I don't know about you but when I started thinking about this problem I immediately thought about cookies. This was in part because I was hungry at the time but I also knew this to be a good solution to my problem.",[651,64330,64331],{},"To me this is a visitor preference and something they can change at anytime. Just like the Gitbook solution I would be able to create a default value by dropping a cookie on the users machine the first time they visited the site. Then I would give them the opportunity to change this at anytime.",[5909,64333,64335],{"id":64334},"vue-cookies","Vue Cookies",[651,64337,64338,64339,64341],{},"In my first attempt to solve this problem I brought in a package called ",[676,64340,64334],{},". I realize that creating a cookie isn't that difficult but having a nice API to manage everything that goes along with cookies is a nice to have. After a quick look at the documentation it looked really easy to setup so I went ahead and added it to my project.",[669,64343,64345],{"className":5851,"code":64344,"language":5853,"meta":674,"style":674},"npm install vue-cookies\n",[676,64346,64347],{"__ignoreMap":674},[679,64348,64349,64351,64353],{"class":681,"line":682},[679,64350,24568],{"class":880},[679,64352,24571],{"class":689},[679,64354,64355],{"class":689}," vue-cookies\n",[651,64357,64358,64359,64362],{},"In a normal Vue application I would jump into ",[676,64360,64361],{},"main.js"," and add the following.",[669,64364,64368],{"className":64365,"code":64366,"language":64367,"meta":674,"style":674},"language-js shiki shiki-themes github-light github-dark github-light","import Vue from 'vue'\nimport VueCookies from 'vue-cookies'\n\n// install the plugin\nVue.use(VueCookies)\n\n// we want this cookie to last for 120 days\nVueCookies.config('120d')\n\n// set global cookie\nVueCookies.set('language','JavaScript');\n","js",[676,64369,64370,64382,64394,64398,64403,64414,64418,64423,64438,64442,64447],{"__ignoreMap":674},[679,64371,64372,64374,64377,64379],{"class":681,"line":682},[679,64373,1999],{"class":685},[679,64375,64376],{"class":693}," Vue ",[679,64378,28887],{"class":685},[679,64380,64381],{"class":689}," 'vue'\n",[679,64383,64384,64386,64389,64391],{"class":681,"line":790},[679,64385,1999],{"class":685},[679,64387,64388],{"class":693}," VueCookies ",[679,64390,28887],{"class":685},[679,64392,64393],{"class":689}," 'vue-cookies'\n",[679,64395,64396],{"class":681,"line":892},[679,64397,889],{"emptyLinePlaceholder":797},[679,64399,64400],{"class":681,"line":901},[679,64401,64402],{"class":1400},"// install the plugin\n",[679,64404,64405,64408,64411],{"class":681,"line":909},[679,64406,64407],{"class":693},"Vue.",[679,64409,64410],{"class":880},"use",[679,64412,64413],{"class":693},"(VueCookies)\n",[679,64415,64416],{"class":681,"line":918},[679,64417,889],{"emptyLinePlaceholder":797},[679,64419,64420],{"class":681,"line":935},[679,64421,64422],{"class":1400},"// we want this cookie to last for 120 days\n",[679,64424,64425,64428,64431,64433,64436],{"class":681,"line":944},[679,64426,64427],{"class":693},"VueCookies.",[679,64429,64430],{"class":880},"config",[679,64432,745],{"class":693},[679,64434,64435],{"class":689},"'120d'",[679,64437,1339],{"class":693},[679,64439,64440],{"class":681,"line":959},[679,64441,889],{"emptyLinePlaceholder":797},[679,64443,64444],{"class":681,"line":964},[679,64445,64446],{"class":1400},"// set global cookie\n",[679,64448,64449,64451,64454,64456,64459,64461,64464],{"class":681,"line":977},[679,64450,64427],{"class":693},[679,64452,64453],{"class":880},"set",[679,64455,745],{"class":693},[679,64457,64458],{"class":689},"'language'",[679,64460,1202],{"class":693},[679,64462,64463],{"class":689},"'JavaScript'",[679,64465,1208],{"class":693},[651,64467,64468,64469,64471],{},"But this is VuePress and I don't have a ",[676,64470,64361],{}," so how can I hook into the existing Vue instance?",[5909,64473,64475],{"id":64474},"app-level-enhancements","App Level Enhancements",[651,64477,64478,64479,64482],{},"Since the VuePress app is a standard Vue app, you can apply app-level enhancements by creating a file ",[676,64480,64481],{},".vuepress/enhanceApp.js",", which will be imported into the app if it is present. The file should export default a hook function which will receive an object containing some app level values. You can use this hook to install additional Vue plugins, register global components, or add additional router hooks:",[669,64484,64486],{"className":64365,"code":64485,"language":64367,"meta":674,"style":674},"export default ({\n Vue, // the version of Vue being used in the VuePress app\n options, // the options for the root Vue instance\n router, // the router instance for the app\n siteData // site metadata\n}) => {\n // ...apply enhancements to the app\n}\n",[676,64487,64488,64497,64505,64513,64521,64529,64538,64543],{"__ignoreMap":674},[679,64489,64490,64492,64494],{"class":681,"line":682},[679,64491,29245],{"class":685},[679,64493,50460],{"class":685},[679,64495,64496],{"class":693}," ({\n",[679,64498,64499,64502],{"class":681,"line":790},[679,64500,64501],{"class":693}," Vue, ",[679,64503,64504],{"class":1400},"// the version of Vue being used in the VuePress app\n",[679,64506,64507,64510],{"class":681,"line":892},[679,64508,64509],{"class":693}," options, ",[679,64511,64512],{"class":1400},"// the options for the root Vue instance\n",[679,64514,64515,64518],{"class":681,"line":901},[679,64516,64517],{"class":693}," router, ",[679,64519,64520],{"class":1400},"// the router instance for the app\n",[679,64522,64523,64526],{"class":681,"line":909},[679,64524,64525],{"class":693}," siteData ",[679,64527,64528],{"class":1400},"// site metadata\n",[679,64530,64531,64534,64536],{"class":681,"line":918},[679,64532,64533],{"class":693},"}) ",[679,64535,21350],{"class":685},[679,64537,884],{"class":693},[679,64539,64540],{"class":681,"line":935},[679,64541,64542],{"class":1400}," // ...apply enhancements to the app\n",[679,64544,64545],{"class":681,"line":944},[679,64546,996],{"class":693},[651,64548,64549],{},[812,64550,64551],{"href":64551,"rel":64552},"https://vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements",[816],[651,64554,64555,64556,64558,64559,64562],{},"This sounds exactly like what I needed so I quickly setup ",[676,64557,64481],{}," and added the following code. This allows me to call ",[676,64560,64561],{},"Vue.use()"," to install the plugin and set a default cookie if one doesn't exist.",[669,64564,64566],{"className":64365,"code":64565,"language":64367,"meta":674,"style":674},"import VueCookies from 'vue-cookies'\n\nexport default ({ Vue, options, router, siteData }) => {\n\n Vue.use(VueCookies)\n VueCookies.config('120d')\n if( !$cookies.isKey('language') ) {\n VueCookies.set('language','JavaScript');\n }\n\n}\n",[676,64567,64568,64578,64582,64615,64619,64628,64641,64661,64678,64682,64686],{"__ignoreMap":674},[679,64569,64570,64572,64574,64576],{"class":681,"line":682},[679,64571,1999],{"class":685},[679,64573,64388],{"class":693},[679,64575,28887],{"class":685},[679,64577,64393],{"class":689},[679,64579,64580],{"class":681,"line":790},[679,64581,889],{"emptyLinePlaceholder":797},[679,64583,64584,64586,64588,64591,64593,64595,64598,64600,64603,64605,64608,64611,64613],{"class":681,"line":892},[679,64585,29245],{"class":685},[679,64587,50460],{"class":685},[679,64589,64590],{"class":693}," ({ ",[679,64592,43866],{"class":2099},[679,64594,2797],{"class":693},[679,64596,64597],{"class":2099},"options",[679,64599,2797],{"class":693},[679,64601,64602],{"class":2099},"router",[679,64604,2797],{"class":693},[679,64606,64607],{"class":2099},"siteData",[679,64609,64610],{"class":693}," }) ",[679,64612,21350],{"class":685},[679,64614,884],{"class":693},[679,64616,64617],{"class":681,"line":901},[679,64618,889],{"emptyLinePlaceholder":797},[679,64620,64621,64624,64626],{"class":681,"line":909},[679,64622,64623],{"class":693}," Vue.",[679,64625,64410],{"class":880},[679,64627,64413],{"class":693},[679,64629,64630,64633,64635,64637,64639],{"class":681,"line":918},[679,64631,64632],{"class":693}," VueCookies.",[679,64634,64430],{"class":880},[679,64636,745],{"class":693},[679,64638,64435],{"class":689},[679,64640,1339],{"class":693},[679,64642,64643,64645,64647,64649,64652,64655,64657,64659],{"class":681,"line":935},[679,64644,47550],{"class":685},[679,64646,1234],{"class":693},[679,64648,1223],{"class":685},[679,64650,64651],{"class":693},"$cookies.",[679,64653,64654],{"class":880},"isKey",[679,64656,745],{"class":693},[679,64658,64458],{"class":689},[679,64660,16100],{"class":693},[679,64662,64663,64666,64668,64670,64672,64674,64676],{"class":681,"line":944},[679,64664,64665],{"class":693}," VueCookies.",[679,64667,64453],{"class":880},[679,64669,745],{"class":693},[679,64671,64458],{"class":689},[679,64673,1202],{"class":693},[679,64675,64463],{"class":689},[679,64677,1208],{"class":693},[679,64679,64680],{"class":681,"line":959},[679,64681,21405],{"class":693},[679,64683,64684],{"class":681,"line":964},[679,64685,889],{"emptyLinePlaceholder":797},[679,64687,64688],{"class":681,"line":977},[679,64689,996],{"class":693},[651,64691,64692,64693,64696],{},"This actually worked out really well and I was really happy with the solution. That was, until I went to build a production version of the documentation as a test. When I ran ",[676,64694,64695],{},"vuepress build"," I started getting some errors saying that window was not defined and I knew right away that I forgot about an important detail.",[5909,64698,64700],{"id":64699},"browser-api-restrictions","Browser API Restrictions",[651,64702,64703,64704,64709],{},"Because VuePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the ",[812,64705,64708],{"href":64706,"rel":64707},"https://ssr.vuejs.org/en/universal.html",[816],"universal code requirements",". In short, make sure to only access Browser / DOM APIs in beforeMount or mounted hooks.",[651,64711,64712],{},"In order to use code that assumes a browser environment on import, you need to dynamically import them in proper lifecycle hooks:",[669,64714,64716],{"className":64365,"code":64715,"language":64367,"meta":674,"style":674},"\u003Cscript>\nexport default {\n mounted () {\n import('./lib-that-access-window-on-import').then(module => {\n // use code\n })\n }\n}\n\u003C/script>\n",[676,64717,64718,64726,64730,64737,64759,64764,64769,64773,64777],{"__ignoreMap":674},[679,64719,64720,64722,64724],{"class":681,"line":682},[679,64721,4505],{"class":693},[679,64723,47668],{"class":4508},[679,64725,4519],{"class":693},[679,64727,64728],{"class":681,"line":790},[679,64729,49965],{"class":693},[679,64731,64732,64734],{"class":681,"line":892},[679,64733,54604],{"class":880},[679,64735,64736],{"class":693}," () {\n",[679,64738,64739,64742,64744,64747,64749,64751,64753,64755,64757],{"class":681,"line":901},[679,64740,64741],{"class":880}," import",[679,64743,745],{"class":693},[679,64745,64746],{"class":689},"'./lib-that-access-window-on-import'",[679,64748,47213],{"class":693},[679,64750,46728],{"class":880},[679,64752,745],{"class":693},[679,64754,27515],{"class":2099},[679,64756,44760],{"class":685},[679,64758,884],{"class":693},[679,64760,64761],{"class":681,"line":909},[679,64762,64763],{"class":1400}," // use code\n",[679,64765,64766],{"class":681,"line":918},[679,64767,64768],{"class":693}," })\n",[679,64770,64771],{"class":681,"line":935},[679,64772,21405],{"class":693},[679,64774,64775],{"class":681,"line":944},[679,64776,996],{"class":693},[679,64778,64779,64781,64783],{"class":681,"line":959},[679,64780,4586],{"class":693},[679,64782,47668],{"class":4508},[679,64784,4519],{"class":693},[651,64786,64787],{},[812,64788,64789],{"href":64789,"rel":64790},"https://v1.vuepress.vuejs.org/guide/using-vue.html#browser-api-access-restrictions",[816],[651,64792,64793],{},"Armed with this knowledge I tried to hack a few things together but fell flat on my face. After banging my head against the desk a few more times I realized that I needed to look at this from a different angle so it was back to the drawing board for me.",[5909,64795,64797],{"id":64796},"rethinking-the-solution","Rethinking the Solution",[651,64799,64800],{},"When I started to think about the problem more I realized that I only needed a script to run once when the application loads for the visitor. At that point I can check to see if there is a cookie and if there isn't, create a default one. From there I can always give the user the option to change the language from another page.",[651,64802,64803,64804,64807,64808,64811],{},"So now the question was how could I add a script to the application where I could perform this logic. I remember from earlier that in ",[676,64805,64806],{},".vuepress/config.js"," I was adding a favicon to the site using the following code. I did some digging around and sure enough I could push a script here as well by dropping it into the ",[676,64809,64810],{},".vuepress/public/scripts"," folder.",[669,64813,64815],{"className":64365,"code":64814,"language":64367,"meta":674,"style":674},"head: [\n ['link', { rel: 'icon', href: '/favicon.png' }],\n ['script', { src: '/scripts/cookies.js' }]\n],\n",[676,64816,64817,64823,64845,64860],{"__ignoreMap":674},[679,64818,64819,64821],{"class":681,"line":682},[679,64820,11741],{"class":880},[679,64822,28491],{"class":693},[679,64824,64825,64828,64831,64834,64837,64839,64842],{"class":681,"line":790},[679,64826,64827],{"class":693}," [",[679,64829,64830],{"class":689},"'link'",[679,64832,64833],{"class":693},", { rel: ",[679,64835,64836],{"class":689},"'icon'",[679,64838,52908],{"class":693},[679,64840,64841],{"class":689},"'/favicon.png'",[679,64843,64844],{"class":693}," }],\n",[679,64846,64847,64849,64852,64855,64858],{"class":681,"line":892},[679,64848,64827],{"class":693},[679,64850,64851],{"class":689},"'script'",[679,64853,64854],{"class":693},", { src: ",[679,64856,64857],{"class":689},"'/scripts/cookies.js'",[679,64859,53226],{"class":693},[679,64861,64862],{"class":681,"line":901},[679,64863,44016],{"class":693},[651,64865,64866],{},"A VuePress application is a single page application so this was only going to run once. Every subsequent request was loaded through the app so this script would only be called once. This was actually ok for this problem so I continued on and built a small cookie script.",[669,64868,64870],{"className":64365,"code":64869,"language":64367,"meta":674,"style":674},"document.addEventListener(\"DOMContentLoaded\", () => {\n\n // if a cookie has not been defined and they aren't on the language selection page\n if( !cookieExists('language') && window.location.pathname != '/language.html' ) {\n // a cookie doesn't exist yet, we need to create one with a default language.\n document.cookie = `language=javascript;max-age=${60*60*24*120};path=/`;\n // we are setting a default cookie but we still want the visitor to have a chance to change it\n window.location.href=\"/language.html\";\n }\n\n})\n\nfunction cookieExists(name) {\n return document.cookie.split(';').filter((item) => item.trim().startsWith(`${name}=`)).length;\n}\n",[676,64871,64872,64892,64896,64901,64930,64935,64965,64970,64982,64986,64990,64994,64998,65011,65065],{"__ignoreMap":674},[679,64873,64874,64877,64880,64882,64885,64888,64890],{"class":681,"line":682},[679,64875,64876],{"class":693},"document.",[679,64878,64879],{"class":880},"addEventListener",[679,64881,745],{"class":693},[679,64883,64884],{"class":689},"\"DOMContentLoaded\"",[679,64886,64887],{"class":693},", () ",[679,64889,21350],{"class":685},[679,64891,884],{"class":693},[679,64893,64894],{"class":681,"line":790},[679,64895,889],{"emptyLinePlaceholder":797},[679,64897,64898],{"class":681,"line":892},[679,64899,64900],{"class":1400}," // if a cookie has not been defined and they aren't on the language selection page\n",[679,64902,64903,64905,64907,64909,64912,64914,64916,64918,64920,64923,64925,64928],{"class":681,"line":901},[679,64904,47550],{"class":685},[679,64906,1234],{"class":693},[679,64908,1223],{"class":685},[679,64910,64911],{"class":880},"cookieExists",[679,64913,745],{"class":693},[679,64915,64458],{"class":689},[679,64917,2378],{"class":693},[679,64919,4201],{"class":685},[679,64921,64922],{"class":693}," window.location.pathname ",[679,64924,1587],{"class":685},[679,64926,64927],{"class":689}," '/language.html'",[679,64929,5581],{"class":693},[679,64931,64932],{"class":681,"line":909},[679,64933,64934],{"class":1400}," // a cookie doesn't exist yet, we need to create one with a default language.\n",[679,64936,64937,64940,64942,64945,64947,64949,64951,64953,64955,64957,64960,64963],{"class":681,"line":918},[679,64938,64939],{"class":693}," document.cookie ",[679,64941,686],{"class":685},[679,64943,64944],{"class":689}," `language=javascript;max-age=${",[679,64946,34932],{"class":931},[679,64948,4150],{"class":685},[679,64950,34932],{"class":931},[679,64952,4150],{"class":685},[679,64954,56053],{"class":931},[679,64956,4150],{"class":685},[679,64958,64959],{"class":931},"120",[679,64961,64962],{"class":689},"};path=/`",[679,64964,1186],{"class":693},[679,64966,64967],{"class":681,"line":935},[679,64968,64969],{"class":1400}," // we are setting a default cookie but we still want the visitor to have a chance to change it\n",[679,64971,64972,64975,64977,64980],{"class":681,"line":944},[679,64973,64974],{"class":693}," window.location.href",[679,64976,686],{"class":685},[679,64978,64979],{"class":689},"\"/language.html\"",[679,64981,1186],{"class":693},[679,64983,64984],{"class":681,"line":959},[679,64985,21405],{"class":693},[679,64987,64988],{"class":681,"line":964},[679,64989,889],{"emptyLinePlaceholder":797},[679,64991,64992],{"class":681,"line":977},[679,64993,6240],{"class":693},[679,64995,64996],{"class":681,"line":982},[679,64997,889],{"emptyLinePlaceholder":797},[679,64999,65000,65002,65005,65007,65009],{"class":681,"line":988},[679,65001,55109],{"class":685},[679,65003,65004],{"class":880}," cookieExists",[679,65006,745],{"class":693},[679,65008,16334],{"class":2099},[679,65010,4390],{"class":693},[679,65012,65013,65015,65018,65020,65022,65025,65027,65030,65032,65035,65037,65039,65042,65044,65046,65049,65051,65053,65055,65058,65061,65063],{"class":681,"line":993},[679,65014,44767],{"class":685},[679,65016,65017],{"class":693}," document.cookie.",[679,65019,55948],{"class":880},[679,65021,745],{"class":693},[679,65023,65024],{"class":689},"';'",[679,65026,47213],{"class":693},[679,65028,65029],{"class":880},"filter",[679,65031,51931],{"class":693},[679,65033,65034],{"class":2099},"item",[679,65036,2378],{"class":693},[679,65038,21350],{"class":685},[679,65040,65041],{"class":693}," item.",[679,65043,55969],{"class":880},[679,65045,10541],{"class":693},[679,65047,65048],{"class":880},"startsWith",[679,65050,745],{"class":693},[679,65052,56368],{"class":689},[679,65054,16334],{"class":693},[679,65056,65057],{"class":689},"}=`",[679,65059,65060],{"class":693},")).",[679,65062,51959],{"class":931},[679,65064,1186],{"class":693},[679,65066,65067],{"class":681,"line":2129},[679,65068,996],{"class":693},[651,65070,65071],{},"The script will check to see if the cookie exists and if it doesn't it will create a default one and forward you on the language selection page. This is nothing more than a simple markdown page with some copy and a custom component I built to change the cookie value.",[669,65073,65075],{"className":4496,"code":65074,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"language\">\n \u003Cp>Current Language: {{ currentLanguage }}\u003C/p>\n\n \u003Cselect @change=\"updateLanguage($event)\">\n \u003Coption value=\"\">Change Language\u003C/option>\n \u003Coption value=\"javascript\">JavaScript\u003C/option>\n \u003Coption value=\"typescript\">TypeScript\u003C/option>\n \u003C/select>\n\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: 'language-select',\n data() {\n return {\n languages: [\n { label: 'JavaScript', value: 'javascript' },\n { lagel: 'TypeScript', value: 'typescript' }\n ],\n currentLanguage: ''\n }\n },\n methods: {\n updateLanguage(event) {\n const language = event.target.value;\n this.setCookie(language);\n this.currentLanguage = language;\n },\n setCookie(value) {\n document.cookie = `language=${value};max-age=${60*60*24*120};path=/`;\n },\n getCookie() {\n return document.cookie.replace(/(?:(?:^|.*;\\s*)language\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n },\n },\n mounted() {\n this.currentLanguage = this.getCookie();\n }\n}\n\u003C/script>\n",[676,65076,65077,65085,65100,65113,65117,65133,65154,65174,65194,65202,65206,65214,65222,65226,65234,65242,65251,65257,65263,65268,65283,65298,65302,65310,65314,65318,65322,65333,65345,65357,65369,65373,65384,65417,65421,65428,65509,65513,65517,65523,65540,65544,65548],{"__ignoreMap":674},[679,65078,65079,65081,65083],{"class":681,"line":682},[679,65080,4505],{"class":693},[679,65082,45982],{"class":4508},[679,65084,4519],{"class":693},[679,65086,65087,65089,65091,65093,65095,65098],{"class":681,"line":790},[679,65088,11738],{"class":693},[679,65090,4509],{"class":4508},[679,65092,4512],{"class":880},[679,65094,686],{"class":693},[679,65096,65097],{"class":689},"\"language\"",[679,65099,4519],{"class":693},[679,65101,65102,65104,65106,65109,65111],{"class":681,"line":892},[679,65103,4524],{"class":693},[679,65105,651],{"class":4508},[679,65107,65108],{"class":693},">Current Language: {{ currentLanguage }}\u003C/",[679,65110,651],{"class":4508},[679,65112,4519],{"class":693},[679,65114,65115],{"class":681,"line":901},[679,65116,889],{"emptyLinePlaceholder":797},[679,65118,65119,65121,65124,65126,65128,65131],{"class":681,"line":909},[679,65120,4524],{"class":693},[679,65122,65123],{"class":4508},"select",[679,65125,53957],{"class":880},[679,65127,686],{"class":693},[679,65129,65130],{"class":689},"\"updateLanguage($event)\"",[679,65132,4519],{"class":693},[679,65134,65135,65137,65140,65143,65145,65147,65150,65152],{"class":681,"line":918},[679,65136,46026],{"class":693},[679,65138,65139],{"class":4508},"option",[679,65141,65142],{"class":880}," value",[679,65144,686],{"class":693},[679,65146,3579],{"class":689},[679,65148,65149],{"class":693},">Change Language\u003C/",[679,65151,65139],{"class":4508},[679,65153,4519],{"class":693},[679,65155,65156,65158,65160,65162,65164,65167,65170,65172],{"class":681,"line":935},[679,65157,46026],{"class":693},[679,65159,65139],{"class":4508},[679,65161,65142],{"class":880},[679,65163,686],{"class":693},[679,65165,65166],{"class":689},"\"javascript\"",[679,65168,65169],{"class":693},">JavaScript\u003C/",[679,65171,65139],{"class":4508},[679,65173,4519],{"class":693},[679,65175,65176,65178,65180,65182,65184,65187,65190,65192],{"class":681,"line":944},[679,65177,46026],{"class":693},[679,65179,65139],{"class":4508},[679,65181,65142],{"class":880},[679,65183,686],{"class":693},[679,65185,65186],{"class":689},"\"typescript\"",[679,65188,65189],{"class":693},">TypeScript\u003C/",[679,65191,65139],{"class":4508},[679,65193,4519],{"class":693},[679,65195,65196,65198,65200],{"class":681,"line":959},[679,65197,4577],{"class":693},[679,65199,65123],{"class":4508},[679,65201,4519],{"class":693},[679,65203,65204],{"class":681,"line":964},[679,65205,889],{"emptyLinePlaceholder":797},[679,65207,65208,65210,65212],{"class":681,"line":977},[679,65209,11840],{"class":693},[679,65211,4509],{"class":4508},[679,65213,4519],{"class":693},[679,65215,65216,65218,65220],{"class":681,"line":982},[679,65217,4586],{"class":693},[679,65219,45982],{"class":4508},[679,65221,4519],{"class":693},[679,65223,65224],{"class":681,"line":988},[679,65225,889],{"emptyLinePlaceholder":797},[679,65227,65228,65230,65232],{"class":681,"line":993},[679,65229,4505],{"class":693},[679,65231,47668],{"class":4508},[679,65233,4519],{"class":693},[679,65235,65236,65238,65240],{"class":681,"line":2129},[679,65237,29245],{"class":685},[679,65239,50460],{"class":685},[679,65241,884],{"class":693},[679,65243,65244,65246,65249],{"class":681,"line":2140},[679,65245,49970],{"class":693},[679,65247,65248],{"class":689},"'language-select'",[679,65250,12083],{"class":693},[679,65252,65253,65255],{"class":681,"line":2145},[679,65254,49979],{"class":880},[679,65256,2667],{"class":693},[679,65258,65259,65261],{"class":681,"line":2154},[679,65260,21478],{"class":685},[679,65262,884],{"class":693},[679,65264,65265],{"class":681,"line":2159},[679,65266,65267],{"class":693}," languages: [\n",[679,65269,65270,65273,65275,65278,65281],{"class":681,"line":2164},[679,65271,65272],{"class":693}," { label: ",[679,65274,64463],{"class":689},[679,65276,65277],{"class":693},", value: ",[679,65279,65280],{"class":689},"'javascript'",[679,65282,51602],{"class":693},[679,65284,65285,65288,65291,65293,65296],{"class":681,"line":3134},[679,65286,65287],{"class":693}," { lagel: ",[679,65289,65290],{"class":689},"'TypeScript'",[679,65292,65277],{"class":693},[679,65294,65295],{"class":689},"'typescript'",[679,65297,39987],{"class":693},[679,65299,65300],{"class":681,"line":3139},[679,65301,28544],{"class":693},[679,65303,65304,65307],{"class":681,"line":3144},[679,65305,65306],{"class":693}," currentLanguage: ",[679,65308,65309],{"class":689},"''\n",[679,65311,65312],{"class":681,"line":3149},[679,65313,985],{"class":693},[679,65315,65316],{"class":681,"line":3169},[679,65317,28483],{"class":693},[679,65319,65320],{"class":681,"line":3185},[679,65321,50502],{"class":693},[679,65323,65324,65327,65329,65331],{"class":681,"line":3194},[679,65325,65326],{"class":880}," updateLanguage",[679,65328,745],{"class":693},[679,65330,51092],{"class":2099},[679,65332,4390],{"class":693},[679,65334,65335,65337,65340,65342],{"class":681,"line":3199},[679,65336,47094],{"class":685},[679,65338,65339],{"class":931}," language",[679,65341,6883],{"class":685},[679,65343,65344],{"class":693}," event.target.value;\n",[679,65346,65347,65349,65351,65354],{"class":681,"line":3212},[679,65348,50514],{"class":931},[679,65350,664],{"class":693},[679,65352,65353],{"class":880},"setCookie",[679,65355,65356],{"class":693},"(language);\n",[679,65358,65359,65361,65364,65366],{"class":681,"line":3217},[679,65360,50514],{"class":931},[679,65362,65363],{"class":693},".currentLanguage ",[679,65365,686],{"class":685},[679,65367,65368],{"class":693}," language;\n",[679,65370,65371],{"class":681,"line":3222},[679,65372,28763],{"class":693},[679,65374,65375,65378,65380,65382],{"class":681,"line":3227},[679,65376,65377],{"class":880}," setCookie",[679,65379,745],{"class":693},[679,65381,19934],{"class":2099},[679,65383,4390],{"class":693},[679,65385,65386,65389,65391,65394,65396,65399,65401,65403,65405,65407,65409,65411,65413,65415],{"class":681,"line":3232},[679,65387,65388],{"class":693}," document.cookie ",[679,65390,686],{"class":685},[679,65392,65393],{"class":689}," `language=${",[679,65395,19934],{"class":693},[679,65397,65398],{"class":689},"};max-age=${",[679,65400,34932],{"class":931},[679,65402,4150],{"class":685},[679,65404,34932],{"class":931},[679,65406,4150],{"class":685},[679,65408,56053],{"class":931},[679,65410,4150],{"class":685},[679,65412,64959],{"class":931},[679,65414,64962],{"class":689},[679,65416,1186],{"class":693},[679,65418,65419],{"class":681,"line":3499},[679,65420,28763],{"class":693},[679,65422,65423,65426],{"class":681,"line":3509},[679,65424,65425],{"class":880}," getCookie",[679,65427,2667],{"class":693},[679,65429,65430,65432,65434,65436,65438,65440,65443,65446,65448,65450,65453,65456,65458,65461,65463,65465,65468,65470,65472,65474,65477,65479,65482,65484,65486,65488,65491,65493,65496,65498,65500,65502,65504,65507],{"class":681,"line":3516},[679,65431,63468],{"class":685},[679,65433,65017],{"class":693},[679,65435,10549],{"class":880},[679,65437,745],{"class":693},[679,65439,4408],{"class":689},[679,65441,65442],{"class":4415},"(?:(?:",[679,65444,65445],{"class":685},"^|",[679,65447,664],{"class":931},[679,65449,4150],{"class":685},[679,65451,65452],{"class":4415},";",[679,65454,65455],{"class":931},"\\s",[679,65457,4150],{"class":685},[679,65459,65460],{"class":4415},")language",[679,65462,65455],{"class":931},[679,65464,4150],{"class":685},[679,65466,65467],{"class":4411},"\\=",[679,65469,65455],{"class":931},[679,65471,4150],{"class":685},[679,65473,745],{"class":4415},[679,65475,65476],{"class":931},"[",[679,65478,4468],{"class":685},[679,65480,65481],{"class":931},";]",[679,65483,4150],{"class":685},[679,65485,50653],{"class":4415},[679,65487,664],{"class":931},[679,65489,65490],{"class":685},"*$",[679,65492,50653],{"class":4415},[679,65494,65495],{"class":685},"|^",[679,65497,664],{"class":931},[679,65499,65490],{"class":685},[679,65501,4408],{"class":689},[679,65503,2797],{"class":693},[679,65505,65506],{"class":689},"\"$1\"",[679,65508,1208],{"class":693},[679,65510,65511],{"class":681,"line":3531},[679,65512,28763],{"class":693},[679,65514,65515],{"class":681,"line":3536},[679,65516,28483],{"class":693},[679,65518,65519,65521],{"class":681,"line":3541},[679,65520,54604],{"class":880},[679,65522,2667],{"class":693},[679,65524,65525,65527,65529,65531,65533,65535,65538],{"class":681,"line":3546},[679,65526,27825],{"class":931},[679,65528,65363],{"class":693},[679,65530,686],{"class":685},[679,65532,21353],{"class":931},[679,65534,664],{"class":693},[679,65536,65537],{"class":880},"getCookie",[679,65539,9317],{"class":693},[679,65541,65542],{"class":681,"line":3551},[679,65543,21405],{"class":693},[679,65545,65546],{"class":681,"line":3557},[679,65547,996],{"class":693},[679,65549,65550,65552,65554],{"class":681,"line":3567},[679,65551,4586],{"class":693},[679,65553,47668],{"class":4508},[679,65555,4519],{"class":693},[4542,65557,65559],{"id":65558},"custom-component-to-read-cookie","Custom Component to read cookie",[651,65561,65562],{},"Now that everything was in place I needed a way to conditionally check in markdown what language the user was set to. It might make sense to start with the component but I like to start with what I want my markup to look like. If I am in markdown and I want to only display the following code if the user's language selection is TypeScript I would envision writing the following markup.",[669,65564,65566],{"className":59028,"code":65565,"language":43882,"meta":674,"style":674},"\u003Ccode-block langugage=\"typescript\">\n```ts\nclass Greeter {\n greeting: string;\n constructor(message: string) {\n this.greeting = message;\n }\n greet() {\n return \"Hello, \" + this.greeting;\n }\n}\n```\n\u003C/code-block>\n",[676,65567,65568,65573,65577,65581,65585,65589,65593,65597,65601,65605,65609,65613,65617],{"__ignoreMap":674},[679,65569,65570],{"class":681,"line":682},[679,65571,65572],{},"\u003Ccode-block langugage=\"typescript\">\n",[679,65574,65575],{"class":681,"line":790},[679,65576,64256],{},[679,65578,65579],{"class":681,"line":892},[679,65580,64202],{},[679,65582,65583],{"class":681,"line":901},[679,65584,64265],{},[679,65586,65587],{"class":681,"line":909},[679,65588,64270],{},[679,65590,65591],{"class":681,"line":918},[679,65592,64275],{},[679,65594,65595],{"class":681,"line":935},[679,65596,985],{},[679,65598,65599],{"class":681,"line":944},[679,65600,64284],{},[679,65602,65603],{"class":681,"line":959},[679,65604,64289],{},[679,65606,65607],{"class":681,"line":964},[679,65608,985],{},[679,65610,65611],{"class":681,"line":977},[679,65612,996],{},[679,65614,65615],{"class":681,"line":982},[679,65616,62766],{},[679,65618,65619],{"class":681,"line":988},[679,65620,65621],{},"\u003C/code-block>\n",[651,65623,65624,65625,65628],{},"This allows me to write code in normal markdown code fences which makes me happy. To pass whatever is between the component tags you can use a slot and I can use a variable to determine if I should display the content or not. The last piece of the puzzle is to read the cookie value and we can do that in the mounted method because we know at that point the DOM is available. If you create ",[676,65626,65627],{},".vuepress/components/CodeBlock.vue"," with the following the code above should work.",[669,65630,65632],{"className":4496,"code":65631,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"code-block\">\n \u003Cslot v-if=\"display\"/>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: 'code-block',\n props: {\n language: String\n },\n data() {\n return {\n display: false\n }\n },\n methods: {\n getCookie() {\n return document.cookie.replace(/(?:(?:^|.*;\\s*)language\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n },\n },\n mounted() {\n const cookieValue = this.getCookie();\n this.display = cookieValue === this.language;\n }\n}\n\u003C/script>\n",[676,65633,65634,65642,65657,65675,65683,65691,65695,65703,65711,65720,65724,65729,65733,65739,65745,65752,65756,65760,65764,65770,65840,65844,65848,65854,65871,65890,65894,65898],{"__ignoreMap":674},[679,65635,65636,65638,65640],{"class":681,"line":682},[679,65637,4505],{"class":693},[679,65639,45982],{"class":4508},[679,65641,4519],{"class":693},[679,65643,65644,65646,65648,65650,65652,65655],{"class":681,"line":790},[679,65645,11738],{"class":693},[679,65647,4509],{"class":4508},[679,65649,4512],{"class":880},[679,65651,686],{"class":693},[679,65653,65654],{"class":689},"\"code-block\"",[679,65656,4519],{"class":693},[679,65658,65659,65661,65664,65666,65668,65671,65673],{"class":681,"line":892},[679,65660,4524],{"class":693},[679,65662,65663],{"class":4508},"slot",[679,65665,52441],{"class":880},[679,65667,686],{"class":693},[679,65669,65670],{"class":689},"\"display\"",[679,65672,4408],{"class":6561},[679,65674,4519],{"class":693},[679,65676,65677,65679,65681],{"class":681,"line":901},[679,65678,11840],{"class":693},[679,65680,4509],{"class":4508},[679,65682,4519],{"class":693},[679,65684,65685,65687,65689],{"class":681,"line":909},[679,65686,4586],{"class":693},[679,65688,45982],{"class":4508},[679,65690,4519],{"class":693},[679,65692,65693],{"class":681,"line":918},[679,65694,889],{"emptyLinePlaceholder":797},[679,65696,65697,65699,65701],{"class":681,"line":935},[679,65698,4505],{"class":693},[679,65700,47668],{"class":4508},[679,65702,4519],{"class":693},[679,65704,65705,65707,65709],{"class":681,"line":944},[679,65706,29245],{"class":685},[679,65708,50460],{"class":685},[679,65710,884],{"class":693},[679,65712,65713,65715,65718],{"class":681,"line":959},[679,65714,49970],{"class":693},[679,65716,65717],{"class":689},"'code-block'",[679,65719,12083],{"class":693},[679,65721,65722],{"class":681,"line":964},[679,65723,57997],{"class":693},[679,65725,65726],{"class":681,"line":977},[679,65727,65728],{"class":693}," language: String\n",[679,65730,65731],{"class":681,"line":982},[679,65732,28483],{"class":693},[679,65734,65735,65737],{"class":681,"line":988},[679,65736,49979],{"class":880},[679,65738,2667],{"class":693},[679,65740,65741,65743],{"class":681,"line":993},[679,65742,21478],{"class":685},[679,65744,884],{"class":693},[679,65746,65747,65750],{"class":681,"line":2129},[679,65748,65749],{"class":693}," display: ",[679,65751,2649],{"class":931},[679,65753,65754],{"class":681,"line":2140},[679,65755,985],{"class":693},[679,65757,65758],{"class":681,"line":2145},[679,65759,28483],{"class":693},[679,65761,65762],{"class":681,"line":2154},[679,65763,50502],{"class":693},[679,65765,65766,65768],{"class":681,"line":2159},[679,65767,65425],{"class":880},[679,65769,2667],{"class":693},[679,65771,65772,65774,65776,65778,65780,65782,65784,65786,65788,65790,65792,65794,65796,65798,65800,65802,65804,65806,65808,65810,65812,65814,65816,65818,65820,65822,65824,65826,65828,65830,65832,65834,65836,65838],{"class":681,"line":2164},[679,65773,63468],{"class":685},[679,65775,65017],{"class":693},[679,65777,10549],{"class":880},[679,65779,745],{"class":693},[679,65781,4408],{"class":689},[679,65783,65442],{"class":4415},[679,65785,65445],{"class":685},[679,65787,664],{"class":931},[679,65789,4150],{"class":685},[679,65791,65452],{"class":4415},[679,65793,65455],{"class":931},[679,65795,4150],{"class":685},[679,65797,65460],{"class":4415},[679,65799,65455],{"class":931},[679,65801,4150],{"class":685},[679,65803,65467],{"class":4411},[679,65805,65455],{"class":931},[679,65807,4150],{"class":685},[679,65809,745],{"class":4415},[679,65811,65476],{"class":931},[679,65813,4468],{"class":685},[679,65815,65481],{"class":931},[679,65817,4150],{"class":685},[679,65819,50653],{"class":4415},[679,65821,664],{"class":931},[679,65823,65490],{"class":685},[679,65825,50653],{"class":4415},[679,65827,65495],{"class":685},[679,65829,664],{"class":931},[679,65831,65490],{"class":685},[679,65833,4408],{"class":689},[679,65835,2797],{"class":693},[679,65837,65506],{"class":689},[679,65839,1208],{"class":693},[679,65841,65842],{"class":681,"line":3134},[679,65843,28763],{"class":693},[679,65845,65846],{"class":681,"line":3139},[679,65847,28483],{"class":693},[679,65849,65850,65852],{"class":681,"line":3144},[679,65851,54604],{"class":880},[679,65853,2667],{"class":693},[679,65855,65856,65858,65861,65863,65865,65867,65869],{"class":681,"line":3149},[679,65857,54021],{"class":685},[679,65859,65860],{"class":931}," cookieValue",[679,65862,6883],{"class":685},[679,65864,21353],{"class":931},[679,65866,664],{"class":693},[679,65868,65537],{"class":880},[679,65870,9317],{"class":693},[679,65872,65873,65875,65878,65880,65883,65885,65887],{"class":681,"line":3169},[679,65874,27825],{"class":931},[679,65876,65877],{"class":693},".display ",[679,65879,686],{"class":685},[679,65881,65882],{"class":693}," cookieValue ",[679,65884,51108],{"class":685},[679,65886,21353],{"class":931},[679,65888,65889],{"class":693},".language;\n",[679,65891,65892],{"class":681,"line":3185},[679,65893,21405],{"class":693},[679,65895,65896],{"class":681,"line":3194},[679,65897,996],{"class":693},[679,65899,65900,65902,65904],{"class":681,"line":3199},[679,65901,4586],{"class":693},[679,65903,47668],{"class":4508},[679,65905,4519],{"class":693},[4542,65907,9042],{"id":9041},[651,65909,65910],{},"I have been writing code a long time so usually when I come across a problem it's one that I have solved over and over again. The idea of static site generators combining the client and server presents problems that I haven't run into before and that is exciting. If you have a better solution for this I would love to hear about it. I hope someone else got something from this article and as always friends....",[651,65912,41105,65913,41109],{},[41107,65914],{},[786,65916,65917],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sLcMm, html code.shiki .sLcMm{--shiki-default:#032F62;--shiki-dark:#DBEDFF;--shiki-sepia:#032F62}html pre.shiki code .sIS5D, html code.shiki .sIS5D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold;--shiki-sepia:#22863A;--shiki-sepia-font-weight:bold}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}",{"title":674,"searchDepth":790,"depth":790,"links":65919},[65920,65921,65927,65928],{"id":64172,"depth":790,"text":64173},{"id":64319,"depth":790,"text":64320,"children":65922},[65923,65924,65925,65926],{"id":64334,"depth":892,"text":64335},{"id":64474,"depth":892,"text":64475},{"id":64699,"depth":892,"text":64700},{"id":64796,"depth":892,"text":64797},{"id":65558,"depth":790,"text":65559},{"id":9041,"depth":790,"text":9042},"In this article, I am going to talk about a recent documentation site to VuePress and how I was able to solve a problem I ran across.",{"slug":65931,"date":65932,"published":797,"author":798,"tags":65933,"cover":65935},"vuepress-cookies","2019-06-13T10:27:46.175Z",[44169,65934],"vuepress","./vuepress-cookies-cover.png",{"title":474,"description":65929},"blog/2019/06/13/vuepress-cookies","E6K75chgAwykiJa5FJQ1T23Oaba0QY2VRyWrsNQSaN8",{"id":65940,"title":471,"body":65941,"description":67762,"extension":793,"meta":67763,"navigation":797,"path":472,"seo":67768,"stem":67769,"__hash__":67770},"content/blog/2019/07/23/website-new-features-improvements.md",{"type":648,"value":65942,"toc":67749},[65943,65946,65950,65953,65957,65963,65967,65973,65976,65979,65983,65991,65994,66000,66004,66013,66026,66048,66057,66128,66141,66146,66160,66163,66221,66224,66250,66254,66257,66340,66343,66553,66561,66924,66927,66933,66936,66942,66946,66949,66952,66973,66979,67430,67436,67439,67443,67446,67450,67453,67456,67462,67465,67471,67474,67574,67577,67719,67728,67734,67737,67739,67742,67746],[651,65944,65945],{},"I launched my new personal website earlier this year and I am really happy with how it's been working out. I believe in the \"ship it early and often\" philosophy so my first priority is to get something out there and then improve on it later. Well, it's later now, and I have built a list of features and improvements that I wanted to add to this website. In this article, I will tell you what I have been working on and why I made the few changes that I did.",[4542,65947,65949],{"id":65948},"new-look-feel","New Look & Feel",[651,65951,65952],{},"In case you are reading this in a galaxy far far away and this site has completely changed here is what the previous landing page looked like and what it looks like now.",[5909,65954,65956],{"id":65955},"before-january-2019","Before (January 2019)",[651,65958,65959],{},[660,65960],{"alt":65961,"src":65962},"Website Look & Feel Before","/images/blog/2019/07/23/before.png",[5909,65964,65966],{"id":65965},"after-july-2019","After (July 2019)",[651,65968,65969],{},[660,65970],{"alt":65971,"src":65972},"Website Look & Feel After","/images/blog/2019/07/23/after.png",[651,65974,65975],{},"I know it's not a huge difference but it feels much cleaner to me and I added some features that are going really going to help improve my visitor's experience. Design is one of those things that I have just never been really good at.",[651,65977,65978],{},"I am able to put together very simple and clean layouts but trying to come up with the look is where I have struggled forever. The reason for this is I usually go into it without much of a plan. I would jump right into the code and try to start slapping elements together and hope for the best. After some amount of time with nothing to show for it, I would usually give up and move on.",[5909,65980,65982],{"id":65981},"adobe-xd","Adobe XD",[651,65984,65985,65986,65990],{},"I have the entire Adobe Creative Suite so I have been looking for a reason to jump in and learn how to use ",[812,65987,65982],{"href":65988,"rel":65989},"https://www.adobe.com/products/xd.html",[816]," and this was the perfect opportunity. If you aren't familiar with Adobe XD it is a design tool that lets you prototype out a design quickly and share it with the world.",[651,65992,65993],{},"What I really appreciate about this tool is just how non-designer friendly it was. I got up to speed in just a couple hours and I was able to to put together some ideas on what I wanted the home page to look like. I also love that I can quickly duplicate artboards and try something new. Now that I could actually see all of these options in front of me I knew what I wanted the new look and feel to look like. With that, I could now open a code editor and get straight to work knowing what I was going to build.",[651,65995,65996],{},[660,65997],{"alt":65998,"src":65999},"Adobe XD Home Page Layouts","/images/blog/2019/07/23/adobe-xd-layouts.png",[4542,66001,66003],{"id":66002},"bulma-css","Bulma CSS",[651,66005,66006,66007,66012],{},"When I started this project it was the first time that I had ever used the ",[812,66008,66011],{"href":66009,"rel":66010},"https://bulma.io/",[816],"Bulma CSS Framework",". The best way to learn something new is to build something with it so I decided to give it a shot and I am glad I did. I have really enjoyed working with Bulma but as I started to work on the new layout one thing became apparent, I was using Bulma wrong.",[651,66014,66015,66016,66021,66022,66025],{},"The first thing you need to understand is how to create layouts in Bulma. The ",[812,66017,66020],{"href":66018,"rel":66019},"https://bulma.io/documentation/layout/container/",[816],"container class"," is great for creating a simple container that centers your content horizontally. The ",[676,66023,66024],{},".container"," class can be used in any context, but mostly as a direct child of either:",[5316,66027,66028,66033,66038,66043],{},[5332,66029,66030],{},[676,66031,66032],{},".navbar",[5332,66034,66035],{},[676,66036,66037],{},".hero",[5332,66039,66040],{},[676,66041,66042],{},".section",[5332,66044,66045],{},[676,66046,66047],{},".footer",[651,66049,66050,66051,66053,66054,66056],{},"This is where I messed up the first time by not using the top level layouts and container correctly and it was causing some issues. When you want to divide your page into sections you can use the ",[676,66052,66042],{}," class along with ",[676,66055,66024],{}," to create a nice looking responsive section.",[669,66058,66060],{"className":4496,"code":66059,"language":4498,"meta":674,"style":674},"\u003Csection class=\"section\">\n \u003Cdiv class=\"container\">\n \u003Ch1 class=\"title\">Section\u003C/h1>\n \u003C/div>\n\u003C/section>\n",[676,66061,66062,66078,66093,66112,66120],{"__ignoreMap":674},[679,66063,66064,66066,66069,66071,66073,66076],{"class":681,"line":682},[679,66065,4505],{"class":693},[679,66067,66068],{"class":4508},"section",[679,66070,4512],{"class":880},[679,66072,686],{"class":693},[679,66074,66075],{"class":689},"\"section\"",[679,66077,4519],{"class":693},[679,66079,66080,66082,66084,66086,66088,66091],{"class":681,"line":790},[679,66081,11738],{"class":693},[679,66083,4509],{"class":4508},[679,66085,4512],{"class":880},[679,66087,686],{"class":693},[679,66089,66090],{"class":689},"\"container\"",[679,66092,4519],{"class":693},[679,66094,66095,66097,66099,66101,66103,66105,66108,66110],{"class":681,"line":892},[679,66096,4524],{"class":693},[679,66098,11859],{"class":4508},[679,66100,4512],{"class":880},[679,66102,686],{"class":693},[679,66104,5250],{"class":689},[679,66106,66107],{"class":693},">Section\u003C/",[679,66109,11859],{"class":4508},[679,66111,4519],{"class":693},[679,66113,66114,66116,66118],{"class":681,"line":901},[679,66115,11840],{"class":693},[679,66117,4509],{"class":4508},[679,66119,4519],{"class":693},[679,66121,66122,66124,66126],{"class":681,"line":909},[679,66123,4586],{"class":693},[679,66125,66068],{"class":4508},[679,66127,4519],{"class":693},[651,66129,66130,66131,66134,66135,66140],{},"The other problem I was having was with how I was importing Bulma. In my ",[676,66132,66133],{},"/src/main.js"," I was just importing Bulma and this approach wasn't working because I wasn't able to customize my ",[812,66136,66139],{"href":66137,"rel":66138},"https://bulma.io/documentation/customize/variables/",[816],"Bulma variables",". I needed the ability to do this so I could turn off the widescreen and HD breakpoints in the responsive system.",[651,66142,66143,66144],{},"I created a new stylesheet and I import that in ",[676,66145,66133],{},[669,66147,66149],{"className":64365,"code":66148,"language":64367,"meta":674,"style":674},"import '~/assets/style/index.scss';\n",[676,66150,66151],{"__ignoreMap":674},[679,66152,66153,66155,66158],{"class":681,"line":682},[679,66154,1999],{"class":685},[679,66156,66157],{"class":689}," '~/assets/style/index.scss'",[679,66159,1186],{"class":693},[651,66161,66162],{},"In there I import a custom stylesheet called variables before I import Bulma and this allows me to customize everything.",[669,66164,66168],{"className":66165,"code":66166,"language":66167,"meta":674,"style":674},"language-css shiki shiki-themes github-light github-dark github-light","/** Import the Bulma variables */\n@import \"variables\";\n\n/** Import Bulma */\n@import \"~bulma\";\n\n/** Import overrides */\n@import \"overrides\";\n","css",[676,66169,66170,66175,66185,66189,66194,66203,66207,66212],{"__ignoreMap":674},[679,66171,66172],{"class":681,"line":682},[679,66173,66174],{"class":1400},"/** Import the Bulma variables */\n",[679,66176,66177,66180,66183],{"class":681,"line":790},[679,66178,66179],{"class":685},"@import",[679,66181,66182],{"class":689}," \"variables\"",[679,66184,1186],{"class":693},[679,66186,66187],{"class":681,"line":892},[679,66188,889],{"emptyLinePlaceholder":797},[679,66190,66191],{"class":681,"line":901},[679,66192,66193],{"class":1400},"/** Import Bulma */\n",[679,66195,66196,66198,66201],{"class":681,"line":909},[679,66197,66179],{"class":685},[679,66199,66200],{"class":689}," \"~bulma\"",[679,66202,1186],{"class":693},[679,66204,66205],{"class":681,"line":918},[679,66206,889],{"emptyLinePlaceholder":797},[679,66208,66209],{"class":681,"line":935},[679,66210,66211],{"class":1400},"/** Import overrides */\n",[679,66213,66214,66216,66219],{"class":681,"line":944},[679,66215,66179],{"class":685},[679,66217,66218],{"class":689}," \"overrides\"",[679,66220,1186],{"class":693},[651,66222,66223],{},"Now I can turn off those break points in Bulma.",[669,66225,66229],{"className":66226,"code":66227,"language":66228,"meta":674,"style":674},"language-scss shiki shiki-themes github-light github-dark github-light","$widescreen-enabled: false;\n\n$fullhd-enabled: false;\n","scss",[676,66230,66231,66239,66243],{"__ignoreMap":674},[679,66232,66233,66236],{"class":681,"line":682},[679,66234,66235],{"class":2099},"$widescreen-enabled",[679,66237,66238],{"class":693},": false;\n",[679,66240,66241],{"class":681,"line":790},[679,66242,889],{"emptyLinePlaceholder":797},[679,66244,66245,66248],{"class":681,"line":892},[679,66246,66247],{"class":2099},"$fullhd-enabled",[679,66249,66238],{"class":693},[4542,66251,66253],{"id":66252},"recent-posts-grid","Recent Posts Grid",[651,66255,66256],{},"The biggest change and challenge was adding a new layout for recent posts. The first thing I needed to do was grab the 9 latest articles and with GraphQL this is pretty easy.",[669,66258,66262],{"className":66259,"code":66260,"language":66261,"meta":674,"style":674},"language-graphql shiki shiki-themes github-light github-dark github-light","\u003Cpage-query>\nquery Posts {\n recentPosts: allPost(perPage: 9) {\n edges {\n node {\n id\n title\n cover\n path,\n date(format: \"MMMM DD, YYYY\"),\n timeToRead\n }\n }\n }\n}\n\u003C/page-query>\n","graphql",[676,66263,66264,66269,66274,66279,66284,66289,66294,66299,66304,66309,66314,66319,66323,66327,66331,66335],{"__ignoreMap":674},[679,66265,66266],{"class":681,"line":682},[679,66267,66268],{},"\u003Cpage-query>\n",[679,66270,66271],{"class":681,"line":790},[679,66272,66273],{},"query Posts {\n",[679,66275,66276],{"class":681,"line":892},[679,66277,66278],{}," recentPosts: allPost(perPage: 9) {\n",[679,66280,66281],{"class":681,"line":901},[679,66282,66283],{}," edges {\n",[679,66285,66286],{"class":681,"line":909},[679,66287,66288],{}," node {\n",[679,66290,66291],{"class":681,"line":918},[679,66292,66293],{}," id\n",[679,66295,66296],{"class":681,"line":935},[679,66297,66298],{}," title\n",[679,66300,66301],{"class":681,"line":944},[679,66302,66303],{}," cover\n",[679,66305,66306],{"class":681,"line":959},[679,66307,66308],{}," path,\n",[679,66310,66311],{"class":681,"line":964},[679,66312,66313],{}," date(format: \"MMMM DD, YYYY\"),\n",[679,66315,66316],{"class":681,"line":977},[679,66317,66318],{}," timeToRead\n",[679,66320,66321],{"class":681,"line":982},[679,66322,11804],{},[679,66324,66325],{"class":681,"line":988},[679,66326,985],{},[679,66328,66329],{"class":681,"line":993},[679,66330,21405],{},[679,66332,66333],{"class":681,"line":2129},[679,66334,996],{},[679,66336,66337],{"class":681,"line":2140},[679,66338,66339],{},"\u003C/page-query>\n",[651,66341,66342],{},"Now that I have my posts I can loop over them and display them.",[669,66344,66346],{"className":4496,"code":66345,"language":4498,"meta":674,"style":674},"\u003Cdiv class=\"posts\">\n \u003Cdiv v-for=\"post in $page.recentPosts.edges\" :key=\"post.node.id\" class=\"post\">\n \u003Cg-link :to=\"post.node.path\" :aria-label=\"post.node.title\">\n \u003Cdiv class=\"overlay\">\u003C/div>\n \u003Cg-image :src=\"post.node.cover.src\" class=\"post-img\" :alt=\"post.node.title\" />\n \u003Cdiv class=\"post-details fadeIn-bottom\">\n \u003Ch3 class=\"post-title\">{{ post.node.title }}\u003C/h3>\n \u003Cp class=\"post-text\">{{ post.node.date }} • ☕️ {{ post.node.timeToRead }} min read\u003C/p>\n \u003C/div>\n \u003C/g-link>\n \u003C/div>\n\u003C/div>\n",[676,66347,66348,66362,66393,66418,66437,66466,66481,66501,66521,66529,66537,66545],{"__ignoreMap":674},[679,66349,66350,66352,66354,66356,66358,66360],{"class":681,"line":682},[679,66351,4505],{"class":693},[679,66353,4509],{"class":4508},[679,66355,4512],{"class":880},[679,66357,686],{"class":693},[679,66359,10069],{"class":689},[679,66361,4519],{"class":693},[679,66363,66364,66366,66368,66371,66373,66376,66379,66381,66384,66386,66388,66391],{"class":681,"line":790},[679,66365,11738],{"class":693},[679,66367,4509],{"class":4508},[679,66369,66370],{"class":880}," v-for",[679,66372,686],{"class":693},[679,66374,66375],{"class":689},"\"post in $page.recentPosts.edges\"",[679,66377,66378],{"class":880}," :key",[679,66380,686],{"class":693},[679,66382,66383],{"class":689},"\"post.node.id\"",[679,66385,4512],{"class":880},[679,66387,686],{"class":693},[679,66389,66390],{"class":689},"\"post\"",[679,66392,4519],{"class":693},[679,66394,66395,66397,66400,66403,66405,66408,66411,66413,66416],{"class":681,"line":892},[679,66396,4524],{"class":693},[679,66398,66399],{"class":4508},"g-link",[679,66401,66402],{"class":880}," :to",[679,66404,686],{"class":693},[679,66406,66407],{"class":689},"\"post.node.path\"",[679,66409,66410],{"class":880}," :aria-label",[679,66412,686],{"class":693},[679,66414,66415],{"class":689},"\"post.node.title\"",[679,66417,4519],{"class":693},[679,66419,66420,66422,66424,66426,66428,66431,66433,66435],{"class":681,"line":901},[679,66421,46026],{"class":693},[679,66423,4509],{"class":4508},[679,66425,4512],{"class":880},[679,66427,686],{"class":693},[679,66429,66430],{"class":689},"\"overlay\"",[679,66432,4563],{"class":693},[679,66434,4509],{"class":4508},[679,66436,4519],{"class":693},[679,66438,66439,66441,66443,66445,66447,66450,66452,66454,66457,66460,66462,66464],{"class":681,"line":909},[679,66440,46026],{"class":693},[679,66442,52438],{"class":4508},[679,66444,52449],{"class":880},[679,66446,686],{"class":693},[679,66448,66449],{"class":689},"\"post.node.cover.src\"",[679,66451,4512],{"class":880},[679,66453,686],{"class":693},[679,66455,66456],{"class":689},"\"post-img\"",[679,66458,66459],{"class":880}," :alt",[679,66461,686],{"class":693},[679,66463,66415],{"class":689},[679,66465,5387],{"class":693},[679,66467,66468,66470,66472,66474,66476,66479],{"class":681,"line":918},[679,66469,46026],{"class":693},[679,66471,4509],{"class":4508},[679,66473,4512],{"class":880},[679,66475,686],{"class":693},[679,66477,66478],{"class":689},"\"post-details fadeIn-bottom\"",[679,66480,4519],{"class":693},[679,66482,66483,66485,66487,66489,66491,66494,66497,66499],{"class":681,"line":935},[679,66484,4904],{"class":693},[679,66486,5909],{"class":4508},[679,66488,4512],{"class":880},[679,66490,686],{"class":693},[679,66492,66493],{"class":689},"\"post-title\"",[679,66495,66496],{"class":693},">{{ post.node.title }}\u003C/",[679,66498,5909],{"class":4508},[679,66500,4519],{"class":693},[679,66502,66503,66505,66507,66509,66511,66514,66517,66519],{"class":681,"line":944},[679,66504,4904],{"class":693},[679,66506,651],{"class":4508},[679,66508,4512],{"class":880},[679,66510,686],{"class":693},[679,66512,66513],{"class":689},"\"post-text\"",[679,66515,66516],{"class":693},">{{ post.node.date }} • ☕️ {{ post.node.timeToRead }} min read\u003C/",[679,66518,651],{"class":4508},[679,66520,4519],{"class":693},[679,66522,66523,66525,66527],{"class":681,"line":959},[679,66524,46324],{"class":693},[679,66526,4509],{"class":4508},[679,66528,4519],{"class":693},[679,66530,66531,66533,66535],{"class":681,"line":964},[679,66532,4577],{"class":693},[679,66534,66399],{"class":4508},[679,66536,4519],{"class":693},[679,66538,66539,66541,66543],{"class":681,"line":977},[679,66540,11840],{"class":693},[679,66542,4509],{"class":4508},[679,66544,4519],{"class":693},[679,66546,66547,66549,66551],{"class":681,"line":982},[679,66548,4586],{"class":693},[679,66550,4509],{"class":4508},[679,66552,4519],{"class":693},[651,66554,66555,66556,664],{},"The toughest part was with the grid being so dynamic how would I be able to get the sizes I wanted from specific grid items. It turns out this was easier than I thought it would be using CSS",[812,66557,66560],{"href":66558,"rel":66559},"https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child",[816],": nth-child() selector",[669,66562,66564],{"className":66165,"code":66563,"language":66167,"meta":674,"style":674},".posts {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n grid-gap: 10px;\n margin-bottom: 30px;\n}\n\n.post {\n position: relative;\n overflow: hidden;\n}\n.post:nth-child(1),\n.post:nth-child(6),\n.post:nth-child(7) {\n grid-column: span 2;\n grid-row: span 2;\n}\n.post:nth-child(6) {\n grid-column: 2/4;\n grid-row: 3/5;\n}\n.post:nth-child(4),\n.post:nth-child(5) {\n grid-column: 1;\n}\n.post:nth-child(3),\n.post:nth-child(5),\n.post:nth-child(9) {\n display: flex;\n align-self: flex-end;\n}\n.post-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n",[676,66565,66566,66573,66585,66609,66622,66636,66640,66644,66650,66662,66674,66678,66690,66701,66712,66724,66735,66739,66749,66763,66778,66782,66792,66802,66812,66816,66826,66836,66847,66858,66870,66874,66881,66895,66908,66920],{"__ignoreMap":674},[679,66567,66568,66571],{"class":681,"line":682},[679,66569,66570],{"class":880},".posts",[679,66572,884],{"class":693},[679,66574,66575,66578,66580,66583],{"class":681,"line":790},[679,66576,66577],{"class":931}," display",[679,66579,4282],{"class":693},[679,66581,66582],{"class":931},"grid",[679,66584,1186],{"class":693},[679,66586,66587,66590,66592,66595,66597,66600,66602,66604,66607],{"class":681,"line":892},[679,66588,66589],{"class":931}," grid-template-columns",[679,66591,4282],{"class":693},[679,66593,66594],{"class":931},"repeat",[679,66596,745],{"class":693},[679,66598,66599],{"class":931},"3",[679,66601,2797],{"class":693},[679,66603,1557],{"class":931},[679,66605,66606],{"class":685},"fr",[679,66608,1208],{"class":693},[679,66610,66611,66614,66616,66618,66620],{"class":681,"line":901},[679,66612,66613],{"class":931}," grid-gap",[679,66615,4282],{"class":693},[679,66617,1205],{"class":931},[679,66619,11797],{"class":685},[679,66621,1186],{"class":693},[679,66623,66624,66627,66629,66632,66634],{"class":681,"line":909},[679,66625,66626],{"class":931}," margin-bottom",[679,66628,4282],{"class":693},[679,66630,66631],{"class":931},"30",[679,66633,11797],{"class":685},[679,66635,1186],{"class":693},[679,66637,66638],{"class":681,"line":918},[679,66639,996],{"class":693},[679,66641,66642],{"class":681,"line":935},[679,66643,889],{"emptyLinePlaceholder":797},[679,66645,66646,66648],{"class":681,"line":944},[679,66647,7865],{"class":880},[679,66649,884],{"class":693},[679,66651,66652,66655,66657,66660],{"class":681,"line":959},[679,66653,66654],{"class":931}," position",[679,66656,4282],{"class":693},[679,66658,66659],{"class":931},"relative",[679,66661,1186],{"class":693},[679,66663,66664,66667,66669,66672],{"class":681,"line":964},[679,66665,66666],{"class":931}," overflow",[679,66668,4282],{"class":693},[679,66670,66671],{"class":931},"hidden",[679,66673,1186],{"class":693},[679,66675,66676],{"class":681,"line":977},[679,66677,996],{"class":693},[679,66679,66680,66683,66685,66687],{"class":681,"line":982},[679,66681,66682],{"class":880},".post:nth-child",[679,66684,745],{"class":693},[679,66686,1557],{"class":931},[679,66688,66689],{"class":693},"),\n",[679,66691,66692,66694,66696,66699],{"class":681,"line":988},[679,66693,66682],{"class":880},[679,66695,745],{"class":693},[679,66697,66698],{"class":931},"6",[679,66700,66689],{"class":693},[679,66702,66703,66705,66707,66710],{"class":681,"line":993},[679,66704,66682],{"class":880},[679,66706,745],{"class":693},[679,66708,66709],{"class":931},"7",[679,66711,4390],{"class":693},[679,66713,66714,66717,66720,66722],{"class":681,"line":2129},[679,66715,66716],{"class":931}," grid-column",[679,66718,66719],{"class":693},": span ",[679,66721,17262],{"class":931},[679,66723,1186],{"class":693},[679,66725,66726,66729,66731,66733],{"class":681,"line":2140},[679,66727,66728],{"class":931}," grid-row",[679,66730,66719],{"class":693},[679,66732,17262],{"class":931},[679,66734,1186],{"class":693},[679,66736,66737],{"class":681,"line":2145},[679,66738,996],{"class":693},[679,66740,66741,66743,66745,66747],{"class":681,"line":2154},[679,66742,66682],{"class":880},[679,66744,745],{"class":693},[679,66746,66698],{"class":931},[679,66748,4390],{"class":693},[679,66750,66751,66753,66755,66757,66759,66761],{"class":681,"line":2159},[679,66752,66716],{"class":931},[679,66754,4282],{"class":693},[679,66756,17262],{"class":931},[679,66758,4408],{"class":693},[679,66760,49776],{"class":931},[679,66762,1186],{"class":693},[679,66764,66765,66767,66769,66771,66773,66776],{"class":681,"line":2164},[679,66766,66728],{"class":931},[679,66768,4282],{"class":693},[679,66770,66599],{"class":931},[679,66772,4408],{"class":693},[679,66774,66775],{"class":931},"5",[679,66777,1186],{"class":693},[679,66779,66780],{"class":681,"line":3134},[679,66781,996],{"class":693},[679,66783,66784,66786,66788,66790],{"class":681,"line":3139},[679,66785,66682],{"class":880},[679,66787,745],{"class":693},[679,66789,49776],{"class":931},[679,66791,66689],{"class":693},[679,66793,66794,66796,66798,66800],{"class":681,"line":3144},[679,66795,66682],{"class":880},[679,66797,745],{"class":693},[679,66799,66775],{"class":931},[679,66801,4390],{"class":693},[679,66803,66804,66806,66808,66810],{"class":681,"line":3149},[679,66805,66716],{"class":931},[679,66807,4282],{"class":693},[679,66809,1557],{"class":931},[679,66811,1186],{"class":693},[679,66813,66814],{"class":681,"line":3169},[679,66815,996],{"class":693},[679,66817,66818,66820,66822,66824],{"class":681,"line":3185},[679,66819,66682],{"class":880},[679,66821,745],{"class":693},[679,66823,66599],{"class":931},[679,66825,66689],{"class":693},[679,66827,66828,66830,66832,66834],{"class":681,"line":3194},[679,66829,66682],{"class":880},[679,66831,745],{"class":693},[679,66833,66775],{"class":931},[679,66835,66689],{"class":693},[679,66837,66838,66840,66842,66845],{"class":681,"line":3199},[679,66839,66682],{"class":880},[679,66841,745],{"class":693},[679,66843,66844],{"class":931},"9",[679,66846,4390],{"class":693},[679,66848,66849,66851,66853,66856],{"class":681,"line":3212},[679,66850,66577],{"class":931},[679,66852,4282],{"class":693},[679,66854,66855],{"class":931},"flex",[679,66857,1186],{"class":693},[679,66859,66860,66863,66865,66868],{"class":681,"line":3217},[679,66861,66862],{"class":931}," align-self",[679,66864,4282],{"class":693},[679,66866,66867],{"class":931},"flex-end",[679,66869,1186],{"class":693},[679,66871,66872],{"class":681,"line":3222},[679,66873,996],{"class":693},[679,66875,66876,66879],{"class":681,"line":3227},[679,66877,66878],{"class":880},".post-img",[679,66880,884],{"class":693},[679,66882,66883,66886,66888,66890,66893],{"class":681,"line":3232},[679,66884,66885],{"class":931}," width",[679,66887,4282],{"class":693},[679,66889,57791],{"class":931},[679,66891,66892],{"class":685},"%",[679,66894,1186],{"class":693},[679,66896,66897,66900,66902,66904,66906],{"class":681,"line":3499},[679,66898,66899],{"class":931}," height",[679,66901,4282],{"class":693},[679,66903,57791],{"class":931},[679,66905,66892],{"class":685},[679,66907,1186],{"class":693},[679,66909,66910,66913,66915,66918],{"class":681,"line":3509},[679,66911,66912],{"class":931}," object-fit",[679,66914,4282],{"class":693},[679,66916,66917],{"class":931},"cover",[679,66919,1186],{"class":693},[679,66921,66922],{"class":681,"line":3516},[679,66923,996],{"class":693},[651,66925,66926],{},"Which gives us this really nice grid layout.",[651,66928,66929],{},[660,66930],{"alt":66931,"src":66932},"CSS Grid Layout","/images/blog/2019/07/23/grid-layout.png",[651,66934,66935],{},"The other thing to think about is what is this going to look like for mobile users. In this case, I decided to use a media query to just stack them when we hit a certain breakpoint.",[651,66937,66938],{},[660,66939],{"alt":66940,"src":66941},"CSS Grid Layout on Mobile","/images/blog/2019/07/23/grid-layout-mobile.png",[4542,66943,66945],{"id":66944},"search","Search",[651,66947,66948],{},"When I first started this blog I did so without migrating posts from my current blog which had close to 1000 articles on it. Because I only had a few I had no real need for search at the time. Now that I have been writing for a while and I have migrated some of my posts over I have a real need for it.",[651,66950,66951],{},"My first pass on this was to use Algolia Search which I have to say is a really great product. I actually had it working pretty well but it did take me awhile and what I found out in the end is that it was overkill for what I needed to do.",[651,66953,66954,66955,66960,66961,66966,66967,66972],{},"I saw a ",[812,66956,66959],{"href":66957,"rel":66958},"https://www.youtube.com/watch?v=6i8D8j5Gkk8",[816],"presentation from Andre Madarang"," on VueJS Search with ",[812,66962,66965],{"href":66963,"rel":66964},"https://github.com/shayneo/vue-fuse",[816],"vue-fuse",". This was the first time I heard of vue-fuse and I was really excited about the idea. Vue Fuse is a wrappper for ",[812,66968,66971],{"href":66969,"rel":66970},"https://fusejs.io/",[816],"Fuse.js"," library which is a lightweight fuzzy search library.",[651,66974,66975,66976],{},"I pretty much followed Andre's tutorial to get the search component setup and then realized that for this to work we need to build a json file of blog posts each time we build as the source for the search. In Gridsome you can do this in the ",[676,66977,66978],{},"gridsome.server.js",[669,66980,66982],{"className":64365,"code":66981,"language":64367,"meta":674,"style":674},"const fs = require('fs');\nconst path = require('path');\nconst pick = require('lodash.pick');\n\nmodule.exports = function (api, options) {\n api.loadSource(store => {\n // Use the Data store API here: https://gridsome.org/docs/data-store-api\n })\n\n api.beforeBuild(({ config, store }) => {\n\n // Generate an index file for Fuse to search Posts\n const { collection } = store.getContentType('Post');\n\n const posts = collection.data.map(post => {\n return pick(post, ['title', 'path', 'excerpt']);\n });\n\n const output = {\n dir: './static',\n name: 'search.json',\n ...options.output\n }\n\n const outputPath = path.resolve(process.cwd(), output.dir)\n const outputPathExists = fs.existsSync(outputPath)\n const fileName = output.name.endsWith('.json')\n ? output.name\n : `${output.name}.json`\n\n if (outputPathExists) {\n fs.writeFileSync(path.resolve(process.cwd(), output.dir, fileName), JSON.stringify(posts))\n } else {\n fs.mkdirSync(outputPath)\n fs.writeFileSync(path.resolve(process.cwd(), output.dir, fileName), JSON.stringify(posts))\n }\n })\n}\n",[676,66983,66984,67001,67018,67036,67040,67062,67079,67084,67088,67092,67114,67118,67123,67148,67152,67174,67197,67201,67205,67215,67225,67234,67242,67246,67250,67274,67291,67313,67321,67338,67342,67349,67378,67386,67394,67418,67422,67426],{"__ignoreMap":674},[679,66985,66986,66988,66990,66992,66994,66996,66999],{"class":681,"line":682},[679,66987,45172],{"class":685},[679,66989,48937],{"class":931},[679,66991,6883],{"class":685},[679,66993,45180],{"class":880},[679,66995,745],{"class":693},[679,66997,66998],{"class":689},"'fs'",[679,67000,1208],{"class":693},[679,67002,67003,67005,67007,67009,67011,67013,67016],{"class":681,"line":790},[679,67004,45172],{"class":685},[679,67006,18596],{"class":931},[679,67008,6883],{"class":685},[679,67010,45180],{"class":880},[679,67012,745],{"class":693},[679,67014,67015],{"class":689},"'path'",[679,67017,1208],{"class":693},[679,67019,67020,67022,67025,67027,67029,67031,67034],{"class":681,"line":892},[679,67021,45172],{"class":685},[679,67023,67024],{"class":931}," pick",[679,67026,6883],{"class":685},[679,67028,45180],{"class":880},[679,67030,745],{"class":693},[679,67032,67033],{"class":689},"'lodash.pick'",[679,67035,1208],{"class":693},[679,67037,67038],{"class":681,"line":901},[679,67039,889],{"emptyLinePlaceholder":797},[679,67041,67042,67044,67046,67048,67050,67052,67054,67056,67058,67060],{"class":681,"line":909},[679,67043,27515],{"class":931},[679,67045,664],{"class":693},[679,67047,43903],{"class":931},[679,67049,6883],{"class":685},[679,67051,21700],{"class":685},[679,67053,4193],{"class":693},[679,67055,24355],{"class":2099},[679,67057,2797],{"class":693},[679,67059,64597],{"class":2099},[679,67061,4390],{"class":693},[679,67063,67064,67067,67070,67072,67075,67077],{"class":681,"line":918},[679,67065,67066],{"class":693}," api.",[679,67068,67069],{"class":880},"loadSource",[679,67071,745],{"class":693},[679,67073,67074],{"class":2099},"store",[679,67076,44760],{"class":685},[679,67078,884],{"class":693},[679,67080,67081],{"class":681,"line":935},[679,67082,67083],{"class":1400}," // Use the Data store API here: https://gridsome.org/docs/data-store-api\n",[679,67085,67086],{"class":681,"line":944},[679,67087,46752],{"class":693},[679,67089,67090],{"class":681,"line":959},[679,67091,889],{"emptyLinePlaceholder":797},[679,67093,67094,67096,67099,67102,67104,67106,67108,67110,67112],{"class":681,"line":964},[679,67095,67066],{"class":693},[679,67097,67098],{"class":880},"beforeBuild",[679,67100,67101],{"class":693},"(({ ",[679,67103,64430],{"class":2099},[679,67105,2797],{"class":693},[679,67107,67074],{"class":2099},[679,67109,64610],{"class":693},[679,67111,21350],{"class":685},[679,67113,884],{"class":693},[679,67115,67116],{"class":681,"line":977},[679,67117,889],{"emptyLinePlaceholder":797},[679,67119,67120],{"class":681,"line":982},[679,67121,67122],{"class":1400}," // Generate an index file for Fuse to search Posts\n",[679,67124,67125,67127,67129,67132,67134,67136,67139,67142,67144,67146],{"class":681,"line":988},[679,67126,54021],{"class":685},[679,67128,2998],{"class":693},[679,67130,67131],{"class":931},"collection",[679,67133,55483],{"class":693},[679,67135,686],{"class":685},[679,67137,67138],{"class":693}," store.",[679,67140,67141],{"class":880},"getContentType",[679,67143,745],{"class":693},[679,67145,52347],{"class":689},[679,67147,1208],{"class":693},[679,67149,67150],{"class":681,"line":993},[679,67151,889],{"emptyLinePlaceholder":797},[679,67153,67154,67156,67159,67161,67164,67166,67168,67170,67172],{"class":681,"line":2129},[679,67155,54021],{"class":685},[679,67157,67158],{"class":931}," posts",[679,67160,6883],{"class":685},[679,67162,67163],{"class":693}," collection.data.",[679,67165,51904],{"class":880},[679,67167,745],{"class":693},[679,67169,5100],{"class":2099},[679,67171,44760],{"class":685},[679,67173,884],{"class":693},[679,67175,67176,67178,67180,67183,67185,67187,67189,67191,67194],{"class":681,"line":2140},[679,67177,63468],{"class":685},[679,67179,67024],{"class":880},[679,67181,67182],{"class":693},"(post, [",[679,67184,25509],{"class":689},[679,67186,2797],{"class":693},[679,67188,67015],{"class":689},[679,67190,2797],{"class":693},[679,67192,67193],{"class":689},"'excerpt'",[679,67195,67196],{"class":693},"]);\n",[679,67198,67199],{"class":681,"line":2145},[679,67200,59286],{"class":693},[679,67202,67203],{"class":681,"line":2154},[679,67204,889],{"emptyLinePlaceholder":797},[679,67206,67207,67209,67211,67213],{"class":681,"line":2159},[679,67208,54021],{"class":685},[679,67210,62384],{"class":931},[679,67212,6883],{"class":685},[679,67214,884],{"class":693},[679,67216,67217,67220,67223],{"class":681,"line":2164},[679,67218,67219],{"class":693}," dir: ",[679,67221,67222],{"class":689},"'./static'",[679,67224,12083],{"class":693},[679,67226,67227,67229,67232],{"class":681,"line":3134},[679,67228,55516],{"class":693},[679,67230,67231],{"class":689},"'search.json'",[679,67233,12083],{"class":693},[679,67235,67236,67239],{"class":681,"line":3139},[679,67237,67238],{"class":685}," ...",[679,67240,67241],{"class":693},"options.output\n",[679,67243,67244],{"class":681,"line":3144},[679,67245,985],{"class":693},[679,67247,67248],{"class":681,"line":3149},[679,67249,889],{"emptyLinePlaceholder":797},[679,67251,67252,67254,67257,67259,67262,67265,67268,67271],{"class":681,"line":3169},[679,67253,54021],{"class":685},[679,67255,67256],{"class":931}," outputPath",[679,67258,6883],{"class":685},[679,67260,67261],{"class":693}," path.",[679,67263,67264],{"class":880},"resolve",[679,67266,67267],{"class":693},"(process.",[679,67269,67270],{"class":880},"cwd",[679,67272,67273],{"class":693},"(), output.dir)\n",[679,67275,67276,67278,67281,67283,67286,67288],{"class":681,"line":3185},[679,67277,54021],{"class":685},[679,67279,67280],{"class":931}," outputPathExists",[679,67282,6883],{"class":685},[679,67284,67285],{"class":693}," fs.",[679,67287,56027],{"class":880},[679,67289,67290],{"class":693},"(outputPath)\n",[679,67292,67293,67295,67298,67300,67303,67306,67308,67311],{"class":681,"line":3194},[679,67294,54021],{"class":685},[679,67296,67297],{"class":931}," fileName",[679,67299,6883],{"class":685},[679,67301,67302],{"class":693}," output.name.",[679,67304,67305],{"class":880},"endsWith",[679,67307,745],{"class":693},[679,67309,67310],{"class":689},"'.json'",[679,67312,1339],{"class":693},[679,67314,67315,67318],{"class":681,"line":3199},[679,67316,67317],{"class":685}," ?",[679,67319,67320],{"class":693}," output.name\n",[679,67322,67323,67326,67328,67331,67333,67335],{"class":681,"line":3212},[679,67324,67325],{"class":685}," :",[679,67327,48656],{"class":689},[679,67329,67330],{"class":693},"output",[679,67332,664],{"class":689},[679,67334,16334],{"class":693},[679,67336,67337],{"class":689},"}.json`\n",[679,67339,67340],{"class":681,"line":3217},[679,67341,889],{"emptyLinePlaceholder":797},[679,67343,67344,67346],{"class":681,"line":3222},[679,67345,1231],{"class":685},[679,67347,67348],{"class":693}," (outputPathExists) {\n",[679,67350,67351,67354,67356,67359,67361,67363,67365,67368,67371,67373,67375],{"class":681,"line":3227},[679,67352,67353],{"class":693}," fs.",[679,67355,56349],{"class":880},[679,67357,67358],{"class":693},"(path.",[679,67360,67264],{"class":880},[679,67362,67267],{"class":693},[679,67364,67270],{"class":880},[679,67366,67367],{"class":693},"(), output.dir, fileName), ",[679,67369,67370],{"class":931},"JSON",[679,67372,664],{"class":693},[679,67374,56185],{"class":880},[679,67376,67377],{"class":693},"(posts))\n",[679,67379,67380,67382,67384],{"class":681,"line":3232},[679,67381,47298],{"class":693},[679,67383,2256],{"class":685},[679,67385,884],{"class":693},[679,67387,67388,67390,67392],{"class":681,"line":3499},[679,67389,67353],{"class":693},[679,67391,48972],{"class":880},[679,67393,67290],{"class":693},[679,67395,67396,67398,67400,67402,67404,67406,67408,67410,67412,67414,67416],{"class":681,"line":3509},[679,67397,67353],{"class":693},[679,67399,56349],{"class":880},[679,67401,67358],{"class":693},[679,67403,67264],{"class":880},[679,67405,67267],{"class":693},[679,67407,67270],{"class":880},[679,67409,67367],{"class":693},[679,67411,67370],{"class":931},[679,67413,664],{"class":693},[679,67415,56185],{"class":880},[679,67417,67377],{"class":693},[679,67419,67420],{"class":681,"line":3516},[679,67421,985],{"class":693},[679,67423,67424],{"class":681,"line":3531},[679,67425,46752],{"class":693},[679,67427,67428],{"class":681,"line":3536},[679,67429,996],{"class":693},[651,67431,67432],{},[660,67433],{"alt":67434,"src":67435},"Search Results","/images/blog/2019/07/23/search-results.png",[651,67437,67438],{},"The styles don't look great on this right now but that is something I can come back and fix up later.",[4542,67440,67442],{"id":67441},"newsletter","Newsletter",[651,67444,67445],{},"Another one of the main reasons for the new look and feel is because I wanted to get a call to action front and center when you visited my home page. I am working on plans to actually start writing a newsletter every Sunday so I am trying to build my list up.",[4542,67447,67449],{"id":67448},"video-covers","Video Covers",[651,67451,67452],{},"There are a few blog posts that I have screencasts of and in those cases, I want to make the visitor aware of them. Some people (heck probably most) would prefer to watch a video over reading a post so I want to start offering this to visitors.",[651,67454,67455],{},"I went back to Adobe XD and tried to come up with a solution to this problem. My first and only idea was to just put a link below the cover image if there was a video. This functionality would work but didn't look all that great and didn't feature the video the way I wanted it to.",[651,67457,67458],{},[660,67459],{"alt":67460,"src":67461},"Video Cover Mockup","/images/blog/2019/07/23/video-cover-mockup.png",[651,67463,67464],{},"I asked for some advice on Twitter and Ryan Edgar replied with an awesome suggestion.",[651,67466,67467],{},[812,67468,67469],{"href":67469,"rel":67470},"https://twitter.com/ryanedg/status/1147426471633440768",[816],[651,67472,67473],{},"I immediately got to work and to get started I added a new property to the front matter for this blog post.",[669,67475,67478],{"className":67476,"code":67477,"language":56308,"meta":674,"style":674},"language-yaml shiki shiki-themes github-light github-dark github-light","---\nslug: 'triggering-events-router-vue'\ntitle: 'Triggering events from Vue Router views'\ndate: '2019-06-05T20:13:17.445Z'\npublished: true\ndescription: 'In this article, I will show you how to trigger events from views using the Router View component.'\nauthor: 'Dan Vega'\ntags: ['vue']\ncover: ./triggering-events-router-vue-cover.png\nvideo: https://www.youtube.com/embed/JwccQYpsE2Q\n---\n",[676,67479,67480,67484,67493,67502,67512,67521,67530,67538,67551,67560,67570],{"__ignoreMap":674},[679,67481,67482],{"class":681,"line":682},[679,67483,31635],{"class":880},[679,67485,67486,67488,67490],{"class":681,"line":790},[679,67487,55311],{"class":4508},[679,67489,4282],{"class":693},[679,67491,67492],{"class":689},"'triggering-events-router-vue'\n",[679,67494,67495,67497,67499],{"class":681,"line":892},[679,67496,11750],{"class":4508},[679,67498,4282],{"class":693},[679,67500,67501],{"class":689},"'Triggering events from Vue Router views'\n",[679,67503,67504,67507,67509],{"class":681,"line":901},[679,67505,67506],{"class":4508},"date",[679,67508,4282],{"class":693},[679,67510,67511],{"class":689},"'2019-06-05T20:13:17.445Z'\n",[679,67513,67514,67517,67519],{"class":681,"line":909},[679,67515,67516],{"class":4508},"published",[679,67518,4282],{"class":693},[679,67520,5134],{"class":931},[679,67522,67523,67525,67527],{"class":681,"line":918},[679,67524,47867],{"class":4508},[679,67526,4282],{"class":693},[679,67528,67529],{"class":689},"'In this article, I will show you how to trigger events from views using the Router View component.'\n",[679,67531,67532,67534,67536],{"class":681,"line":935},[679,67533,6526],{"class":4508},[679,67535,4282],{"class":693},[679,67537,25161],{"class":689},[679,67539,67540,67542,67545,67548],{"class":681,"line":944},[679,67541,55480],{"class":4508},[679,67543,67544],{"class":693},": [",[679,67546,67547],{"class":689},"'vue'",[679,67549,67550],{"class":693},"]\n",[679,67552,67553,67555,67557],{"class":681,"line":959},[679,67554,66917],{"class":4508},[679,67556,4282],{"class":693},[679,67558,67559],{"class":689},"./triggering-events-router-vue-cover.png\n",[679,67561,67562,67565,67567],{"class":681,"line":964},[679,67563,67564],{"class":4508},"video",[679,67566,4282],{"class":693},[679,67568,67569],{"class":689},"https://www.youtube.com/embed/JwccQYpsE2Q\n",[679,67571,67572],{"class":681,"line":977},[679,67573,31635],{"class":880},[651,67575,67576],{},"Now in my post template I could check to see if the post had a video and if did I would display that an not the cover.",[669,67578,67580],{"className":4496,"code":67579,"language":4498,"meta":674,"style":674},"\u003Cdiv class=\"embed-container\" v-if=\"$page.post.video\">\n \u003Ciframe\n width=\"1000\"\n height=\"563\"\n :src=\"$page.post.video\"\n frameborder=\"0\"\n allow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\"\n allowfullscreen\n v-if=\"$page.post.video\"\n >\u003C/iframe>\n\u003C/div>\n\u003Cg-image v-if=\"!$page.post.video && $page.post.cover\" :src=\"$page.post.cover\" class=\"cover\" />\n",[676,67581,67582,67604,67611,67621,67631,67641,67651,67661,67666,67675,67684,67692],{"__ignoreMap":674},[679,67583,67584,67586,67588,67590,67592,67595,67597,67599,67602],{"class":681,"line":682},[679,67585,4505],{"class":693},[679,67587,4509],{"class":4508},[679,67589,4512],{"class":880},[679,67591,686],{"class":693},[679,67593,67594],{"class":689},"\"embed-container\"",[679,67596,52441],{"class":880},[679,67598,686],{"class":693},[679,67600,67601],{"class":689},"\"$page.post.video\"",[679,67603,4519],{"class":693},[679,67605,67606,67608],{"class":681,"line":790},[679,67607,11738],{"class":693},[679,67609,67610],{"class":4508},"iframe\n",[679,67612,67613,67616,67618],{"class":681,"line":892},[679,67614,67615],{"class":880}," width",[679,67617,686],{"class":693},[679,67619,67620],{"class":689},"\"1000\"\n",[679,67622,67623,67626,67628],{"class":681,"line":901},[679,67624,67625],{"class":880}," height",[679,67627,686],{"class":693},[679,67629,67630],{"class":689},"\"563\"\n",[679,67632,67633,67636,67638],{"class":681,"line":909},[679,67634,67635],{"class":880}," :src",[679,67637,686],{"class":693},[679,67639,67640],{"class":689},"\"$page.post.video\"\n",[679,67642,67643,67646,67648],{"class":681,"line":918},[679,67644,67645],{"class":880}," frameborder",[679,67647,686],{"class":693},[679,67649,67650],{"class":689},"\"0\"\n",[679,67652,67653,67656,67658],{"class":681,"line":935},[679,67654,67655],{"class":880}," allow",[679,67657,686],{"class":693},[679,67659,67660],{"class":689},"\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\"\n",[679,67662,67663],{"class":681,"line":944},[679,67664,67665],{"class":880}," allowfullscreen\n",[679,67667,67668,67671,67673],{"class":681,"line":959},[679,67669,67670],{"class":880}," v-if",[679,67672,686],{"class":693},[679,67674,67640],{"class":689},[679,67676,67677,67680,67682],{"class":681,"line":964},[679,67678,67679],{"class":693}," >\u003C/",[679,67681,59634],{"class":4508},[679,67683,4519],{"class":693},[679,67685,67686,67688,67690],{"class":681,"line":977},[679,67687,4586],{"class":693},[679,67689,4509],{"class":4508},[679,67691,4519],{"class":693},[679,67693,67694,67696,67698,67700,67702,67705,67707,67709,67711,67713,67715,67717],{"class":681,"line":982},[679,67695,4505],{"class":693},[679,67697,52438],{"class":4508},[679,67699,52441],{"class":880},[679,67701,686],{"class":693},[679,67703,67704],{"class":689},"\"!$page.post.video && $page.post.cover\"",[679,67706,52449],{"class":880},[679,67708,686],{"class":693},[679,67710,52446],{"class":689},[679,67712,4512],{"class":880},[679,67714,686],{"class":693},[679,67716,52460],{"class":689},[679,67718,5387],{"class":693},[651,67720,67721,67722,67727],{},"Now when you go to a ",[812,67723,67726],{"href":67724,"rel":67725},"https://www.danvega.dev/blog/2019/06/05/triggering-events-router-vue",[816],"post that has a video"," it will display the YouTube embed instead of the cover image.",[651,67729,67730],{},[660,67731],{"alt":67732,"src":67733},"Blog Post Video Cover","/images/blog/2019/07/23/video-cover.png",[651,67735,67736],{},"If you're going to do something similar it's important to remember that you still need a cover image even if you aren't displaying one. This is because you will still use that image for social media sharing and you don't want to lose that.",[4542,67738,9042],{"id":9041},[651,67740,67741],{},"I have a lot of fun working on my personal website because I am constantly learning new things. If you can find side projects that are fun and allow you to learn you are getting the best of both worlds. This was meant to be more of an overview of features I have been working on. If there is anything in the post that I talked about and you would like me to go more in-depth, please reach out and let me know. As always friends...",[651,67743,41105,67744,41109],{},[41107,67745],{},[786,67747,67748],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":67750},[67751,67756,67757,67758,67759,67760,67761],{"id":65948,"depth":790,"text":65949,"children":67752},[67753,67754,67755],{"id":65955,"depth":892,"text":65956},{"id":65965,"depth":892,"text":65966},{"id":65981,"depth":892,"text":65982},{"id":66002,"depth":790,"text":66003},{"id":66252,"depth":790,"text":66253},{"id":66944,"depth":790,"text":66945},{"id":67441,"depth":790,"text":67442},{"id":67448,"depth":790,"text":67449},{"id":9041,"depth":790,"text":9042},"In this article I am going to walk you through some of the new features I have been working on for my website",{"slug":67764,"date":67765,"published":797,"author":798,"tags":67766,"cover":67767},"website-new-features-improvements","2019-07-23T11:20:30.619Z",[43724,44169,43847],"./website-new-features-cover.png",{"title":471,"description":67762},"blog/2019/07/23/website-new-features-improvements","eSkCOztJBx9PRDG2jVwQCp8cbxxdaMRJXx8Esd3GtQc",{"id":67772,"title":468,"body":67773,"description":69280,"extension":793,"meta":69281,"navigation":797,"path":469,"seo":69287,"stem":69288,"__hash__":69289},"content/blog/2019/08/08/css-grid-generator.md",{"type":648,"value":67774,"toc":69273},[67775,67778,67781,67795,67798,67803,67809,67813,67825,67830,67834,67837,67842,67845,67859,67862,67867,67870,67875,67878,67889,67894,67901,67906,67915,67919,67922,68190,68197,68376,68379,68850,68860,69044,69054,69112,69115,69120,69129,69234,69237,69241,69244,69246,69257,69266,69270],[651,67776,67777],{},"As someone who has been developing web applications for almost 20 years now, I have seen it all. I started in FrontPage, moved to Dreamweaver, nested tables inside of tables and learned how to float and clear fix everything.",[651,67779,67780],{},"While you won't see any awards for my designs on my bookshelf I am able to create some very nice simple and clean layouts. That said I have never really enjoyed it because it has always been a huge pain in the a$$. I have always felt like I was just hacking away until my layout looked almost like what I had in mind.",[651,67782,67783,67784,27319,67789,67794],{},"This is why when tools like ",[812,67785,67788],{"href":67786,"rel":67787},"https://css-tricks.com/snippets/css/complete-guide-grid/",[816],"CSS Grid",[812,67790,67793],{"href":67791,"rel":67792},"https://css-tricks.com/snippets/css/a-guide-to-flexbox/",[816],"Flexbox"," came along I was beyond excited about the possibilities of creating clean layouts. I think if you combine these 2 technologies along with just how far JavaScript has come it is what has really got me excited about front end development these days.",[651,67796,67797],{},"While I have a pretty good understanding of CSS Grid at this point I know it can be a little bit confusing to get started. That is why I am really excited to share with you a new tool that I came across that I really think can help you out if you're trying to understand CSS Grid.",[651,67799,67800],{},[660,67801],{"alt":674,"src":67802},"/images/blog/2019/08/08/undraw_web_developer_p3e5-bc5394ac-2ec5-4a11-894c-10168231520e.png",[651,67804,67805],{},[812,67806,67807],{"href":67807,"rel":67808},"https://undraw.co/",[816],[4542,67810,67812],{"id":67811},"css-grid-generator-by-sarah-drasner","CSS Grid Generator by Sarah Drasner",[651,67814,67815,67820,67821,67824],{},[812,67816,67819],{"href":67817,"rel":67818},"https://cssgrid-generator.netlify.com/",[816],"CSS Grid Generator"," is a free tool created by the super talented ",[812,67822,57131],{"href":57129,"rel":67823},[816],". It is a visual design tool that allows you to create a basic grid layout and then copy the code that was used to create it. When you first open the tool you will be presented with a default layout but from here you can easily customize the layout to your needs.",[651,67826,67827],{},[660,67828],{"alt":674,"src":67829},"/images/blog/2019/08/08/css-grid-generator-default-layout.png",[5909,67831,67833],{"id":67832},"css-grid-layout-example","CSS Grid Layout Example",[651,67835,67836],{},"When I am learning something I find the best way to learn is by building something practical with my shiny new toy. In this article, you are going to take a very simple layout and then use the CSS Grid Generator to create the code needed to use in a real project. This is the finished layout with a little bit of styling just so each section is apparent.",[651,67838,67839],{},[660,67840],{"alt":674,"src":67841},"/images/blog/2019/08/08/simple-layout.png",[651,67843,67844],{},"Now that you know what we are aiming for we can start building it out the layout. Start by updating the right hand side to the following.",[5316,67846,67847,67850,67853,67856],{},[5332,67848,67849],{},"Columns: 4",[5332,67851,67852],{},"Rows: 3",[5332,67854,67855],{},"Column Gap: 20",[5332,67857,67858],{},"Row Gap: 20",[651,67860,67861],{},"The gaps allow us to have some margin between our content. I could have just used column gap but I wanted some margin after the header and before the footer so that is what the row gap is doing.",[651,67863,67864],{},[660,67865],{"alt":674,"src":67866},"/images/blog/2019/08/08/css-grid-generator-rows-columns.png",[651,67868,67869],{},"Next you will want to define the different areas of your application. In the CSS Grid Generator you can click and drag to create an area. You will want the header to span the entire grid, the sidebars to take up one cell, the main content area to span 2 columns and the footer to span 4 columns. If you do everything correctly you should end up with something that looks like this.",[651,67871,67872],{},[660,67873],{"alt":674,"src":67874},"/images/blog/2019/08/08/css-grid-generator-areas.png",[651,67876,67877],{},"This is starting to look more like our layout but you still need to define some sizes of different things. You will notice an input box next to each row and column that you can use to set a specific size.",[5316,67879,67880,67883,67886],{},[5332,67881,67882],{},"Header: 100px height",[5332,67884,67885],{},"Sidebars: 200px width",[5332,67887,67888],{},"Footer: 50px height",[651,67890,67891],{},[660,67892],{"alt":674,"src":67893},"/images/blog/2019/08/08/css-grid-generator-sizes.png",[651,67895,67896,67897,67900],{},"This is starting to look more like the layout we were going for but you might be asking what that ",[676,67898,67899],{},"1fr"," unit is.",[1004,67902,67903],{},[651,67904,67905],{},"Tracks can be defined using any length unit. Grid also introduces an additional length unit to help us create flexible grid tracks. The new fr unit represents a fraction of the available space in the grid container.",[651,67907,40060,67908,67910,67911,67914],{},[676,67909,67899],{}," in 2nd row will tell that area to take up the rest of the available space. If you set the container to ",[676,67912,67913],{},"100vh"," you can then have content that takes up the full page and same goes for the columns.",[5909,67916,67918],{"id":67917},"css-grid-generated-code","CSS Grid Generated Code",[651,67920,67921],{},"If you look under the columns and row inputs on the right hand side you will see a button that says \"Please may I have some code\", click it. You should see some generated code that looks something like this. Click the copy button to copy the code and then head over to a text editor.",[669,67923,67925],{"className":66226,"code":67924,"language":66228,"meta":674,"style":674},".parent {\n display: grid;\n grid-template-columns: 200px 1fr 1fr 200px;\n grid-template-rows: 100px 1fr 50px;\n grid-column-gap: 20px;\n grid-row-gap: 20px;\n .div1 {\n grid-area: 1 / 1 / 2 / 5;\n }\n .div2 {\n grid-area: 2 / 1 / 3 / 2;\n }\n .div3 {\n grid-area: 2 / 2 / 3 / 4;\n }\n .div4 {\n grid-area: 2 / 4 / 3 / 5;\n }\n .div5 {\n grid-area: 3 / 1 / 4 / 5;\n }\n}\n",[676,67926,67927,67934,67944,67970,67992,68005,68018,68025,68049,68053,68060,68082,68086,68093,68116,68120,68127,68149,68153,68160,68182,68186],{"__ignoreMap":674},[679,67928,67929,67932],{"class":681,"line":682},[679,67930,67931],{"class":880},".parent",[679,67933,884],{"class":693},[679,67935,67936,67938,67940,67942],{"class":681,"line":790},[679,67937,66577],{"class":931},[679,67939,4282],{"class":693},[679,67941,66582],{"class":931},[679,67943,1186],{"class":693},[679,67945,67946,67948,67950,67953,67955,67957,67959,67961,67963,67966,67968],{"class":681,"line":892},[679,67947,66589],{"class":931},[679,67949,4282],{"class":693},[679,67951,67952],{"class":931},"200",[679,67954,11797],{"class":685},[679,67956,48606],{"class":931},[679,67958,66606],{"class":685},[679,67960,48606],{"class":931},[679,67962,66606],{"class":685},[679,67964,67965],{"class":931}," 200",[679,67967,11797],{"class":685},[679,67969,1186],{"class":693},[679,67971,67972,67975,67977,67979,67981,67983,67985,67988,67990],{"class":681,"line":901},[679,67973,67974],{"class":931}," grid-template-rows",[679,67976,4282],{"class":693},[679,67978,57791],{"class":931},[679,67980,11797],{"class":685},[679,67982,48606],{"class":931},[679,67984,66606],{"class":685},[679,67986,67987],{"class":931}," 50",[679,67989,11797],{"class":685},[679,67991,1186],{"class":693},[679,67993,67994,67997,67999,68001,68003],{"class":681,"line":909},[679,67995,67996],{"class":931}," grid-column-gap",[679,67998,4282],{"class":693},[679,68000,11794],{"class":931},[679,68002,11797],{"class":685},[679,68004,1186],{"class":693},[679,68006,68007,68010,68012,68014,68016],{"class":681,"line":918},[679,68008,68009],{"class":931}," grid-row-gap",[679,68011,4282],{"class":693},[679,68013,11794],{"class":931},[679,68015,11797],{"class":685},[679,68017,1186],{"class":693},[679,68019,68020,68023],{"class":681,"line":935},[679,68021,68022],{"class":880}," .div1",[679,68024,884],{"class":693},[679,68026,68027,68030,68032,68034,68036,68038,68040,68042,68044,68047],{"class":681,"line":944},[679,68028,68029],{"class":931}," grid-area",[679,68031,4282],{"class":693},[679,68033,1557],{"class":931},[679,68035,59327],{"class":685},[679,68037,48606],{"class":931},[679,68039,59327],{"class":685},[679,68041,21871],{"class":931},[679,68043,59327],{"class":685},[679,68045,68046],{"class":931}," 5",[679,68048,1186],{"class":693},[679,68050,68051],{"class":681,"line":959},[679,68052,21405],{"class":693},[679,68054,68055,68058],{"class":681,"line":964},[679,68056,68057],{"class":880}," .div2",[679,68059,884],{"class":693},[679,68061,68062,68064,68066,68068,68070,68072,68074,68076,68078,68080],{"class":681,"line":977},[679,68063,68029],{"class":931},[679,68065,4282],{"class":693},[679,68067,17262],{"class":931},[679,68069,59327],{"class":685},[679,68071,48606],{"class":931},[679,68073,59327],{"class":685},[679,68075,55460],{"class":931},[679,68077,59327],{"class":685},[679,68079,21871],{"class":931},[679,68081,1186],{"class":693},[679,68083,68084],{"class":681,"line":982},[679,68085,21405],{"class":693},[679,68087,68088,68091],{"class":681,"line":988},[679,68089,68090],{"class":880}," .div3",[679,68092,884],{"class":693},[679,68094,68095,68097,68099,68101,68103,68105,68107,68109,68111,68114],{"class":681,"line":993},[679,68096,68029],{"class":931},[679,68098,4282],{"class":693},[679,68100,17262],{"class":931},[679,68102,59327],{"class":685},[679,68104,21871],{"class":931},[679,68106,59327],{"class":685},[679,68108,55460],{"class":931},[679,68110,59327],{"class":685},[679,68112,68113],{"class":931}," 4",[679,68115,1186],{"class":693},[679,68117,68118],{"class":681,"line":2129},[679,68119,21405],{"class":693},[679,68121,68122,68125],{"class":681,"line":2140},[679,68123,68124],{"class":880}," .div4",[679,68126,884],{"class":693},[679,68128,68129,68131,68133,68135,68137,68139,68141,68143,68145,68147],{"class":681,"line":2145},[679,68130,68029],{"class":931},[679,68132,4282],{"class":693},[679,68134,17262],{"class":931},[679,68136,59327],{"class":685},[679,68138,68113],{"class":931},[679,68140,59327],{"class":685},[679,68142,55460],{"class":931},[679,68144,59327],{"class":685},[679,68146,68046],{"class":931},[679,68148,1186],{"class":693},[679,68150,68151],{"class":681,"line":2154},[679,68152,21405],{"class":693},[679,68154,68155,68158],{"class":681,"line":2159},[679,68156,68157],{"class":880}," .div5",[679,68159,884],{"class":693},[679,68161,68162,68164,68166,68168,68170,68172,68174,68176,68178,68180],{"class":681,"line":2164},[679,68163,68029],{"class":931},[679,68165,4282],{"class":693},[679,68167,66599],{"class":931},[679,68169,59327],{"class":685},[679,68171,48606],{"class":931},[679,68173,59327],{"class":685},[679,68175,68113],{"class":931},[679,68177,59327],{"class":685},[679,68179,68046],{"class":931},[679,68181,1186],{"class":693},[679,68183,68184],{"class":681,"line":3134},[679,68185,21405],{"class":693},[679,68187,68188],{"class":681,"line":3139},[679,68189,996],{"class":693},[651,68191,68192,68193,68196],{},"Create a new document ",[676,68194,68195],{},"simple-layout.htm"," and add the following code which will reset the margin and padding for the body.",[669,68198,68200],{"className":4496,"code":68199,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n \u003Chead>\n \u003Cmeta charset=\"UTF-8\" />\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n \u003Ctitle>Simple Layout\u003C/title>\n \u003Cstyle>\n body {\n margin: 0;\n padding: 0;\n }\n \u003C/style>\n \u003C/head>\n \u003Cbody>\u003C/body>\n\u003C/html>\n",[676,68201,68202,68212,68226,68234,68248,68268,68288,68301,68309,68315,68325,68336,68340,68348,68356,68368],{"__ignoreMap":674},[679,68203,68204,68206,68208,68210],{"class":681,"line":682},[679,68205,11904],{"class":693},[679,68207,11907],{"class":4508},[679,68209,11910],{"class":880},[679,68211,4519],{"class":693},[679,68213,68214,68216,68218,68220,68222,68224],{"class":681,"line":790},[679,68215,4505],{"class":693},[679,68217,4498],{"class":4508},[679,68219,18806],{"class":880},[679,68221,686],{"class":693},[679,68223,57251],{"class":689},[679,68225,4519],{"class":693},[679,68227,68228,68230,68232],{"class":681,"line":892},[679,68229,11738],{"class":693},[679,68231,11741],{"class":4508},[679,68233,4519],{"class":693},[679,68235,68236,68238,68240,68242,68244,68246],{"class":681,"line":901},[679,68237,4524],{"class":693},[679,68239,11968],{"class":4508},[679,68241,11971],{"class":880},[679,68243,686],{"class":693},[679,68245,47958],{"class":689},[679,68247,5387],{"class":693},[679,68249,68250,68252,68254,68256,68258,68260,68262,68264,68266],{"class":681,"line":909},[679,68251,4524],{"class":693},[679,68253,11968],{"class":4508},[679,68255,5283],{"class":880},[679,68257,686],{"class":693},[679,68259,12015],{"class":689},[679,68261,11995],{"class":880},[679,68263,686],{"class":693},[679,68265,48042],{"class":689},[679,68267,5387],{"class":693},[679,68269,68270,68272,68274,68276,68278,68280,68282,68284,68286],{"class":681,"line":918},[679,68271,4524],{"class":693},[679,68273,11968],{"class":4508},[679,68275,11987],{"class":880},[679,68277,686],{"class":693},[679,68279,11992],{"class":689},[679,68281,11995],{"class":880},[679,68283,686],{"class":693},[679,68285,57314],{"class":689},[679,68287,5387],{"class":693},[679,68289,68290,68292,68294,68297,68299],{"class":681,"line":935},[679,68291,4524],{"class":693},[679,68293,11750],{"class":4508},[679,68295,68296],{"class":693},">Simple Layout\u003C/",[679,68298,11750],{"class":4508},[679,68300,4519],{"class":693},[679,68302,68303,68305,68307],{"class":681,"line":944},[679,68304,4524],{"class":693},[679,68306,786],{"class":4508},[679,68308,4519],{"class":693},[679,68310,68311,68313],{"class":681,"line":959},[679,68312,11770],{"class":4508},[679,68314,884],{"class":693},[679,68316,68317,68319,68321,68323],{"class":681,"line":964},[679,68318,11789],{"class":931},[679,68320,4282],{"class":693},[679,68322,1060],{"class":931},[679,68324,1186],{"class":693},[679,68326,68327,68330,68332,68334],{"class":681,"line":977},[679,68328,68329],{"class":931}," padding",[679,68331,4282],{"class":693},[679,68333,1060],{"class":931},[679,68335,1186],{"class":693},[679,68337,68338],{"class":681,"line":982},[679,68339,11804],{"class":693},[679,68341,68342,68344,68346],{"class":681,"line":988},[679,68343,4577],{"class":693},[679,68345,786],{"class":4508},[679,68347,4519],{"class":693},[679,68349,68350,68352,68354],{"class":681,"line":993},[679,68351,11840],{"class":693},[679,68353,11741],{"class":4508},[679,68355,4519],{"class":693},[679,68357,68358,68360,68362,68364,68366],{"class":681,"line":2129},[679,68359,11738],{"class":693},[679,68361,3006],{"class":4508},[679,68363,4563],{"class":693},[679,68365,3006],{"class":4508},[679,68367,4519],{"class":693},[679,68369,68370,68372,68374],{"class":681,"line":2140},[679,68371,4586],{"class":693},[679,68373,4498],{"class":4508},[679,68375,4519],{"class":693},[651,68377,68378],{},"Next add in the CSS (the copied code was in SASS but you can pull the divs out)",[669,68380,68382],{"className":4496,"code":68381,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n \u003Chead>\n \u003Cmeta charset=\"UTF-8\" />\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n \u003Ctitle>Simple Layout\u003C/title>\n \u003Cstyle>\n body {\n margin: 0;\n padding: 0;\n }\n .parent {\n display: grid;\n grid-template-columns: 200px 1fr 1fr 200px;\n grid-template-rows: 100px 1fr 50px;\n grid-column-gap: 20px;\n grid-row-gap: 20px;\n height: 100vh;\n }\n\n .div1 {\n grid-area: 1 / 1 / 2 / 5;\n }\n\n .div2 {\n grid-area: 2 / 1 / 3 / 2;\n }\n\n .div3 {\n grid-area: 2 / 2 / 3 / 4;\n }\n\n .div4 {\n grid-area: 2 / 4 / 3 / 5;\n }\n\n .div5 {\n grid-area: 3 / 1 / 4 / 5;\n }\n \u003C/style>\n \u003C/head>\n \u003Cbody>\u003C/body>\n\u003C/html>\n",[676,68383,68384,68394,68408,68416,68430,68450,68470,68482,68490,68496,68506,68516,68520,68527,68538,68563,68584,68597,68610,68624,68628,68632,68639,68662,68666,68670,68677,68699,68703,68707,68714,68736,68740,68744,68751,68773,68777,68781,68788,68810,68814,68822,68830,68842],{"__ignoreMap":674},[679,68385,68386,68388,68390,68392],{"class":681,"line":682},[679,68387,11904],{"class":693},[679,68389,11907],{"class":4508},[679,68391,11910],{"class":880},[679,68393,4519],{"class":693},[679,68395,68396,68398,68400,68402,68404,68406],{"class":681,"line":790},[679,68397,4505],{"class":693},[679,68399,4498],{"class":4508},[679,68401,18806],{"class":880},[679,68403,686],{"class":693},[679,68405,57251],{"class":689},[679,68407,4519],{"class":693},[679,68409,68410,68412,68414],{"class":681,"line":892},[679,68411,11738],{"class":693},[679,68413,11741],{"class":4508},[679,68415,4519],{"class":693},[679,68417,68418,68420,68422,68424,68426,68428],{"class":681,"line":901},[679,68419,4524],{"class":693},[679,68421,11968],{"class":4508},[679,68423,11971],{"class":880},[679,68425,686],{"class":693},[679,68427,47958],{"class":689},[679,68429,5387],{"class":693},[679,68431,68432,68434,68436,68438,68440,68442,68444,68446,68448],{"class":681,"line":909},[679,68433,4524],{"class":693},[679,68435,11968],{"class":4508},[679,68437,5283],{"class":880},[679,68439,686],{"class":693},[679,68441,12015],{"class":689},[679,68443,11995],{"class":880},[679,68445,686],{"class":693},[679,68447,48042],{"class":689},[679,68449,5387],{"class":693},[679,68451,68452,68454,68456,68458,68460,68462,68464,68466,68468],{"class":681,"line":918},[679,68453,4524],{"class":693},[679,68455,11968],{"class":4508},[679,68457,11987],{"class":880},[679,68459,686],{"class":693},[679,68461,11992],{"class":689},[679,68463,11995],{"class":880},[679,68465,686],{"class":693},[679,68467,57314],{"class":689},[679,68469,5387],{"class":693},[679,68471,68472,68474,68476,68478,68480],{"class":681,"line":935},[679,68473,4524],{"class":693},[679,68475,11750],{"class":4508},[679,68477,68296],{"class":693},[679,68479,11750],{"class":4508},[679,68481,4519],{"class":693},[679,68483,68484,68486,68488],{"class":681,"line":944},[679,68485,4524],{"class":693},[679,68487,786],{"class":4508},[679,68489,4519],{"class":693},[679,68491,68492,68494],{"class":681,"line":959},[679,68493,11770],{"class":4508},[679,68495,884],{"class":693},[679,68497,68498,68500,68502,68504],{"class":681,"line":964},[679,68499,11789],{"class":931},[679,68501,4282],{"class":693},[679,68503,1060],{"class":931},[679,68505,1186],{"class":693},[679,68507,68508,68510,68512,68514],{"class":681,"line":977},[679,68509,68329],{"class":931},[679,68511,4282],{"class":693},[679,68513,1060],{"class":931},[679,68515,1186],{"class":693},[679,68517,68518],{"class":681,"line":982},[679,68519,11804],{"class":693},[679,68521,68522,68525],{"class":681,"line":988},[679,68523,68524],{"class":880}," .parent",[679,68526,884],{"class":693},[679,68528,68529,68532,68534,68536],{"class":681,"line":993},[679,68530,68531],{"class":931}," display",[679,68533,4282],{"class":693},[679,68535,66582],{"class":931},[679,68537,1186],{"class":693},[679,68539,68540,68543,68545,68547,68549,68551,68553,68555,68557,68559,68561],{"class":681,"line":2129},[679,68541,68542],{"class":931}," grid-template-columns",[679,68544,4282],{"class":693},[679,68546,67952],{"class":931},[679,68548,11797],{"class":685},[679,68550,48606],{"class":931},[679,68552,66606],{"class":685},[679,68554,48606],{"class":931},[679,68556,66606],{"class":685},[679,68558,67965],{"class":931},[679,68560,11797],{"class":685},[679,68562,1186],{"class":693},[679,68564,68565,68568,68570,68572,68574,68576,68578,68580,68582],{"class":681,"line":2140},[679,68566,68567],{"class":931}," grid-template-rows",[679,68569,4282],{"class":693},[679,68571,57791],{"class":931},[679,68573,11797],{"class":685},[679,68575,48606],{"class":931},[679,68577,66606],{"class":685},[679,68579,67987],{"class":931},[679,68581,11797],{"class":685},[679,68583,1186],{"class":693},[679,68585,68586,68589,68591,68593,68595],{"class":681,"line":2145},[679,68587,68588],{"class":931}," grid-column-gap",[679,68590,4282],{"class":693},[679,68592,11794],{"class":931},[679,68594,11797],{"class":685},[679,68596,1186],{"class":693},[679,68598,68599,68602,68604,68606,68608],{"class":681,"line":2154},[679,68600,68601],{"class":931}," grid-row-gap",[679,68603,4282],{"class":693},[679,68605,11794],{"class":931},[679,68607,11797],{"class":685},[679,68609,1186],{"class":693},[679,68611,68612,68615,68617,68619,68622],{"class":681,"line":2159},[679,68613,68614],{"class":931}," height",[679,68616,4282],{"class":693},[679,68618,57791],{"class":931},[679,68620,68621],{"class":685},"vh",[679,68623,1186],{"class":693},[679,68625,68626],{"class":681,"line":2164},[679,68627,11804],{"class":693},[679,68629,68630],{"class":681,"line":3134},[679,68631,889],{"emptyLinePlaceholder":797},[679,68633,68634,68637],{"class":681,"line":3139},[679,68635,68636],{"class":880}," .div1",[679,68638,884],{"class":693},[679,68640,68641,68644,68646,68648,68650,68652,68654,68656,68658,68660],{"class":681,"line":3144},[679,68642,68643],{"class":931}," grid-area",[679,68645,4282],{"class":693},[679,68647,1557],{"class":931},[679,68649,40796],{"class":693},[679,68651,1557],{"class":931},[679,68653,40796],{"class":693},[679,68655,17262],{"class":931},[679,68657,40796],{"class":693},[679,68659,66775],{"class":931},[679,68661,1186],{"class":693},[679,68663,68664],{"class":681,"line":3149},[679,68665,11804],{"class":693},[679,68667,68668],{"class":681,"line":3169},[679,68669,889],{"emptyLinePlaceholder":797},[679,68671,68672,68675],{"class":681,"line":3185},[679,68673,68674],{"class":880}," .div2",[679,68676,884],{"class":693},[679,68678,68679,68681,68683,68685,68687,68689,68691,68693,68695,68697],{"class":681,"line":3194},[679,68680,68643],{"class":931},[679,68682,4282],{"class":693},[679,68684,17262],{"class":931},[679,68686,40796],{"class":693},[679,68688,1557],{"class":931},[679,68690,40796],{"class":693},[679,68692,66599],{"class":931},[679,68694,40796],{"class":693},[679,68696,17262],{"class":931},[679,68698,1186],{"class":693},[679,68700,68701],{"class":681,"line":3199},[679,68702,11804],{"class":693},[679,68704,68705],{"class":681,"line":3212},[679,68706,889],{"emptyLinePlaceholder":797},[679,68708,68709,68712],{"class":681,"line":3217},[679,68710,68711],{"class":880}," .div3",[679,68713,884],{"class":693},[679,68715,68716,68718,68720,68722,68724,68726,68728,68730,68732,68734],{"class":681,"line":3222},[679,68717,68643],{"class":931},[679,68719,4282],{"class":693},[679,68721,17262],{"class":931},[679,68723,40796],{"class":693},[679,68725,17262],{"class":931},[679,68727,40796],{"class":693},[679,68729,66599],{"class":931},[679,68731,40796],{"class":693},[679,68733,49776],{"class":931},[679,68735,1186],{"class":693},[679,68737,68738],{"class":681,"line":3227},[679,68739,11804],{"class":693},[679,68741,68742],{"class":681,"line":3232},[679,68743,889],{"emptyLinePlaceholder":797},[679,68745,68746,68749],{"class":681,"line":3499},[679,68747,68748],{"class":880}," .div4",[679,68750,884],{"class":693},[679,68752,68753,68755,68757,68759,68761,68763,68765,68767,68769,68771],{"class":681,"line":3509},[679,68754,68643],{"class":931},[679,68756,4282],{"class":693},[679,68758,17262],{"class":931},[679,68760,40796],{"class":693},[679,68762,49776],{"class":931},[679,68764,40796],{"class":693},[679,68766,66599],{"class":931},[679,68768,40796],{"class":693},[679,68770,66775],{"class":931},[679,68772,1186],{"class":693},[679,68774,68775],{"class":681,"line":3516},[679,68776,11804],{"class":693},[679,68778,68779],{"class":681,"line":3531},[679,68780,889],{"emptyLinePlaceholder":797},[679,68782,68783,68786],{"class":681,"line":3536},[679,68784,68785],{"class":880}," .div5",[679,68787,884],{"class":693},[679,68789,68790,68792,68794,68796,68798,68800,68802,68804,68806,68808],{"class":681,"line":3541},[679,68791,68643],{"class":931},[679,68793,4282],{"class":693},[679,68795,66599],{"class":931},[679,68797,40796],{"class":693},[679,68799,1557],{"class":931},[679,68801,40796],{"class":693},[679,68803,49776],{"class":931},[679,68805,40796],{"class":693},[679,68807,66775],{"class":931},[679,68809,1186],{"class":693},[679,68811,68812],{"class":681,"line":3546},[679,68813,11804],{"class":693},[679,68815,68816,68818,68820],{"class":681,"line":3551},[679,68817,4577],{"class":693},[679,68819,786],{"class":4508},[679,68821,4519],{"class":693},[679,68823,68824,68826,68828],{"class":681,"line":3557},[679,68825,11840],{"class":693},[679,68827,11741],{"class":4508},[679,68829,4519],{"class":693},[679,68831,68832,68834,68836,68838,68840],{"class":681,"line":3567},[679,68833,11738],{"class":693},[679,68835,3006],{"class":4508},[679,68837,4563],{"class":693},[679,68839,3006],{"class":4508},[679,68841,4519],{"class":693},[679,68843,68844,68846,68848],{"class":681,"line":3574},[679,68845,4586],{"class":693},[679,68847,4498],{"class":4508},[679,68849,4519],{"class":693},[651,68851,68852,68853,68855,68856,68859],{},"What the tool gave you was the css needed to create this layout. You will need to add markup needed which by looking at the CSS is just a ",[676,68854,67931],{}," with 5 ",[676,68857,68858],{},".div"," nested. I am adding the text just so you can see each section.",[669,68861,68863],{"className":4496,"code":68862,"language":4498,"meta":674,"style":674},"\u003Cbody>\n \u003Cdiv class=\"parent\">\n \u003Cdiv class=\"div1\">\n Header\n \u003C/div>\n \u003Cdiv class=\"div2\">\n Left Sidebar\n \u003C/div>\n \u003Cdiv class=\"div3\">\n Main Content\n \u003C/div>\n \u003Cdiv class=\"div4\">\n Right Sidebar\n \u003C/div>\n \u003Cdiv class=\"div5\">\n Footer\n \u003C/div>\n \u003C/div>\n\u003C/body>\n",[676,68864,68865,68873,68888,68903,68908,68916,68931,68936,68944,68959,68964,68972,68987,68992,69000,69015,69020,69028,69036],{"__ignoreMap":674},[679,68866,68867,68869,68871],{"class":681,"line":682},[679,68868,4505],{"class":693},[679,68870,3006],{"class":4508},[679,68872,4519],{"class":693},[679,68874,68875,68877,68879,68881,68883,68886],{"class":681,"line":790},[679,68876,11738],{"class":693},[679,68878,4509],{"class":4508},[679,68880,4512],{"class":880},[679,68882,686],{"class":693},[679,68884,68885],{"class":689},"\"parent\"",[679,68887,4519],{"class":693},[679,68889,68890,68892,68894,68896,68898,68901],{"class":681,"line":892},[679,68891,4524],{"class":693},[679,68893,4509],{"class":4508},[679,68895,4512],{"class":880},[679,68897,686],{"class":693},[679,68899,68900],{"class":689},"\"div1\"",[679,68902,4519],{"class":693},[679,68904,68905],{"class":681,"line":901},[679,68906,68907],{"class":693}," Header\n",[679,68909,68910,68912,68914],{"class":681,"line":909},[679,68911,4577],{"class":693},[679,68913,4509],{"class":4508},[679,68915,4519],{"class":693},[679,68917,68918,68920,68922,68924,68926,68929],{"class":681,"line":918},[679,68919,4524],{"class":693},[679,68921,4509],{"class":4508},[679,68923,4512],{"class":880},[679,68925,686],{"class":693},[679,68927,68928],{"class":689},"\"div2\"",[679,68930,4519],{"class":693},[679,68932,68933],{"class":681,"line":935},[679,68934,68935],{"class":693}," Left Sidebar\n",[679,68937,68938,68940,68942],{"class":681,"line":944},[679,68939,4577],{"class":693},[679,68941,4509],{"class":4508},[679,68943,4519],{"class":693},[679,68945,68946,68948,68950,68952,68954,68957],{"class":681,"line":959},[679,68947,4524],{"class":693},[679,68949,4509],{"class":4508},[679,68951,4512],{"class":880},[679,68953,686],{"class":693},[679,68955,68956],{"class":689},"\"div3\"",[679,68958,4519],{"class":693},[679,68960,68961],{"class":681,"line":964},[679,68962,68963],{"class":693}," Main Content\n",[679,68965,68966,68968,68970],{"class":681,"line":977},[679,68967,4577],{"class":693},[679,68969,4509],{"class":4508},[679,68971,4519],{"class":693},[679,68973,68974,68976,68978,68980,68982,68985],{"class":681,"line":982},[679,68975,4524],{"class":693},[679,68977,4509],{"class":4508},[679,68979,4512],{"class":880},[679,68981,686],{"class":693},[679,68983,68984],{"class":689},"\"div4\"",[679,68986,4519],{"class":693},[679,68988,68989],{"class":681,"line":988},[679,68990,68991],{"class":693}," Right Sidebar\n",[679,68993,68994,68996,68998],{"class":681,"line":993},[679,68995,4577],{"class":693},[679,68997,4509],{"class":4508},[679,68999,4519],{"class":693},[679,69001,69002,69004,69006,69008,69010,69013],{"class":681,"line":2129},[679,69003,4524],{"class":693},[679,69005,4509],{"class":4508},[679,69007,4512],{"class":880},[679,69009,686],{"class":693},[679,69011,69012],{"class":689},"\"div5\"",[679,69014,4519],{"class":693},[679,69016,69017],{"class":681,"line":2140},[679,69018,69019],{"class":693}," Footer\n",[679,69021,69022,69024,69026],{"class":681,"line":2145},[679,69023,4577],{"class":693},[679,69025,4509],{"class":4508},[679,69027,4519],{"class":693},[679,69029,69030,69032,69034],{"class":681,"line":2154},[679,69031,11840],{"class":693},[679,69033,4509],{"class":4508},[679,69035,4519],{"class":693},[679,69037,69038,69040,69042],{"class":681,"line":2159},[679,69039,4586],{"class":693},[679,69041,3006],{"class":4508},[679,69043,4519],{"class":693},[651,69045,69046,69047,69050,69051],{},"Finally add the follow CSS that will just add some padding a background color for ",[676,69048,69049],{},".div1"," - ",[676,69052,69053],{},".div5",[669,69055,69057],{"className":66165,"code":69056,"language":66167,"meta":674,"style":674},"div:not(.parent) {\n padding: 10px;\n background-color: rgb(199, 199, 199);\n}\n",[676,69058,69059,69072,69085,69108],{"__ignoreMap":674},[679,69060,69061,69063,69066,69068,69070],{"class":681,"line":682},[679,69062,4509],{"class":4508},[679,69064,69065],{"class":880},":not",[679,69067,745],{"class":693},[679,69069,67931],{"class":880},[679,69071,4390],{"class":693},[679,69073,69074,69077,69079,69081,69083],{"class":681,"line":790},[679,69075,69076],{"class":931}," padding",[679,69078,4282],{"class":693},[679,69080,1205],{"class":931},[679,69082,11797],{"class":685},[679,69084,1186],{"class":693},[679,69086,69087,69090,69092,69094,69096,69098,69100,69102,69104,69106],{"class":681,"line":892},[679,69088,69089],{"class":931}," background-color",[679,69091,4282],{"class":693},[679,69093,49714],{"class":931},[679,69095,745],{"class":693},[679,69097,51628],{"class":931},[679,69099,2797],{"class":693},[679,69101,51628],{"class":931},[679,69103,2797],{"class":693},[679,69105,51628],{"class":931},[679,69107,1208],{"class":693},[679,69109,69110],{"class":681,"line":901},[679,69111,996],{"class":693},[651,69113,69114],{},"If you were to run this you would end up with the following.",[651,69116,69117],{},[660,69118],{"alt":674,"src":69119},"/images/blog/2019/08/08/simple-layout-no-height.png",[651,69121,69122,69123,69126,69127,4512],{},"This looks pretty good but you want this to take up the entire browser window. An easy way to fix this is to add ",[676,69124,69125],{},"height: 100vh"," to the ",[676,69128,67931],{},[669,69130,69132],{"className":66165,"code":69131,"language":66167,"meta":674,"style":674},".parent {\n display: grid;\n grid-template-columns: 200px 1fr 1fr 200px;\n grid-template-rows: 100px 1fr 50px;\n grid-column-gap: 20px;\n grid-row-gap: 20px;\n height: 100vh;\n}\n",[676,69133,69134,69140,69150,69174,69194,69206,69218,69230],{"__ignoreMap":674},[679,69135,69136,69138],{"class":681,"line":682},[679,69137,67931],{"class":880},[679,69139,884],{"class":693},[679,69141,69142,69144,69146,69148],{"class":681,"line":790},[679,69143,66577],{"class":931},[679,69145,4282],{"class":693},[679,69147,66582],{"class":931},[679,69149,1186],{"class":693},[679,69151,69152,69154,69156,69158,69160,69162,69164,69166,69168,69170,69172],{"class":681,"line":892},[679,69153,66589],{"class":931},[679,69155,4282],{"class":693},[679,69157,67952],{"class":931},[679,69159,11797],{"class":685},[679,69161,48606],{"class":931},[679,69163,66606],{"class":685},[679,69165,48606],{"class":931},[679,69167,66606],{"class":685},[679,69169,67965],{"class":931},[679,69171,11797],{"class":685},[679,69173,1186],{"class":693},[679,69175,69176,69178,69180,69182,69184,69186,69188,69190,69192],{"class":681,"line":901},[679,69177,67974],{"class":931},[679,69179,4282],{"class":693},[679,69181,57791],{"class":931},[679,69183,11797],{"class":685},[679,69185,48606],{"class":931},[679,69187,66606],{"class":685},[679,69189,67987],{"class":931},[679,69191,11797],{"class":685},[679,69193,1186],{"class":693},[679,69195,69196,69198,69200,69202,69204],{"class":681,"line":909},[679,69197,67996],{"class":931},[679,69199,4282],{"class":693},[679,69201,11794],{"class":931},[679,69203,11797],{"class":685},[679,69205,1186],{"class":693},[679,69207,69208,69210,69212,69214,69216],{"class":681,"line":918},[679,69209,68009],{"class":931},[679,69211,4282],{"class":693},[679,69213,11794],{"class":931},[679,69215,11797],{"class":685},[679,69217,1186],{"class":693},[679,69219,69220,69222,69224,69226,69228],{"class":681,"line":935},[679,69221,66899],{"class":931},[679,69223,4282],{"class":693},[679,69225,57791],{"class":931},[679,69227,68621],{"class":685},[679,69229,1186],{"class":693},[679,69231,69232],{"class":681,"line":944},[679,69233,996],{"class":693},[651,69235,69236],{},"With that, you have the layout you were looking for!",[651,69238,69239],{},[660,69240],{"alt":674,"src":67841},[651,69242,69243],{},"This tool is meant to help you create a basic layout in CSS Grid really fast. From here it will be up to you to go out and learn more about CSS Grid so that you can start customizing and creating advanced layouts.",[4542,69245,9042],{"id":9041},[651,69247,69248,69249,69252,69253,69256],{},"The user interface of CSS Grid Generator is beautiful, clean and easy to use. I ",[2939,69250,69251],{},"love"," that the entire project was written in Vue and hosted on ",[812,69254,43833],{"href":43831,"rel":69255},[816],", 2 of my favorite technologies.",[651,69258,69259,69260,69265],{},"If you're ever curious how a project like this is built you can pop open the source code and take a look (Thanks Sarah!). There is a nice SVG animation in the upper right hand side that will take you to the ",[812,69261,69264],{"href":69262,"rel":69263},"https://github.com/sdras/cssgridgenerator",[816],"Github repository",". I hope you found this tool as useful as I have, as always...",[651,69267,41105,69268,41109],{},[41107,69269],{},[786,69271,69272],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":69274},[69275,69279],{"id":67811,"depth":790,"text":67812,"children":69276},[69277,69278],{"id":67832,"depth":892,"text":67833},{"id":67917,"depth":892,"text":67918},{"id":9041,"depth":790,"text":9042},"Learn how to create a CSS Grid Layout with a free tool called CSS Grid Generator",{"slug":69282,"date":69283,"published":797,"author":798,"tags":69284,"cover":69285,"video":69286},"css-grid-generator","2019-08-08T18:13:57.456Z",[66167,44169],"./css-grid-generator-cover.png","https://www.youtube.com/embed/ZopBBEs9TPg",{"title":468,"description":69280},"blog/2019/08/08/css-grid-generator","463HqLbCpXNrl9iZI5YFPHaIoaiKDAiPPWLjRiu9gj4",{"id":69291,"title":465,"body":69292,"description":69340,"extension":793,"meta":69341,"navigation":797,"path":466,"seo":69346,"stem":69347,"__hash__":69348},"content/blog/2019/10/01/unit-testing-vue-part-01.md",{"type":648,"value":69293,"toc":69338},[69294,69303,69306,69317,69323,69326,69332],[651,69295,69296,69297,69302],{},"If you're ",[812,69298,69301],{"href":69299,"rel":69300},"https://www.danvega.dev/signup/",[816],"subscribed to my newsletter"," than you know I have been working on a series of articles for my friends over at Vue Mastery for a little while now. The series is titled \"A beginner's guide: Unit Testing in Vue\" and has been broken into 3 parts. I am really excited to announce that the first article in this series is now live.",[651,69304,69305],{},"In this first post we take a look at why you should test, what you should test and more importantly what not to test. In this article you will learn:",[5316,69307,69308,69311,69314],{},[5332,69309,69310],{},"Why are we writing tests to begin with? What are the goals of testing?",[5332,69312,69313],{},"How to Identify what you should (and should NOT) be testing",[5332,69315,69316],{},"What are the different tools used to write these tests",[651,69318,69319],{},[812,69320,69321],{"href":69321,"rel":69322},"https://www.vuemastery.com/blog/unit-testing-vue-1",[816],[651,69324,69325],{},"A special thanks to Adam Jahr for the help in getting this article published. I also want to thank Vue Mastery and VueJS for these tweets.",[651,69327,69328],{},[812,69329,69330],{"href":69330,"rel":69331},"https://twitter.com/VueMastery/status/1177696674787012609",[816],[651,69333,69334],{},[812,69335,69336],{"href":69336,"rel":69337},"https://twitter.com/vuejs/status/1177701970305527809",[816],{"title":674,"searchDepth":790,"depth":790,"links":69339},[],"Unit Testing in Vue: What to test?",{"slug":69342,"date":69343,"published":797,"author":798,"tags":69344,"cover":69345},"unit-testing-vue-part-01","2019-10-01T17:00:00.000Z",[44169],"unit-testing-vue-mastery-01.png",{"title":465,"description":69340},"blog/2019/10/01/unit-testing-vue-part-01","fXbwj6wdDWeh9UriChzXmOD9MtiEkBk9smHFumgV1TE",{"id":69350,"title":462,"body":69351,"description":69415,"extension":793,"meta":69416,"navigation":797,"path":463,"seo":69421,"stem":69422,"__hash__":69423},"content/blog/2019/10/15/unit-testing-vue-part-02.md",{"type":648,"value":69352,"toc":69413},[69353,69367,69376,69401],[651,69354,69355,69356,69361,69362,69366],{},"If you're interested in getting started with Unit Testing in Vue I have something that is really going to help you out. I have been working on a 3 part series for my friends over at ",[812,69357,69360],{"href":69358,"rel":69359},"https://www.vuemastery.com",[816],"VueMastery"," In ",[812,69363,69365],{"href":69321,"rel":69364},[816],"part 1 of the series"," we talked about what to test and more importantly what not to test.",[651,69368,69369,69370,69375],{},"I am happy to announce that ",[812,69371,69374],{"href":69372,"rel":69373},"https://www.vuemastery.com/blog/Unit-Testing-in-Vue-Your-First-Test",[816],"part 2 of this series"," was released yesterday. Now that you know what to test in your Vue components it's time to write some code. In this article, we dive into writing and running your first tests. In this article you will learn:",[5316,69377,69378,69381,69384,69398],{},[5332,69379,69380],{},"How to add Unit Testing to your project using the Vue CLI",[5332,69382,69383],{},"What are the different libraries you will be using",[5332,69385,69386,69387],{},"Writing your first test\n",[5316,69388,69389,69392,69395],{},[5332,69390,69391],{},"What is Jest?",[5332,69393,69394],{},"Understanding assertions",[5332,69396,69397],{},"Introduction to Vue Test Utils",[5332,69399,69400],{},"Running your first test",[651,69402,69403,69404,69407,69408,69412],{},"I hope you enjoy this article and if you do please let me know on ",[812,69405,51474],{"href":51472,"rel":69406},[816]," and let the team over at ",[812,69409,69360],{"href":69410,"rel":69411},"https://twitter.com/VueMastery",[816]," know that you want me to keep writing for them 😉",{"title":674,"searchDepth":790,"depth":790,"links":69414},[],"In the 2nd part of this 3 part series we will look at writing and running your first unit test in Vue",{"slug":69417,"date":69418,"published":797,"author":798,"tags":69419,"cover":69420},"unit-testing-vue-part-02","2019-10-15T12:33:53.592Z",[44169],"./unit-testing-part-02-cover.jpg",{"title":462,"description":69415},"blog/2019/10/15/unit-testing-vue-part-02","blrb8_XcisIVKXyywS_nciZ-NEvdTDgxuxVb0W0xT0U",{"id":69425,"title":459,"body":69426,"description":69467,"extension":793,"meta":69468,"navigation":797,"path":460,"seo":69473,"stem":69474,"__hash__":69475},"content/blog/2019/10/29/unit-testing-vue-part-03.md",{"type":648,"value":69427,"toc":69465},[69428,69431,69452,69455,69458,69461],[651,69429,69430],{},"This is the third and final article in the series I did for Vue Mastery. You can read all three of these articles over on Vue Mastery:",[5316,69432,69433,69439,69445],{},[5332,69434,69435],{},[812,69436,69438],{"href":69321,"rel":69437},[816],"Unit Testing in Vue: What to test",[5332,69440,69441],{},[812,69442,69444],{"href":69372,"rel":69443},[816],"Unit Testing in Vue: Writing your first Test",[5332,69446,69447],{},[812,69448,69451],{"href":69449,"rel":69450},"https://www.vuemastery.com/blog/Unit-Testing-in%20Vue-More-complex-components",[816],"Unit Testing in Vue: Testing More Complex Components",[651,69453,69454],{},"In part one of the series we really focused on identifying what to test and more importantly what NOT to test in your components. From there we wrote our first test in part two and spent some time understanding the different libraries involved. In part three we learn through repetition by writing more unit tests with a couple of components that add some new wrinkles.",[651,69456,69457],{},"I want to thank Adam and Gregg over at Vue Mastery for giving me this opportunity to write for them. I have always looked up to them for their unique style of teaching and everything they do for the Vue community. I don't think this is the last time you will see me collaborating with them and I am looking forward to what we do next.",[651,69459,69460],{},"I put a lot of hard work into this series and if you enjoyed it please let them know. I hope you're having a wonderful day and as always friends....",[651,69462,41105,69463,41109],{},[41107,69464],{},{"title":674,"searchDepth":790,"depth":790,"links":69466},[],"In Part 3 of this series we learn how to write unit tests in Vue by testing more complex components.",{"slug":69469,"date":69470,"published":797,"author":798,"tags":69471,"cover":69472},"unit-testing-vue-part-03","2019-10-29T15:30:00.000Z",[44169],"./unit-testing-vue-mastery-03-cover.jpg",{"title":459,"description":69467},"blog/2019/10/29/unit-testing-vue-part-03","aEVIxUgV_Rf3ggv2_xfX9GdqsJzoD2Tq5zMucpRTkiY",{"id":69477,"title":456,"body":69478,"description":69544,"extension":793,"meta":69545,"navigation":797,"path":457,"seo":69550,"stem":69551,"__hash__":69552},"content/blog/2019/11/21/spring-boot-visual-studio-code.md",{"type":648,"value":69479,"toc":69539},[69480,69483,69486,69489,69492,69496,69499,69502,69518,69522,69525,69530,69532,69535],[651,69481,69482],{},"I feel very lucky that I get to work with so many different programming languages & frameworks on a day to day basis. This wouldn't be as much fun if I had to bounce around using different IDE's, editors and tools.",[651,69484,69485],{},"Thankfully we have a tool like Visual Studio Code that has support for so many languages, frameworks, and a really great extension ecosystem. It is because of this I can switch between languages like Java, C# and Go or Frameworks like Spring Boot, .NET and Java and still be productive.",[651,69487,69488],{},"I mentioned to someone that I was creating Spring Boot Applications in Visual Studio Code and the reaction was \"You can do that?\". With that, I decided to put together a tutorial on how to create a Spring Boot Application in Visual Studio Code.",[651,69490,69491],{},"Assuming you already have VS Code installed, you're probably wondering \"What extensions do I need to install?\"",[4542,69493,69495],{"id":69494},"visual-studio-code-extension-packs","Visual Studio Code Extension Packs",[651,69497,69498],{},"I don't know if a lot of people know about this, but there are some really good extensions for Visual Studio Code if you're a Java developer.",[651,69500,69501],{},"The problem with getting up and running with a new language or framework is that there can be a lot of extensions that you need to install. Fortunately for us, there is a feature in VS Code called extension packs. This allows a single extension, the extension pack, to group multiple extensions into one. Instead of asking you to go out and install 10 extensions, you just need to install these two.",[5316,69503,69504,69511],{},[5332,69505,69506],{},[812,69507,69510],{"href":69508,"rel":69509},"https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack",[816],"Java Extension Pack",[5332,69512,69513],{},[812,69514,69517],{"href":69515,"rel":69516},"https://marketplace.visualstudio.com/items?itemName=Pivotal.vscode-boot-dev-pack",[816],"Spring Boot Extension Pack",[4542,69519,69521],{"id":69520},"spring-boot-in-visual-studio-code-tutorial","Spring Boot in Visual Studio Code Tutorial",[651,69523,69524],{},"With Visual Studio Code and the proper extensions installed you are ready to create a new Spring Boot Application. In this tutorial, you will create a Spring Boot REST API and then a simple Vue application that talks to it.",[59634,69526],{"width":69527,"src":69528,"frameBorder":1060,"allow":69529,"allowFullScreen":797},"100%","https://www.youtube.com/embed/5mpHejytgFE","accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",[4542,69531,9042],{"id":9041},[651,69533,69534],{},"I hope you found value in this tutorial. Are you creating Spring Boot Applications in Visual Studio Code? If you are and have any tips or tricks for my readers please leave them below. As always friends...",[651,69536,41105,69537,41109],{},[41107,69538],{},{"title":674,"searchDepth":790,"depth":790,"links":69540},[69541,69542,69543],{"id":69494,"depth":790,"text":69495},{"id":69520,"depth":790,"text":69521},{"id":9041,"depth":790,"text":9042},"In this tutorial I will show you how to create a new Spring Boot application in Visual Studio Code",{"slug":69546,"date":69547,"published":797,"author":798,"tags":69548,"cover":69549},"spring-boot-visual-studio-code","2019-11-21T11:28:07.266Z",[7077,4109],"./spring-boot-in-vscode.png",{"title":456,"description":69544},"blog/2019/11/21/spring-boot-visual-studio-code","OQrNmS0xbONLiVkfEe8APAMqNm4Rp9Dolp0unz5hzUI",{"id":69554,"title":453,"body":69555,"description":69736,"extension":793,"meta":69737,"navigation":797,"path":454,"seo":69742,"stem":69743,"__hash__":69744},"content/blog/2019/12/19/up-and-running-with-vuejs.md",{"type":648,"value":69556,"toc":69730},[69557,69571,69575,69580,69583,69586,69590,69595,69598,69601,69606,69609,69614,69617,69622,69625,69629,69634,69637,69640,69719,69721,69724],[651,69558,69559,69560,69565,69566,69570],{},"This is a course that I have wanted to make for a long time now and I am happy to announce that ",[812,69561,69564],{"href":69562,"rel":69563},"https://www.udemy.com/course/vue-intro/?couponCode=4F5DE476322755E7AFBP",[816],"it is finally here",". If you follow me at all you know that I have been a big fan of ",[812,69567,69569],{"href":43587,"rel":69568},[816],"Vue.JS"," for awhile now. I use it personally on this blog, we teach it at Tech Elevator and now I have my own course that teaches the very basics of Vue.",[4542,69572,69574],{"id":69573},"course-motivation","Course Motivation",[651,69576,69577],{},[660,69578],{"alt":69574,"src":69579},"/images/blog/2019/12/19/mia-baker-CuoMduHwRZY-unsplash.jpg",[651,69581,69582],{},"For most of my career I have primarily been a back-end developer. I have always still had to work on projects where I would be doing front-end development but I can honestly say I never liked it. It was always something I wanted to get better at but the tools available to me weren't that great.",[651,69584,69585],{},"A little less than two years ago I found Vue and it was just so easy to get started with. About the same time CSS started making some advancements with Flexbox and CSS Grid. JavaScript and the Browser API support have also made working on the client a lot more enjoyable. When you combine all of that with just how awesome some of these front-end frameworks like Vue are, it's a great time to be a front-end developer.",[4542,69587,69589],{"id":69588},"course-goals","Course Goals",[651,69591,69592],{},[660,69593],{"alt":69589,"src":69594},"/images/blog/2019/12/19/s-o-c-i-a-l-c-u-t-6iYb1BWWbV0-unsplash.jpg",[651,69596,69597],{},"When I started looking at the current landscape of courses in the Vue community I noticed a couple of things. First, there are some really great courses out there by some amazing instructors. I also noticed that a lot of the introduction material came bundled with hours of additional content that might not be relevant for a beginner. It also could be pretty intimidating to want to learn something new and find a course and that is 25 hours long.",[651,69599,69600],{},"With that, I set out to create a course that taught the basics of Vue to beginners. I had a few goals in mind when I started designing the curriculum for this course.",[651,69602,69603],{},[2939,69604,69605],{},"#1 Keep the course length short",[651,69607,69608],{},"Like Vue, I wanted this course to be approachable and I felt like 1-3 hours was a good target. This course comes with 2 hours of video content and its packed with tons of extras.",[651,69610,69611],{},[2939,69612,69613],{},"#2 Backup lessons with quizzes, exercises, and coding challenges",[651,69615,69616],{},"If you're going to learn how to code you can't just watch someone else do it. That is why I really wanted to make sure that this course included quizzes, exercises, and coding challenges. The coding challenges ask you to build something with what you just learned in the lesson.",[651,69618,69619],{},[2939,69620,69621],{},"#3 Keep it Simple",[651,69623,69624],{},"I wanted to target developers with HTML/CSS/JS experience. This meant that I wanted you to just be able to drop a script tag on a page and go. No prior knowledge of Node or NPM would be needed and we would stay away from installing any command-line interfaces.",[4542,69626,69628],{"id":69627},"course-curriculum","Course Curriculum",[651,69630,69631],{},[660,69632],{"alt":69628,"src":69633},"/images/blog/2019/12/19/tim-mossholder-WE_Kv_ZB1l0-unsplash.jpg",[651,69635,69636],{},"This course is really geared towards beginners who know HTML, CSS & JavaScript but maybe haven't jumped into a framework just yet. If that sounds like you I think Vue is the perfect framework to learn. Below is a trailer for the course and an outline of the curriculum.",[59634,69638],{"width":69527,"src":69639,"frameBorder":1060,"allow":69529,"allowFullScreen":797},"https://www.youtube.com/embed/CtQGbnNxhHk",[5316,69641,69642,69669,69705],{},[5332,69643,38779,69644],{},[5316,69645,69646,69649,69652,69655,69658,69666],{},[5332,69647,69648],{},"Welcome",[5332,69650,69651],{},"What is Vue",[5332,69653,69654],{},"Why use Vue",[5332,69656,69657],{},"Moving from jQuery to Vue",[5332,69659,69660,69661],{},"Hello, World!\n",[5316,69662,69663],{},[5332,69664,69665],{},"Coding Exercise",[5332,69667,69668],{},"Coding Challenge",[5332,69670,69671,69672],{},"Vue Core Concepts\n",[5316,69673,69674,69676,69679,69682,69690,69693,69696,69699,69702],{},[5332,69675,25857],{},[5332,69677,69678],{},"Data Binding: Expressions & Directives",[5332,69680,69681],{},"Lists",[5332,69683,69684,69685],{},"Counter Application\n",[5316,69686,69687],{},[5332,69688,69689],{},"Counter Application Coding Challenge",[5332,69691,69692],{},"Two Way Data Binding",[5332,69694,69695],{},"Timer Component",[5332,69697,69698],{},"Creating Vue Components",[5332,69700,69701],{},"Core Concepts Quiz",[5332,69703,69704],{},"Final Coding Challenge",[5332,69706,69707,69708],{},"Resource & Next Steps\n",[5316,69709,69710,69713,69716],{},[5332,69711,69712],{},"Thank You",[5332,69714,69715],{},"Resource & Next Steps",[5332,69717,69718],{},"Bonus Lesson",[4542,69720,9042],{"id":9041},[651,69722,69723],{},"I am really happy with the way the course turned out and I hope you find some real value in it. You can use the link below to check out the course landing page. So what are you waiting for, click that enroll button and I will see you inside!",[651,69725,69726],{},[812,69727,69729],{"href":69562,"rel":69728},[816],"Up & Running with Vue.js",{"title":674,"searchDepth":790,"depth":790,"links":69731},[69732,69733,69734,69735],{"id":69573,"depth":790,"text":69574},{"id":69588,"depth":790,"text":69589},{"id":69627,"depth":790,"text":69628},{"id":9041,"depth":790,"text":9042},"I released a new course on how to get up and running with Vue.js for beginners",{"slug":69738,"date":69739,"published":797,"author":798,"tags":69740,"cover":69741},"up-and-running-with-vuejs","2019-12-19T15:30:00.000Z",[44169],"./up-and-running-with-vuejs-cover.png",{"title":453,"description":69736},"blog/2019/12/19/up-and-running-with-vuejs","v4nKVvou9H1HJRgxz2GAm4gPoITYNfWkgfdTVNqolMM",{"id":69746,"title":450,"body":69747,"description":69932,"extension":793,"meta":69933,"navigation":797,"path":451,"seo":69938,"stem":69939,"__hash__":69940},"content/blog/2020/01/09/codemash-2020.md",{"type":648,"value":69748,"toc":69921},[69749,69753,69756,69759,69762,69775,69781,69784,69822,69825,69827,69831,69834,69837,69840,69846,69850,69853,69859,69861,69898,69902,69905,69911,69913,69916],[4542,69750,69752],{"id":69751},"overview","Overview",[651,69754,69755],{},"This was one of the more interesting & challenging presentations that I have had to put together. Usually when I create a presentation I have a wealth of knowledge and examples to fall back on. In the case of Vue 3 still being in alpha I didn't have a lot to go on. This took a lot of tinkering around with the source code which I love to do so I was up for the challenge.",[651,69757,69758],{},"I think I watched a few presentations by Evan You at least a dozen times, so Thank You Evan. I also want to give a huge kudos to the entire Vue Core team for the RFCs. These documents were extremely detailed and I loved the fact that they were explicit with the motivation behind each feature. These were the two main resources I used for both learning the new features of Vue 3 and putting together this presentation.",[651,69760,69761],{},"Going into this talk I had higher hopes of showing off more features than I actually covered. I think this is usually the case though whenever I start thinking about a talk and then realizing I only have an hour. I ended up with 134 slides which is extremely ambitious but I will get through them.",[651,69763,69764,69765,23212,69768,69771,69772,664],{},"After taking a look at all the new features in Vue 3 it became clear to me that there were a couple of goals with this release. First, they wanted to make the framework ",[2939,69766,69767],{},"Smaller",[2939,69769,69770],{},"Faster"," (which they accomplished) and a lot of what I covered aligned with those goals. Second, they wanted to make the framework scalable and for me that is where the Composition API shines and makes Vue ",[2939,69773,69774],{},"Stronger",[651,69776,69777],{},[660,69778],{"alt":69779,"src":69780},"Slide Deck Cover","/images/blog/2020/01/09/presentation-cover.png",[651,69782,69783],{},"This is how I ended up coming up with the title \"Vue 3: Smaller, Faster and Stronger\" for my presentation. These are a list of features I ended up talking about that really fit into those buckets.",[5316,69785,69786,69803,69814],{},[5332,69787,69788,69789],{},"Smaller\n",[5316,69790,69791,69794,69797,69800],{},[5332,69792,69793],{},"TypeScript Rewrite",[5332,69795,69796],{},"Global API Change",[5332,69798,69799],{},"Treeshaking",[5332,69801,69802],{},"Fragments",[5332,69804,69805,69806],{},"Faster\n",[5316,69807,69808,69811],{},[5332,69809,69810],{},"Virtual DOM",[5332,69812,69813],{},"Reactivity System",[5332,69815,69816,69817],{},"Stronger\n",[5316,69818,69819],{},[5332,69820,69821],{},"Composition API",[651,69823,69824],{},"Below are some resources such as slides, a Github repository with demo code and any links I mentioned during the presentation.",[4542,69826,21931],{"id":21930},[5909,69828,69830],{"id":69829},"slides","Slides",[651,69832,69833],{},"For now I have exported my slides to a PDF that you can find in the Github repository below. I might share these out in additional formats later but I am curious to find out how speakers are sharing the keynote presentations so please let me know.",[5909,69835,17458],{"id":69836},"github",[651,69838,69839],{},"This repository contains most of the examples that I showed off during my presentation. There was no live coding so most of this code was just shown on slides.",[651,69841,69842],{},[812,69843,69844],{"href":69844,"rel":69845},"https://github.com/danvega/vue3-smaller-faster-stronger",[816],[5909,69847,69849],{"id":69848},"articles","Articles",[651,69851,69852],{},"I wrote an article for Vue Mastery on some of my favorite resources for learning Vue 3.",[651,69854,69855],{},[812,69856,69857],{"href":69857,"rel":69858},"https://www.vuemastery.com/blog/top-ways-to-learn-Vue-3",[816],[5909,69860,40845],{"id":40844},[5316,69862,69863,69870,69877,69884,69891],{},[5332,69864,69865],{},[812,69866,69869],{"href":69867,"rel":69868},"https://github.com/vuejs/vue-next",[816],"Vue Next",[5332,69871,69872],{},[812,69873,69876],{"href":69874,"rel":69875},"https://github.com/vuejs/vue-next-webpack-preview",[816],"Vue 3 + Webpack Setup",[5332,69878,69879],{},[812,69880,69883],{"href":69881,"rel":69882},"https://github.com/vuejs/rfcs",[816],"Vue Request for Comments (RFCs)",[5332,69885,69886],{},[812,69887,69890],{"href":69888,"rel":69889},"https://vue-composition-api-rfc.netlify.com/",[816],"Composition API RFC",[5332,69892,69893],{},[812,69894,69897],{"href":69895,"rel":69896},"https://vue-composition-api-rfc.netlify.com/api.html",[816],"Composition API Reference",[5909,69899,69901],{"id":69900},"vue-3-course","Vue 3 Course",[651,69903,69904],{},"I am currently building out a curriculum for the upcoming release of Vue 3 . If you're interested in learning about all of the new and exciting features this course is for you. Please use the form on this page and you will receive updates as they become available.",[651,69906,69907],{},[812,69908,69909],{"href":69909,"rel":69910},"https://www.danvega.dev/courses/vue3",[816],[4542,69912,9042],{"id":9041},[651,69914,69915],{},"If you attended this presentation I really hope you enjoyed it. I put a lot of work into this and my hope is that it showed. I am hoping to give this presentation at more conferences and meetups this year so if you're interested in me presenting please reach out to me. As always friends...",[651,69917,41105,69918,69920],{},[41107,69919],{},"\nDan Vega",{"title":674,"searchDepth":790,"depth":790,"links":69922},[69923,69924,69931],{"id":69751,"depth":790,"text":69752},{"id":21930,"depth":790,"text":21931,"children":69925},[69926,69927,69928,69929,69930],{"id":69829,"depth":892,"text":69830},{"id":69836,"depth":892,"text":17458},{"id":69848,"depth":892,"text":69849},{"id":40844,"depth":892,"text":40845},{"id":69900,"depth":892,"text":69901},{"id":9041,"depth":790,"text":9042},"This post is a collection of resources for my presentation at CodeMash 2020.",{"slug":69934,"date":69935,"published":797,"author":798,"tags":69936,"cover":69937},"codemash-2020","2020-01-09T09:00:00.000Z",[44169],"./codemash-vue3.png",{"title":450,"description":69932},"blog/2020/01/09/codemash-2020","gaMKosZBn2Vc7fN-yFEkwo2Gxahvqh9XxltlHAxy3g0",{"id":69942,"title":447,"body":69943,"description":70128,"extension":793,"meta":70129,"navigation":797,"path":448,"seo":70134,"stem":70135,"__hash__":70136},"content/blog/2020/01/13/codemash-2020-recap.md",{"type":648,"value":69944,"toc":70115},[69945,69948,69952,69955,69959,69962,69968,69972,69975,69979,69987,69990,69996,70000,70008,70011,70017,70020,70024,70032,70035,70041,70045,70053,70062,70068,70072,70075,70079,70082,70088,70095,70101,70110,70112],[651,69946,69947],{},"I'm back home after attending and speaking at my first CodeMash and I would like to tell you all about it. The highlights for me were the awesome venue, being able to take my family with me and getting to spend some time with my friend Todd who I haven't seen in awhile.",[4542,69949,69951],{"id":69950},"codemash-newbie","CodeMash Newbie",[651,69953,69954],{},"The first thing I need to say is that I have been to a decent amount of conferences in my life and this was one of the most organized and well run conferences I have ever attended. I just want to give a huge kudos to everyone involved in putting this conference together. The venue was amazing, the sessions were great and I couldn't think of a better way to kick off the new year.",[4542,69956,69958],{"id":69957},"kalahari","Kalahari",[651,69960,69961],{},"I was lucky enough to bring my wife and daughter with me to Kalahari so this conference doubled as a nice little vacation for the family. This was my first trip to Kalahari and we had a blast. The water park had so many things to do for everyone. They also had a great assortment of places to eat and drink. I will definitely be taking the family back there one day.",[651,69963,69964],{},[660,69965],{"alt":69966,"src":69967},"Bella & Dad at Kalahari","/images/blog/2020/01/13/bella_codemash.jpg",[4542,69969,69971],{"id":69970},"sessions-workshops-i-attended","Sessions & Workshops I attended",[651,69973,69974],{},"I attended a lot of sessions and workshops but these are a few that I want to share with you.",[5909,69976,69978],{"id":69977},"java-9-10-11-workshop","Java 9, 10 & 11 Workshop",[651,69980,69981,69982,50653],{},"Presented by: Chris Judd (",[812,69983,69986],{"href":69984,"rel":69985},"https://twitter.com/javajudd",[816],"@javajudd",[651,69988,69989],{},"This was a really great workshop that went into some of the new features in Java 9, 10 & 11. Most companies are stuck on Java 8 so a lot of these features really are new to people. Chris did a really great job of opening the workshop by answering some questions around licensing and LTS (long term support) which really confuses a lot of people.",[651,69991,69992],{},[812,69993,69994],{"href":69994,"rel":69995},"https://twitter.com/therealdanvega/status/1214969350869995520",[816],[5909,69997,69999],{"id":69998},"calculating-insulin-with-automated-carb-counting-using-ai-ml-and-web-bluetooth","Calculating Insulin With Automated Carb Counting Using AI, ML and Web Bluetooth",[651,70001,70002,70003,50653],{},"Presented by: Todd Sharp (",[812,70004,70007],{"href":70005,"rel":70006},"https://twitter.com/recursivecodes",[816],"@recursivecodes",[651,70009,70010],{},"Not only is Todd a good friend but he is a better human being and developer. In this session Todd shows off the app he built to help is daughter out who was diagnosed with Type 1 Diabetes. There is a lot of cool tech and code that went into building this application and it was fun to see how it all goes together. I really enjoyed how Todd closed his presentation by saying we all have passions outside of coding, try and take your coding skills and your passions and do something good for this world. Kudos to you my friend.",[651,70012,70013],{},[812,70014,70015],{"href":70015,"rel":70016},"https://twitter.com/therealdanvega/status/1215689790756179968",[816],[651,70018,70019],{},"The picture above is a really bad picture by me because it looks like there are no people in the talk. I zoomed in on Todd and I just need to point that out because it was a well attended session. Sorry Todd 🤦♂️",[5909,70021,70023],{"id":70022},"building-a-super-performant-gossipgirlcom-in-gatsby-in-under-50-minutes","Building a super performant GossipGirl.com in Gatsby in under 50 Minutes",[651,70025,70026,70027,50653],{},"Presented by: Jennifer Wadella (",[812,70028,70031],{"href":70029,"rel":70030},"https://twitter.com/likeOMGitsFEDAY",[816],"@likeOMGitsFEDAY",[651,70033,70034],{},"I am not a React developer but I have heard so many great things about Gatsby so I thought I would check out this session and I am glad I did. Jennifer is a really great presenter who had a lot of fun with this talk and brought some great energy to the morning crowd. She ended up live coding an entire website from scratch and did an amazing job with it.",[651,70036,70037],{},[812,70038,70039],{"href":70039,"rel":70040},"https://twitter.com/therealdanvega/status/1215661794238324739",[816],[5909,70042,70044],{"id":70043},"supersonic-subatomic-java","Supersonic, Subatomic Java",[651,70046,70047,70048,50653],{},"Presented by: Scott Seighman (",[812,70049,70052],{"href":70050,"rel":70051},"https://twitter.com/ScottSeighman",[816],"@ScottSeighman",[651,70054,70055,70056,70061],{},"I have known Scott for a long time and I was excited to run into him at the conference. He told about this project he was working on at RedHat called ",[812,70057,70060],{"href":70058,"rel":70059},"https://quarkus.io/",[816],"Quarkus"," and that he was giving a presentation on it. I was really interested in this for writing AWS Lambda functions. While you can use Java to write your serverless functions startup times and memory consumption with using the out of the box Java 11 runtime are a big issue. With the small memory footprint and really fast startup times I think this could be the answer I have been looking for.",[651,70063,70064],{},[812,70065,70066],{"href":70066,"rel":70067},"https://twitter.com/therealdanvega/status/1215703419543003136",[816],[4542,70069,70071],{"id":70070},"pool-party","Pool Party",[651,70073,70074],{},"I am really glad that I was selected to speak on Thursday so that I could enjoy the pool party on Thursday night. The water park opened up for CodeMash attendees at 10 PM and I went down with some friends. We go to go on some really cool slides and relax in the swim bar. It was a lot of fun and if you plan on attending CodeMash in the future I would try and make the party.",[4542,70076,70078],{"id":70077},"my-session-vue-3-smaller-faster-stronger","My Session: Vue 3 - Smaller, Faster & Stronger",[651,70080,70081],{},"I put a lot of time and effort into this presentation and at the end of the day I think it really showed. The session was well attended and the audience was engaged. I got a lot of really great feedback after the talk which really makes me so happy and I feel incredibly lucky to be a speaker at such an amazing conference. My wife and daughter hung out in the back and watched the beginning of my talk and that made it all the more special.",[651,70083,70084],{},[660,70085],{"alt":70086,"src":70087},"Vue 3: Smaller, Faster & Stronger","/images/blog/2020/01/13/danvega_vue3_session.jpg",[651,70089,70090,70091,70094],{},"I als want to really thank everyone at ",[812,70092,41137],{"href":54681,"rel":70093},[816]," for supporting me. It means a lot to work for such a great company where everyone shares the same mission.",[651,70096,70097],{},[812,70098,70099],{"href":70099,"rel":70100},"https://twitter.com/Tech_Elevator/status/1215730352582512640",[816],[651,70102,70103,70104,70109],{},"Thank you to the CodeMash selection committee for such a privilege. If you want to learn more about the talk I have a ",[812,70105,70108],{"href":70106,"rel":70107},"https://www.danvega.dev/blog/2020/01/09/codemash-2020/",[816],"blog post here"," that has details and resources about it. The talk was recorded by Pluralsight and I will let you all know when it has been released to the public.",[4542,70111,9042],{"id":9041},[651,70113,70114],{},"CodeMash reminded me just how much I love going to & speaking at conferences. I am going to make a huge effort to submit more CFPs this year and I hope to see all of you at a conference near you. I also look forward to attending CodeMash next year as an attendee and hopefully speaker!",{"title":674,"searchDepth":790,"depth":790,"links":70116},[70117,70118,70119,70125,70126,70127],{"id":69950,"depth":790,"text":69951},{"id":69957,"depth":790,"text":69958},{"id":69970,"depth":790,"text":69971,"children":70120},[70121,70122,70123,70124],{"id":69977,"depth":892,"text":69978},{"id":69998,"depth":892,"text":69999},{"id":70022,"depth":892,"text":70023},{"id":70043,"depth":892,"text":70044},{"id":70070,"depth":790,"text":70071},{"id":70077,"depth":790,"text":70078},{"id":9041,"depth":790,"text":9042},"A recap of the conference CodeMash 2020 as both an attendee and a speaker.",{"slug":70130,"date":70131,"published":797,"author":798,"tags":70132,"cover":70133},"codemash-2020-recap","2020-01-13T14:30:00.000Z",[41511,44169],"./codemash-recap-cover.png",{"title":447,"description":70128},"blog/2020/01/13/codemash-2020-recap","LZq0PBha_Xf4Ew3jsFFhogPKibAB9sj4B2MfvLK0U3Q",{"id":70138,"title":444,"body":70139,"description":70536,"extension":793,"meta":70537,"navigation":797,"path":445,"seo":70544,"stem":70545,"__hash__":70546},"content/blog/2020/01/17/vue-3-alpha-cli-plugin.md",{"type":648,"value":70140,"toc":70529},[70141,70144,70147,70151,70184,70187,70314,70318,70506,70508,70526],[651,70142,70143],{},"So you've heard of Vue 3 and you want to start playing around with it but not sure where to start. Did you know that you can add Vue 3 to a new project using the Vue CLI? In this tutorial, I will show you how to create a new project using the Vue CLI and then using a new plugin, add Vue 3 Alpha to your project.",[651,70145,70146],{},"With Vue 3 installed we will walk through creating a new component using the Composition API. We will start by creating a simple Counter component and it might be a trivial example it does allow you to see the building blocks of Vue's new Composition API.",[4542,70148,70150],{"id":70149},"sample-code","Sample Code",[669,70152,70154],{"className":7993,"code":70153,"language":7995,"meta":674,"style":674},"# create a Vue 2 project using the Vue CLI\nvue create hello-vue3-sfc\n# in an existing Vue CLI project\nvue add vue-next\n",[676,70155,70156,70161,70170,70175],{"__ignoreMap":674},[679,70157,70158],{"class":681,"line":682},[679,70159,70160],{"class":1400},"# create a Vue 2 project using the Vue CLI\n",[679,70162,70163,70165,70167],{"class":681,"line":790},[679,70164,44169],{"class":880},[679,70166,49468],{"class":689},[679,70168,70169],{"class":689}," hello-vue3-sfc\n",[679,70171,70172],{"class":681,"line":892},[679,70173,70174],{"class":1400},"# in an existing Vue CLI project\n",[679,70176,70177,70179,70181],{"class":681,"line":901},[679,70178,44169],{"class":880},[679,70180,8872],{"class":689},[679,70182,70183],{"class":689}," vue-next\n",[5909,70185,49477],{"id":70186},"appvue",[669,70188,70190],{"className":50297,"code":70189,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv id=\"app\">\n \u003Ccounter>\u003C/counter>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nimport Counter from './components/Counter.vue';\n\nexport default {\n name: \"app\",\n components: {\n Counter\n }\n};\n\u003C/script>\n",[676,70191,70192,70200,70214,70227,70235,70243,70247,70255,70269,70273,70281,70289,70293,70298,70302,70306],{"__ignoreMap":674},[679,70193,70194,70196,70198],{"class":681,"line":682},[679,70195,4505],{"class":693},[679,70197,45982],{"class":4508},[679,70199,4519],{"class":693},[679,70201,70202,70204,70206,70208,70210,70212],{"class":681,"line":790},[679,70203,11738],{"class":693},[679,70205,4509],{"class":4508},[679,70207,5578],{"class":880},[679,70209,686],{"class":693},[679,70211,28626],{"class":689},[679,70213,4519],{"class":693},[679,70215,70216,70218,70221,70223,70225],{"class":681,"line":892},[679,70217,4524],{"class":693},[679,70219,70220],{"class":4508},"counter",[679,70222,4563],{"class":693},[679,70224,70220],{"class":4508},[679,70226,4519],{"class":693},[679,70228,70229,70231,70233],{"class":681,"line":901},[679,70230,11840],{"class":693},[679,70232,4509],{"class":4508},[679,70234,4519],{"class":693},[679,70236,70237,70239,70241],{"class":681,"line":909},[679,70238,4586],{"class":693},[679,70240,45982],{"class":4508},[679,70242,4519],{"class":693},[679,70244,70245],{"class":681,"line":918},[679,70246,889],{"emptyLinePlaceholder":797},[679,70248,70249,70251,70253],{"class":681,"line":935},[679,70250,4505],{"class":693},[679,70252,47668],{"class":4508},[679,70254,4519],{"class":693},[679,70256,70257,70259,70262,70264,70267],{"class":681,"line":944},[679,70258,1999],{"class":685},[679,70260,70261],{"class":693}," Counter ",[679,70263,28887],{"class":685},[679,70265,70266],{"class":689}," './components/Counter.vue'",[679,70268,1186],{"class":693},[679,70270,70271],{"class":681,"line":959},[679,70272,889],{"emptyLinePlaceholder":797},[679,70274,70275,70277,70279],{"class":681,"line":964},[679,70276,29245],{"class":685},[679,70278,50460],{"class":685},[679,70280,884],{"class":693},[679,70282,70283,70285,70287],{"class":681,"line":977},[679,70284,49970],{"class":693},[679,70286,28626],{"class":689},[679,70288,12083],{"class":693},[679,70290,70291],{"class":681,"line":982},[679,70292,54590],{"class":693},[679,70294,70295],{"class":681,"line":988},[679,70296,70297],{"class":693}," Counter\n",[679,70299,70300],{"class":681,"line":993},[679,70301,21405],{"class":693},[679,70303,70304],{"class":681,"line":2129},[679,70305,44055],{"class":693},[679,70307,70308,70310,70312],{"class":681,"line":2140},[679,70309,4586],{"class":693},[679,70311,47668],{"class":4508},[679,70313,4519],{"class":693},[5909,70315,70317],{"id":70316},"countervue","Counter.vue",[669,70319,70321],{"className":50297,"code":70320,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cbutton @click=\"increment\">\n Count: {{ state.count }} Double {{ state.double }}\n \u003C/button>\n\u003C/template>\n\n\u003Cscript>\nimport { reactive, computed } from 'vue';\n\nexport default {\n setup() {\n const state = reactive({\n count: 0,\n double: computed(() => state.count * 2)\n });\n function increment() {\n state.count++;\n }\n return { state, increment };\n }\n};\n\u003C/script>\n",[676,70322,70323,70331,70346,70351,70359,70367,70371,70379,70393,70397,70405,70412,70426,70435,70456,70460,70470,70479,70483,70490,70494,70498],{"__ignoreMap":674},[679,70324,70325,70327,70329],{"class":681,"line":682},[679,70326,4505],{"class":693},[679,70328,45982],{"class":4508},[679,70330,4519],{"class":693},[679,70332,70333,70335,70337,70339,70341,70344],{"class":681,"line":790},[679,70334,11738],{"class":693},[679,70336,54258],{"class":4508},[679,70338,63399],{"class":880},[679,70340,686],{"class":693},[679,70342,70343],{"class":689},"\"increment\"",[679,70345,4519],{"class":693},[679,70347,70348],{"class":681,"line":892},[679,70349,70350],{"class":693}," Count: {{ state.count }} Double {{ state.double }}\n",[679,70352,70353,70355,70357],{"class":681,"line":901},[679,70354,11840],{"class":693},[679,70356,54258],{"class":4508},[679,70358,4519],{"class":693},[679,70360,70361,70363,70365],{"class":681,"line":909},[679,70362,4586],{"class":693},[679,70364,45982],{"class":4508},[679,70366,4519],{"class":693},[679,70368,70369],{"class":681,"line":918},[679,70370,889],{"emptyLinePlaceholder":797},[679,70372,70373,70375,70377],{"class":681,"line":935},[679,70374,4505],{"class":693},[679,70376,47668],{"class":4508},[679,70378,4519],{"class":693},[679,70380,70381,70383,70386,70388,70391],{"class":681,"line":944},[679,70382,1999],{"class":685},[679,70384,70385],{"class":693}," { reactive, computed } ",[679,70387,28887],{"class":685},[679,70389,70390],{"class":689}," 'vue'",[679,70392,1186],{"class":693},[679,70394,70395],{"class":681,"line":959},[679,70396,889],{"emptyLinePlaceholder":797},[679,70398,70399,70401,70403],{"class":681,"line":964},[679,70400,29245],{"class":685},[679,70402,50460],{"class":685},[679,70404,884],{"class":693},[679,70406,70407,70410],{"class":681,"line":977},[679,70408,70409],{"class":880}," setup",[679,70411,2667],{"class":693},[679,70413,70414,70416,70419,70421,70424],{"class":681,"line":982},[679,70415,54021],{"class":685},[679,70417,70418],{"class":931}," state",[679,70420,6883],{"class":685},[679,70422,70423],{"class":880}," reactive",[679,70425,21218],{"class":693},[679,70427,70428,70431,70433],{"class":681,"line":988},[679,70429,70430],{"class":693}," count: ",[679,70432,1060],{"class":931},[679,70434,12083],{"class":693},[679,70436,70437,70440,70443,70445,70447,70450,70452,70454],{"class":681,"line":993},[679,70438,70439],{"class":693}," double: ",[679,70441,70442],{"class":880},"computed",[679,70444,55186],{"class":693},[679,70446,21350],{"class":685},[679,70448,70449],{"class":693}," state.count ",[679,70451,4150],{"class":685},[679,70453,21871],{"class":931},[679,70455,1339],{"class":693},[679,70457,70458],{"class":681,"line":2129},[679,70459,59286],{"class":693},[679,70461,70462,70465,70468],{"class":681,"line":2140},[679,70463,70464],{"class":685}," function",[679,70466,70467],{"class":880}," increment",[679,70469,2667],{"class":693},[679,70471,70472,70475,70477],{"class":681,"line":2145},[679,70473,70474],{"class":693}," state.count",[679,70476,1569],{"class":685},[679,70478,1186],{"class":693},[679,70480,70481],{"class":681,"line":2154},[679,70482,985],{"class":693},[679,70484,70485,70487],{"class":681,"line":2159},[679,70486,21478],{"class":685},[679,70488,70489],{"class":693}," { state, increment };\n",[679,70491,70492],{"class":681,"line":2164},[679,70493,21405],{"class":693},[679,70495,70496],{"class":681,"line":3134},[679,70497,44055],{"class":693},[679,70499,70500,70502,70504],{"class":681,"line":3139},[679,70501,4586],{"class":693},[679,70503,47668],{"class":4508},[679,70505,4519],{"class":693},[4542,70507,21931],{"id":21930},[5316,70509,70510,70515,70520],{},[5332,70511,70512],{},[812,70513,69867],{"href":69867,"rel":70514},[816],[5332,70516,70517],{},[812,70518,69874],{"href":69874,"rel":70519},[816],[5332,70521,70522],{},[812,70523,70524],{"href":70524,"rel":70525},"https://github.com/vuejs/vue-cli-plugin-vue-next",[816],[786,70527,70528],{},"html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":70530},[70531,70535],{"id":70149,"depth":790,"text":70150,"children":70532},[70533,70534],{"id":70186,"depth":892,"text":49477},{"id":70316,"depth":892,"text":70317},{"id":21930,"depth":790,"text":21931},"In this tutorial, I walk you through how to add Vue 3 to a new project.",{"slug":70538,"date":70539,"published":797,"author":798,"tags":70540,"cover":70542,"video":70543},"vue-3-alpha-cli-plugin","2020-01-17T13:58:29.936Z",[70541,36273],"vue3","./start-using-vue3-today-cover.png","https://www.youtube.com/embed/o-jiS563yI8",{"title":444,"description":70536},"blog/2020/01/17/vue-3-alpha-cli-plugin","sx_TiDMfIGl3l9rT6n590SHbGxQD6M4u58jRh0WhCpk",{"id":70548,"title":441,"body":70549,"description":72889,"extension":793,"meta":72890,"navigation":797,"path":442,"seo":72896,"stem":72897,"__hash__":72898},"content/blog/2020/02/12/vue3-ref-vs-reactive.md",{"type":648,"value":70550,"toc":72878},[70551,70554,70557,70564,70570,70573,70576,70580,70583,70677,70688,70692,70699,70797,70806,70939,70955,71119,71125,71129,71135,71156,71264,71270,71373,71390,71464,71479,71483,71489,71522,71528,71535,71539,71554,71567,71646,71662,71786,71790,71805,71810,71822,71934,71941,72142,72145,72149,72160,72163,72166,72356,72359,72494,72497,72610,72613,72755,72762,72856,72859,72862,72871,72875],[651,70552,70553],{},"At the time of writing this article, we are getting closer and closer to the release of Vue 3. I think what I am most excited about is to see how other developers embrace it and use it. While I have had a chance to play with it over the last few months I know that isn't the case for everyone.",[651,70555,70556],{},"The biggest feature coming to Vue 3 is the Composition API. This offers an alternative approach to creating components that is much different than the existing options API. I have no problem admitting that when I first saw it, I didn't get it. The more I use it though the more it just makes sense. While you won't go rewriting entire applications using the Composition API it will make you think about how create components and compose functionality going forward.",[651,70558,11688,70559,70563],{},[812,70560,70562],{"href":70106,"rel":70561},[816],"given a couple"," presentations on Vue 3 recently and one question that keeps coming up is when do I use Ref vs Reactive to declare a reactive property. I never had a great answer for this so over the past couple of weeks I set out to answer this question and this article is the result of that research.",[651,70565,70566],{},[660,70567],{"alt":70568,"src":70569},"New Technology","/images/blog/2020/02/12/christopher-gower-m_HRfLhgABo-unsplash.jpg",[651,70571,70572],{},"I would also like to point out that this is my opinion and please do not take this as \"the way\" things should be. This is how I am going to use Ref & Reactive until someone tells me otherwise or until I discover a better approach. With any new technology, I think that it takes some time to figure out how we use it and from there a best practice might emerge.",[651,70574,70575],{},"Before we get started I am going to assume that you have at least looked at the Composition API and understand the different components of it. This article is going to focus on Ref vs Reactive and not the mechanics of the Composition API. If you're interested in an in-depth tutorial on that please let me know.",[4542,70577,70579],{"id":70578},"reactive-state-in-vue-2","Reactive State in Vue 2",[651,70581,70582],{},"To give this article a little context I want to quickly explore how to create reactive data in a Vue 2 application. When you want Vue to keep track of changes to data you need to declare that property inside of an object that is returned from the data function.",[669,70584,70586],{"className":4496,"code":70585,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>{{ title }}\u003C/h1>\n\u003C/template>\n\n\u003Cscript>\n export default {\n data() {\n return {\n title: \"Hello, Vue!\"\n };\n }\n };\n\u003C/script>\n",[676,70587,70588,70596,70609,70617,70621,70629,70637,70643,70649,70657,70661,70665,70669],{"__ignoreMap":674},[679,70589,70590,70592,70594],{"class":681,"line":682},[679,70591,4505],{"class":693},[679,70593,45982],{"class":4508},[679,70595,4519],{"class":693},[679,70597,70598,70600,70602,70605,70607],{"class":681,"line":790},[679,70599,11738],{"class":693},[679,70601,11859],{"class":4508},[679,70603,70604],{"class":693},">{{ title }}\u003C/",[679,70606,11859],{"class":4508},[679,70608,4519],{"class":693},[679,70610,70611,70613,70615],{"class":681,"line":892},[679,70612,4586],{"class":693},[679,70614,45982],{"class":4508},[679,70616,4519],{"class":693},[679,70618,70619],{"class":681,"line":901},[679,70620,889],{"emptyLinePlaceholder":797},[679,70622,70623,70625,70627],{"class":681,"line":909},[679,70624,4505],{"class":693},[679,70626,47668],{"class":4508},[679,70628,4519],{"class":693},[679,70630,70631,70633,70635],{"class":681,"line":918},[679,70632,63260],{"class":685},[679,70634,50460],{"class":685},[679,70636,884],{"class":693},[679,70638,70639,70641],{"class":681,"line":935},[679,70640,63461],{"class":880},[679,70642,2667],{"class":693},[679,70644,70645,70647],{"class":681,"line":944},[679,70646,63468],{"class":685},[679,70648,884],{"class":693},[679,70650,70651,70654],{"class":681,"line":959},[679,70652,70653],{"class":693}," title: ",[679,70655,70656],{"class":689},"\"Hello, Vue!\"\n",[679,70658,70659],{"class":681,"line":964},[679,70660,19700],{"class":693},[679,70662,70663],{"class":681,"line":977},[679,70664,985],{"class":693},[679,70666,70667],{"class":681,"line":982},[679,70668,53075],{"class":693},[679,70670,70671,70673,70675],{"class":681,"line":988},[679,70672,4586],{"class":693},[679,70674,47668],{"class":4508},[679,70676,4519],{"class":693},[651,70678,70679,70680,70683,70684,70687],{},"Under the hood Vue 2, looks at each property and uses ",[676,70681,70682],{},"Object.defineProperty()"," to create getters and setters for each piece of data it needs to keep track of. This is a basic explanation of the process but what I want to get across is that it is not magic. You can't just create data anywhere and expect Vue to keep track of it. You must follow the process of defining it in the ",[676,70685,70686],{},"data()"," function.",[4542,70689,70691],{"id":70690},"ref-vs-reactive","Ref vs Reactive",[651,70693,70694,70695,70698],{},"With the Options API, we have to follow some rules when defining reactive data and the Composition API is no different. You can't just declare data and expect Vue to know that you would like it tracked for changes. In the following example, I have defined a title and returned that from the ",[676,70696,70697],{},"setup()"," function making it available in the template.",[669,70700,70702],{"className":4496,"code":70701,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>{{ title }}\u003C/h1>\n\u003C/template>\n\n\u003Cscript>\n export default {\n setup() {\n let title = \"Hello, Vue 3!\";\n return { title };\n }\n };\n\u003C/script>\n",[676,70703,70704,70712,70724,70732,70736,70744,70752,70759,70774,70781,70785,70789],{"__ignoreMap":674},[679,70705,70706,70708,70710],{"class":681,"line":682},[679,70707,4505],{"class":693},[679,70709,45982],{"class":4508},[679,70711,4519],{"class":693},[679,70713,70714,70716,70718,70720,70722],{"class":681,"line":790},[679,70715,11738],{"class":693},[679,70717,11859],{"class":4508},[679,70719,70604],{"class":693},[679,70721,11859],{"class":4508},[679,70723,4519],{"class":693},[679,70725,70726,70728,70730],{"class":681,"line":892},[679,70727,4586],{"class":693},[679,70729,45982],{"class":4508},[679,70731,4519],{"class":693},[679,70733,70734],{"class":681,"line":901},[679,70735,889],{"emptyLinePlaceholder":797},[679,70737,70738,70740,70742],{"class":681,"line":909},[679,70739,4505],{"class":693},[679,70741,47668],{"class":4508},[679,70743,4519],{"class":693},[679,70745,70746,70748,70750],{"class":681,"line":918},[679,70747,63260],{"class":685},[679,70749,50460],{"class":685},[679,70751,884],{"class":693},[679,70753,70754,70757],{"class":681,"line":935},[679,70755,70756],{"class":880}," setup",[679,70758,2667],{"class":693},[679,70760,70761,70764,70767,70769,70772],{"class":681,"line":944},[679,70762,70763],{"class":685}," let",[679,70765,70766],{"class":693}," title ",[679,70768,686],{"class":685},[679,70770,70771],{"class":689}," \"Hello, Vue 3!\"",[679,70773,1186],{"class":693},[679,70775,70776,70778],{"class":681,"line":959},[679,70777,63468],{"class":685},[679,70779,70780],{"class":693}," { title };\n",[679,70782,70783],{"class":681,"line":964},[679,70784,985],{"class":693},[679,70786,70787],{"class":681,"line":977},[679,70788,53075],{"class":693},[679,70790,70791,70793,70795],{"class":681,"line":982},[679,70792,4586],{"class":693},[679,70794,47668],{"class":4508},[679,70796,4519],{"class":693},[651,70798,70799,70800,70802,70803,70805],{},"This will work but the title property is not reactive. This means that if something changes title those changes will ",[2939,70801,10922],{}," be reflected in the DOM. Say for example you wanted to update the title after 5 seconds, the following will ",[2939,70804,10922],{}," work.",[669,70807,70809],{"className":4496,"code":70808,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>{{ title }}\u003C/h1>\n\u003C/template>\n\n\u003Cscript>\n export default {\n setup() {\n let title = \"Hello, Vue 3!\";\n\n setTimeout(() => {\n title = \"THIS IS A NEW TITLE\";\n }, 5000);\n\n return { title };\n }\n };\n\u003C/script>\n",[676,70810,70811,70819,70831,70839,70843,70851,70859,70865,70877,70881,70892,70904,70913,70917,70923,70927,70931],{"__ignoreMap":674},[679,70812,70813,70815,70817],{"class":681,"line":682},[679,70814,4505],{"class":693},[679,70816,45982],{"class":4508},[679,70818,4519],{"class":693},[679,70820,70821,70823,70825,70827,70829],{"class":681,"line":790},[679,70822,11738],{"class":693},[679,70824,11859],{"class":4508},[679,70826,70604],{"class":693},[679,70828,11859],{"class":4508},[679,70830,4519],{"class":693},[679,70832,70833,70835,70837],{"class":681,"line":892},[679,70834,4586],{"class":693},[679,70836,45982],{"class":4508},[679,70838,4519],{"class":693},[679,70840,70841],{"class":681,"line":901},[679,70842,889],{"emptyLinePlaceholder":797},[679,70844,70845,70847,70849],{"class":681,"line":909},[679,70846,4505],{"class":693},[679,70848,47668],{"class":4508},[679,70850,4519],{"class":693},[679,70852,70853,70855,70857],{"class":681,"line":918},[679,70854,63260],{"class":685},[679,70856,50460],{"class":685},[679,70858,884],{"class":693},[679,70860,70861,70863],{"class":681,"line":935},[679,70862,70756],{"class":880},[679,70864,2667],{"class":693},[679,70866,70867,70869,70871,70873,70875],{"class":681,"line":944},[679,70868,70763],{"class":685},[679,70870,70766],{"class":693},[679,70872,686],{"class":685},[679,70874,70771],{"class":689},[679,70876,1186],{"class":693},[679,70878,70879],{"class":681,"line":959},[679,70880,889],{"emptyLinePlaceholder":797},[679,70882,70883,70886,70888,70890],{"class":681,"line":964},[679,70884,70885],{"class":880}," setTimeout",[679,70887,55186],{"class":693},[679,70889,21350],{"class":685},[679,70891,884],{"class":693},[679,70893,70894,70897,70899,70902],{"class":681,"line":977},[679,70895,70896],{"class":693}," title ",[679,70898,686],{"class":685},[679,70900,70901],{"class":689}," \"THIS IS A NEW TITLE\"",[679,70903,1186],{"class":693},[679,70905,70906,70908,70911],{"class":681,"line":982},[679,70907,57788],{"class":693},[679,70909,70910],{"class":931},"5000",[679,70912,1208],{"class":693},[679,70914,70915],{"class":681,"line":988},[679,70916,889],{"emptyLinePlaceholder":797},[679,70918,70919,70921],{"class":681,"line":993},[679,70920,63468],{"class":685},[679,70922,70780],{"class":693},[679,70924,70925],{"class":681,"line":2129},[679,70926,985],{"class":693},[679,70928,70929],{"class":681,"line":2140},[679,70930,53075],{"class":693},[679,70932,70933,70935,70937],{"class":681,"line":2145},[679,70934,4586],{"class":693},[679,70936,47668],{"class":4508},[679,70938,4519],{"class":693},[651,70940,70941,70942,70945,70946,70949,70950,664],{},"To fix the example above we can ",[676,70943,70944],{},"import { ref } from 'vue'"," and use ",[676,70947,70948],{},"ref()"," which will mark that variable as reactive data. Under the hood, and new in Vue 3, Vue will create a ",[812,70951,70954],{"href":70952,"rel":70953},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy",[816],"Proxy",[669,70956,70958],{"className":4496,"code":70957,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>{{ title }}\u003C/h1>\n\u003C/template>\n\n\u003Cscript>\n import { ref } from \"vue\";\n\n export default {\n setup() {\n const title = ref(\"Hello, Vue 3!\");\n\n setTimeout(() => {\n // you might be asking yourself, what is this .value all about...\n // more about that soon\n title.value = \"New Title\";\n }, 5000);\n\n return { title };\n }\n };\n\u003C/script>\n",[676,70959,70960,70968,70980,70988,70992,71000,71014,71018,71026,71032,71049,71053,71063,71068,71073,71085,71093,71097,71103,71107,71111],{"__ignoreMap":674},[679,70961,70962,70964,70966],{"class":681,"line":682},[679,70963,4505],{"class":693},[679,70965,45982],{"class":4508},[679,70967,4519],{"class":693},[679,70969,70970,70972,70974,70976,70978],{"class":681,"line":790},[679,70971,11738],{"class":693},[679,70973,11859],{"class":4508},[679,70975,70604],{"class":693},[679,70977,11859],{"class":4508},[679,70979,4519],{"class":693},[679,70981,70982,70984,70986],{"class":681,"line":892},[679,70983,4586],{"class":693},[679,70985,45982],{"class":4508},[679,70987,4519],{"class":693},[679,70989,70990],{"class":681,"line":901},[679,70991,889],{"emptyLinePlaceholder":797},[679,70993,70994,70996,70998],{"class":681,"line":909},[679,70995,4505],{"class":693},[679,70997,47668],{"class":4508},[679,70999,4519],{"class":693},[679,71001,71002,71004,71007,71009,71012],{"class":681,"line":918},[679,71003,63707],{"class":685},[679,71005,71006],{"class":693}," { ref } ",[679,71008,28887],{"class":685},[679,71010,71011],{"class":689}," \"vue\"",[679,71013,1186],{"class":693},[679,71015,71016],{"class":681,"line":935},[679,71017,889],{"emptyLinePlaceholder":797},[679,71019,71020,71022,71024],{"class":681,"line":944},[679,71021,63260],{"class":685},[679,71023,50460],{"class":685},[679,71025,884],{"class":693},[679,71027,71028,71030],{"class":681,"line":959},[679,71029,70756],{"class":880},[679,71031,2667],{"class":693},[679,71033,71034,71036,71038,71040,71042,71044,71047],{"class":681,"line":964},[679,71035,47094],{"class":685},[679,71037,5353],{"class":931},[679,71039,6883],{"class":685},[679,71041,54261],{"class":880},[679,71043,745],{"class":693},[679,71045,71046],{"class":689},"\"Hello, Vue 3!\"",[679,71048,1208],{"class":693},[679,71050,71051],{"class":681,"line":977},[679,71052,889],{"emptyLinePlaceholder":797},[679,71054,71055,71057,71059,71061],{"class":681,"line":982},[679,71056,70885],{"class":880},[679,71058,55186],{"class":693},[679,71060,21350],{"class":685},[679,71062,884],{"class":693},[679,71064,71065],{"class":681,"line":988},[679,71066,71067],{"class":1400}," // you might be asking yourself, what is this .value all about...\n",[679,71069,71070],{"class":681,"line":993},[679,71071,71072],{"class":1400}," // more about that soon\n",[679,71074,71075,71078,71080,71083],{"class":681,"line":2129},[679,71076,71077],{"class":693}," title.value ",[679,71079,686],{"class":685},[679,71081,71082],{"class":689}," \"New Title\"",[679,71084,1186],{"class":693},[679,71086,71087,71089,71091],{"class":681,"line":2140},[679,71088,57788],{"class":693},[679,71090,70910],{"class":931},[679,71092,1208],{"class":693},[679,71094,71095],{"class":681,"line":2145},[679,71096,889],{"emptyLinePlaceholder":797},[679,71098,71099,71101],{"class":681,"line":2154},[679,71100,63468],{"class":685},[679,71102,70780],{"class":693},[679,71104,71105],{"class":681,"line":2159},[679,71106,985],{"class":693},[679,71108,71109],{"class":681,"line":2164},[679,71110,53075],{"class":693},[679,71112,71113,71115,71117],{"class":681,"line":3134},[679,71114,4586],{"class":693},[679,71116,47668],{"class":4508},[679,71118,4519],{"class":693},[651,71120,71121,71122,71124],{},"I also want to be clear that when it comes to Ref vs Reactive I believe there are two stories to be told. The first story has to do when you're creating a component like we are above and you need to define reactive data. The second story is when you are creating composable functions that functions or components will ",[2939,71123,64410],{},". In this article, I will take a look at each of these scenarios.",[5909,71126,71128],{"id":71127},"ref","Ref",[651,71130,71131,71132,71134],{},"If you want to make a primitive data type a reactive property, ",[676,71133,70948],{}," is going to be your first choice. Again, this isn't a silver bullet but this is a good place to start. If you need a refresher the seven primitive data types in JavaScript are:",[5316,71136,71137,71139,71142,71145,71147,71150,71153],{},[5332,71138,4758],{},[5332,71140,71141],{},"Number",[5332,71143,71144],{},"BigInt",[5332,71146,1138],{},[5332,71148,71149],{},"Symbol",[5332,71151,71152],{},"Null",[5332,71154,71155],{},"Undefined",[669,71157,71159],{"className":64365,"code":71158,"language":64367,"meta":674,"style":674},"import { ref } from \"vue\";\n\nexport default {\n setup() {\n const title = ref(\"\");\n const one = ref(1);\n const isValid = ref(true);\n const foo = ref(null);\n }\n};\n",[676,71160,71161,71173,71177,71185,71191,71207,71223,71239,71256,71260],{"__ignoreMap":674},[679,71162,71163,71165,71167,71169,71171],{"class":681,"line":682},[679,71164,1999],{"class":685},[679,71166,71006],{"class":693},[679,71168,28887],{"class":685},[679,71170,71011],{"class":689},[679,71172,1186],{"class":693},[679,71174,71175],{"class":681,"line":790},[679,71176,889],{"emptyLinePlaceholder":797},[679,71178,71179,71181,71183],{"class":681,"line":892},[679,71180,29245],{"class":685},[679,71182,50460],{"class":685},[679,71184,884],{"class":693},[679,71186,71187,71189],{"class":681,"line":901},[679,71188,70409],{"class":880},[679,71190,2667],{"class":693},[679,71192,71193,71195,71197,71199,71201,71203,71205],{"class":681,"line":909},[679,71194,54021],{"class":685},[679,71196,5353],{"class":931},[679,71198,6883],{"class":685},[679,71200,54261],{"class":880},[679,71202,745],{"class":693},[679,71204,3579],{"class":689},[679,71206,1208],{"class":693},[679,71208,71209,71211,71213,71215,71217,71219,71221],{"class":681,"line":918},[679,71210,54021],{"class":685},[679,71212,62032],{"class":931},[679,71214,6883],{"class":685},[679,71216,54261],{"class":880},[679,71218,745],{"class":693},[679,71220,1557],{"class":931},[679,71222,1208],{"class":693},[679,71224,71225,71227,71229,71231,71233,71235,71237],{"class":681,"line":935},[679,71226,54021],{"class":685},[679,71228,20408],{"class":931},[679,71230,6883],{"class":685},[679,71232,54261],{"class":880},[679,71234,745],{"class":693},[679,71236,3441],{"class":931},[679,71238,1208],{"class":693},[679,71240,71241,71243,71246,71248,71250,71252,71254],{"class":681,"line":944},[679,71242,54021],{"class":685},[679,71244,71245],{"class":931}," foo",[679,71247,6883],{"class":685},[679,71249,54261],{"class":880},[679,71251,745],{"class":693},[679,71253,1146],{"class":931},[679,71255,1208],{"class":693},[679,71257,71258],{"class":681,"line":959},[679,71259,21405],{"class":693},[679,71261,71262],{"class":681,"line":964},[679,71263,44055],{"class":693},[651,71265,71266,71267,71269],{},"From the previous example, we had a String called title so ",[676,71268,70948],{}," was a good choice for declaring reactive data. If you have some questions about that code we wrote below don't worry, I had the same questions.",[669,71271,71273],{"className":64365,"code":71272,"language":64367,"meta":674,"style":674},"import { ref } from \"vue\";\n\nexport default {\n setup() {\n const title = ref(\"Hello, Vue 3!\");\n\n setTimeout(() => {\n title.value = \"New Title\";\n }, 5000);\n\n return { title };\n }\n};\n",[676,71274,71275,71287,71291,71299,71305,71321,71325,71336,71347,71355,71359,71365,71369],{"__ignoreMap":674},[679,71276,71277,71279,71281,71283,71285],{"class":681,"line":682},[679,71278,1999],{"class":685},[679,71280,71006],{"class":693},[679,71282,28887],{"class":685},[679,71284,71011],{"class":689},[679,71286,1186],{"class":693},[679,71288,71289],{"class":681,"line":790},[679,71290,889],{"emptyLinePlaceholder":797},[679,71292,71293,71295,71297],{"class":681,"line":892},[679,71294,29245],{"class":685},[679,71296,50460],{"class":685},[679,71298,884],{"class":693},[679,71300,71301,71303],{"class":681,"line":901},[679,71302,70409],{"class":880},[679,71304,2667],{"class":693},[679,71306,71307,71309,71311,71313,71315,71317,71319],{"class":681,"line":909},[679,71308,54021],{"class":685},[679,71310,5353],{"class":931},[679,71312,6883],{"class":685},[679,71314,54261],{"class":880},[679,71316,745],{"class":693},[679,71318,71046],{"class":689},[679,71320,1208],{"class":693},[679,71322,71323],{"class":681,"line":918},[679,71324,889],{"emptyLinePlaceholder":797},[679,71326,71327,71330,71332,71334],{"class":681,"line":935},[679,71328,71329],{"class":880}," setTimeout",[679,71331,55186],{"class":693},[679,71333,21350],{"class":685},[679,71335,884],{"class":693},[679,71337,71338,71341,71343,71345],{"class":681,"line":944},[679,71339,71340],{"class":693}," title.value ",[679,71342,686],{"class":685},[679,71344,71082],{"class":689},[679,71346,1186],{"class":693},[679,71348,71349,71351,71353],{"class":681,"line":959},[679,71350,58101],{"class":693},[679,71352,70910],{"class":931},[679,71354,1208],{"class":693},[679,71356,71357],{"class":681,"line":964},[679,71358,889],{"emptyLinePlaceholder":797},[679,71360,71361,71363],{"class":681,"line":977},[679,71362,21478],{"class":685},[679,71364,70780],{"class":693},[679,71366,71367],{"class":681,"line":982},[679,71368,21405],{"class":693},[679,71370,71371],{"class":681,"line":988},[679,71372,44055],{"class":693},[651,71374,71375,71376,71378,71379,71381,71382,71385,71386,71389],{},"Why use a ",[676,71377,45172],{}," for the title when the value is going to change? Shouldn't we be using ",[676,71380,51795],{}," here? If you were to ",[676,71383,71384],{},"console.log(title)"," you might expect to see the value ",[676,71387,71388],{},"Hello, Vue 3!",", instead you get an object that looks like this:",[669,71391,71393],{"className":64365,"code":71392,"language":64367,"meta":674,"style":674},"{_isRef: true}\nvalue: (...)\n_isRef: true\nget value: ƒ value()\nset value: ƒ value(newVal)\n__proto__: Object\n",[676,71394,71395,71409,71420,71428,71442,71456],{"__ignoreMap":674},[679,71396,71397,71400,71403,71405,71407],{"class":681,"line":682},[679,71398,71399],{"class":693},"{",[679,71401,71402],{"class":880},"_isRef",[679,71404,4282],{"class":693},[679,71406,3441],{"class":931},[679,71408,996],{"class":693},[679,71410,71411,71413,71416,71418],{"class":681,"line":790},[679,71412,19934],{"class":880},[679,71414,71415],{"class":693},": (",[679,71417,52190],{"class":685},[679,71419,1339],{"class":693},[679,71421,71422,71424,71426],{"class":681,"line":892},[679,71423,71402],{"class":880},[679,71425,4282],{"class":693},[679,71427,5134],{"class":931},[679,71429,71430,71433,71435,71438,71440],{"class":681,"line":901},[679,71431,71432],{"class":693},"get ",[679,71434,19934],{"class":880},[679,71436,71437],{"class":693},": ƒ ",[679,71439,19934],{"class":880},[679,71441,17545],{"class":693},[679,71443,71444,71447,71449,71451,71453],{"class":681,"line":909},[679,71445,71446],{"class":693},"set ",[679,71448,19934],{"class":880},[679,71450,71437],{"class":693},[679,71452,19934],{"class":880},[679,71454,71455],{"class":693},"(newVal)\n",[679,71457,71458,71461],{"class":681,"line":918},[679,71459,71460],{"class":880},"__proto__",[679,71462,71463],{"class":693},": Object\n",[651,71465,71466,71468,71469,71472,71473,71476,71477,664],{},[676,71467,70948],{}," takes an inner value and returns a reactive and mutable ref object. The ref object has a single property ",[676,71470,71471],{},".value"," that points to the inner value. This means that if you want to access or mutate the value you need to use ",[676,71474,71475],{},"title.value",". and because this is an object that won't change I have decided to declare it as a ",[676,71478,45172],{},[5909,71480,71482],{"id":71481},"ref-unwrapping","Ref Unwrapping",[651,71484,71485,71486,71488],{},"The next question you might ask is \"Why don't we have to reference ",[676,71487,71471],{}," in the template\"?",[669,71490,71492],{"className":4496,"code":71491,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>{{ title }}\u003C/h1>\n\u003C/template>\n",[676,71493,71494,71502,71514],{"__ignoreMap":674},[679,71495,71496,71498,71500],{"class":681,"line":682},[679,71497,4505],{"class":693},[679,71499,45982],{"class":4508},[679,71501,4519],{"class":693},[679,71503,71504,71506,71508,71510,71512],{"class":681,"line":790},[679,71505,11738],{"class":693},[679,71507,11859],{"class":4508},[679,71509,70604],{"class":693},[679,71511,11859],{"class":4508},[679,71513,4519],{"class":693},[679,71515,71516,71518,71520],{"class":681,"line":892},[679,71517,4586],{"class":693},[679,71519,45982],{"class":4508},[679,71521,4519],{"class":693},[651,71523,71524,71525,71527],{},"When a ref is returned as a property on the render context (the object returned from setup()) and accessed in the template, it automatically unwraps to the inner value. There is no need to append ",[676,71526,71471],{}," in the template.",[1004,71529,71530],{},[651,71531,71532,71533],{},"Computed Properties work the same so if you need the value of a computed property within the setup() method you will need to use ",[676,71534,71471],{},[5909,71536,71538],{"id":71537},"reactive","Reactive",[651,71540,71541,71542,71544,71545,71547,71548,71551,71552,664],{},"We just looked at some examples of using ",[676,71543,70948],{}," when you want to define reactive data on primitive values. What happens if you want to create an reactive object? In that case, you could still use ",[676,71546,70948],{}," but underneath the hood, it's just calling ",[676,71549,71550],{},"reactive()"," so I will stick to using ",[676,71553,71550],{},[651,71555,71556,71557,71559,71560,71562,71563,71566],{},"On the flip side of that ",[676,71558,71550],{}," will not work with primitive values. ",[676,71561,71550],{}," takes an object and returns a reactive proxy of the original. This is equivalent to 2.x's ",[676,71564,71565],{},"Vue.observable()"," and was renamed to avoid confusion with RxJS observables.",[669,71568,71570],{"className":64365,"code":71569,"language":64367,"meta":674,"style":674},"import { reactive } from \"vue\";\n\nexport default {\n setup() {\n const data = reactive({\n title: \"Hello, Vue 3\"\n });\n\n return { data };\n }\n};\n",[676,71571,71572,71585,71589,71597,71603,71616,71623,71627,71631,71638,71642],{"__ignoreMap":674},[679,71573,71574,71576,71579,71581,71583],{"class":681,"line":682},[679,71575,1999],{"class":685},[679,71577,71578],{"class":693}," { reactive } ",[679,71580,28887],{"class":685},[679,71582,71011],{"class":689},[679,71584,1186],{"class":693},[679,71586,71587],{"class":681,"line":790},[679,71588,889],{"emptyLinePlaceholder":797},[679,71590,71591,71593,71595],{"class":681,"line":892},[679,71592,29245],{"class":685},[679,71594,50460],{"class":685},[679,71596,884],{"class":693},[679,71598,71599,71601],{"class":681,"line":901},[679,71600,70409],{"class":880},[679,71602,2667],{"class":693},[679,71604,71605,71607,71610,71612,71614],{"class":681,"line":909},[679,71606,54021],{"class":685},[679,71608,71609],{"class":931}," data",[679,71611,6883],{"class":685},[679,71613,70423],{"class":880},[679,71615,21218],{"class":693},[679,71617,71618,71620],{"class":681,"line":918},[679,71619,53104],{"class":693},[679,71621,71622],{"class":689},"\"Hello, Vue 3\"\n",[679,71624,71625],{"class":681,"line":935},[679,71626,59286],{"class":693},[679,71628,71629],{"class":681,"line":944},[679,71630,889],{"emptyLinePlaceholder":797},[679,71632,71633,71635],{"class":681,"line":959},[679,71634,21478],{"class":685},[679,71636,71637],{"class":693}," { data };\n",[679,71639,71640],{"class":681,"line":964},[679,71641,21405],{"class":693},[679,71643,71644],{"class":681,"line":977},[679,71645,44055],{"class":693},[651,71647,71648,71649,71651,71652,71654,71655,71657,71658,71661],{},"The big difference here is when you want to access data defined using ",[676,71650,71550],{}," in your template. In the previous example ",[676,71653,54091],{}," is an object that contains a property named ",[676,71656,11750],{},". You will need to reference ",[676,71659,71660],{},"data.title"," in your template:",[669,71663,71665],{"className":4496,"code":71664,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>{{ data.title }}\u003C/h1>\n\u003C/template>\n\n\u003Cscript>\n import { ref } from \"vue\";\n\n export default {\n setup() {\n const data = ref({\n title: \"Hello, Vue 3\"\n });\n\n return { data };\n }\n };\n\u003C/script>\n",[676,71666,71667,71675,71688,71696,71700,71708,71720,71724,71732,71738,71750,71756,71760,71764,71770,71774,71778],{"__ignoreMap":674},[679,71668,71669,71671,71673],{"class":681,"line":682},[679,71670,4505],{"class":693},[679,71672,45982],{"class":4508},[679,71674,4519],{"class":693},[679,71676,71677,71679,71681,71684,71686],{"class":681,"line":790},[679,71678,11738],{"class":693},[679,71680,11859],{"class":4508},[679,71682,71683],{"class":693},">{{ data.title }}\u003C/",[679,71685,11859],{"class":4508},[679,71687,4519],{"class":693},[679,71689,71690,71692,71694],{"class":681,"line":892},[679,71691,4586],{"class":693},[679,71693,45982],{"class":4508},[679,71695,4519],{"class":693},[679,71697,71698],{"class":681,"line":901},[679,71699,889],{"emptyLinePlaceholder":797},[679,71701,71702,71704,71706],{"class":681,"line":909},[679,71703,4505],{"class":693},[679,71705,47668],{"class":4508},[679,71707,4519],{"class":693},[679,71709,71710,71712,71714,71716,71718],{"class":681,"line":918},[679,71711,63707],{"class":685},[679,71713,71006],{"class":693},[679,71715,28887],{"class":685},[679,71717,71011],{"class":689},[679,71719,1186],{"class":693},[679,71721,71722],{"class":681,"line":935},[679,71723,889],{"emptyLinePlaceholder":797},[679,71725,71726,71728,71730],{"class":681,"line":944},[679,71727,63260],{"class":685},[679,71729,50460],{"class":685},[679,71731,884],{"class":693},[679,71733,71734,71736],{"class":681,"line":959},[679,71735,70756],{"class":880},[679,71737,2667],{"class":693},[679,71739,71740,71742,71744,71746,71748],{"class":681,"line":964},[679,71741,47094],{"class":685},[679,71743,71609],{"class":931},[679,71745,6883],{"class":685},[679,71747,54261],{"class":880},[679,71749,21218],{"class":693},[679,71751,71752,71754],{"class":681,"line":977},[679,71753,70653],{"class":693},[679,71755,71622],{"class":689},[679,71757,71758],{"class":681,"line":982},[679,71759,47293],{"class":693},[679,71761,71762],{"class":681,"line":988},[679,71763,889],{"emptyLinePlaceholder":797},[679,71765,71766,71768],{"class":681,"line":993},[679,71767,63468],{"class":685},[679,71769,71637],{"class":693},[679,71771,71772],{"class":681,"line":2129},[679,71773,985],{"class":693},[679,71775,71776],{"class":681,"line":2140},[679,71777,53075],{"class":693},[679,71779,71780,71782,71784],{"class":681,"line":2145},[679,71781,4586],{"class":693},[679,71783,47668],{"class":4508},[679,71785,4519],{"class":693},[5909,71787,71789],{"id":71788},"ref-vs-reactive-in-components","Ref vs Reactive in Components",[651,71791,71792,71793,71795,71796,71798,71799,71804],{},"So based on everything discussed so far the answer is pretty easy right? We should just use ",[676,71794,70948],{}," for primitives and ",[676,71797,71550],{}," for objects. As I started building components out that wasn't always the case and in-fact ",[812,71800,71803],{"href":71801,"rel":71802},"https://vue-composition-api-rfc.netlify.com/#ref-vs-reactive",[816],"the documentation"," states:",[1004,71806,71807],{},[651,71808,71809],{},"The difference between using ref and reactive can be somewhat compared to how you would write standard JavaScript logic",[651,71811,71812,71813,71815,71816,71818,71819,71821],{},"I started thinking about that and it led me to the following conclusion. In the examples, we have seen I single property named ",[676,71814,11750],{}," which was a ",[676,71817,4758],{}," and it made perfect sense to use ",[676,71820,70948],{},". As my application started growing though I had the following properties defined:",[669,71823,71825],{"className":64365,"code":71824,"language":64367,"meta":674,"style":674},"export default {\n setup() {\n const title = ref(\"Hello, World!\");\n const description = ref(\"\");\n const content = ref(\"Hello world\");\n const wordCount = computed(() => content.value.length);\n\n return { title, description, content, wordCount };\n }\n};\n",[676,71826,71827,71835,71841,71858,71875,71892,71915,71919,71926,71930],{"__ignoreMap":674},[679,71828,71829,71831,71833],{"class":681,"line":682},[679,71830,29245],{"class":685},[679,71832,50460],{"class":685},[679,71834,884],{"class":693},[679,71836,71837,71839],{"class":681,"line":790},[679,71838,70409],{"class":880},[679,71840,2667],{"class":693},[679,71842,71843,71845,71847,71849,71851,71853,71856],{"class":681,"line":892},[679,71844,54021],{"class":685},[679,71846,5353],{"class":931},[679,71848,6883],{"class":685},[679,71850,54261],{"class":880},[679,71852,745],{"class":693},[679,71854,71855],{"class":689},"\"Hello, World!\"",[679,71857,1208],{"class":693},[679,71859,71860,71862,71865,71867,71869,71871,71873],{"class":681,"line":901},[679,71861,54021],{"class":685},[679,71863,71864],{"class":931}," description",[679,71866,6883],{"class":685},[679,71868,54261],{"class":880},[679,71870,745],{"class":693},[679,71872,3579],{"class":689},[679,71874,1208],{"class":693},[679,71876,71877,71879,71881,71883,71885,71887,71890],{"class":681,"line":909},[679,71878,54021],{"class":685},[679,71880,11995],{"class":931},[679,71882,6883],{"class":685},[679,71884,54261],{"class":880},[679,71886,745],{"class":693},[679,71888,71889],{"class":689},"\"Hello world\"",[679,71891,1208],{"class":693},[679,71893,71894,71896,71899,71901,71904,71906,71908,71911,71913],{"class":681,"line":918},[679,71895,54021],{"class":685},[679,71897,71898],{"class":931}," wordCount",[679,71900,6883],{"class":685},[679,71902,71903],{"class":880}," computed",[679,71905,55186],{"class":693},[679,71907,21350],{"class":685},[679,71909,71910],{"class":693}," content.value.",[679,71912,51959],{"class":931},[679,71914,1208],{"class":693},[679,71916,71917],{"class":681,"line":935},[679,71918,889],{"emptyLinePlaceholder":797},[679,71920,71921,71923],{"class":681,"line":944},[679,71922,21478],{"class":685},[679,71924,71925],{"class":693}," { title, description, content, wordCount };\n",[679,71927,71928],{"class":681,"line":959},[679,71929,21405],{"class":693},[679,71931,71932],{"class":681,"line":964},[679,71933,44055],{"class":693},[651,71935,71936,71937,71940],{},"In JavaScript, I would look at these properties and determine that they are all properties of my ",[676,71938,71939],{},"page"," object. In that case, I would group them all of them into a JavaScript object so why not do the same here.",[669,71942,71944],{"className":4496,"code":71943,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"page\">\n \u003Ch1>{{ page.title }}\u003C/h1>\n \u003Cp>{{ page.wordCount }}\u003C/p>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\n import { ref, computed, reactive } from \"vue\";\n\n export default {\n setup() {\n const page = reactive({\n title: \"Hello, World!\",\n description: \"\",\n content: \"Hello world\",\n wordCount: computed(() => page.content.length)\n });\n\n return { page };\n }\n };\n\u003C/script>\n",[676,71945,71946,71954,71969,71982,71995,72003,72011,72015,72023,72036,72040,72048,72054,72067,72075,72084,72093,72111,72115,72119,72126,72130,72134],{"__ignoreMap":674},[679,71947,71948,71950,71952],{"class":681,"line":682},[679,71949,4505],{"class":693},[679,71951,45982],{"class":4508},[679,71953,4519],{"class":693},[679,71955,71956,71958,71960,71962,71964,71967],{"class":681,"line":790},[679,71957,11738],{"class":693},[679,71959,4509],{"class":4508},[679,71961,4512],{"class":880},[679,71963,686],{"class":693},[679,71965,71966],{"class":689},"\"page\"",[679,71968,4519],{"class":693},[679,71970,71971,71973,71975,71978,71980],{"class":681,"line":892},[679,71972,4524],{"class":693},[679,71974,11859],{"class":4508},[679,71976,71977],{"class":693},">{{ page.title }}\u003C/",[679,71979,11859],{"class":4508},[679,71981,4519],{"class":693},[679,71983,71984,71986,71988,71991,71993],{"class":681,"line":901},[679,71985,4524],{"class":693},[679,71987,651],{"class":4508},[679,71989,71990],{"class":693},">{{ page.wordCount }}\u003C/",[679,71992,651],{"class":4508},[679,71994,4519],{"class":693},[679,71996,71997,71999,72001],{"class":681,"line":909},[679,71998,11840],{"class":693},[679,72000,4509],{"class":4508},[679,72002,4519],{"class":693},[679,72004,72005,72007,72009],{"class":681,"line":918},[679,72006,4586],{"class":693},[679,72008,45982],{"class":4508},[679,72010,4519],{"class":693},[679,72012,72013],{"class":681,"line":935},[679,72014,889],{"emptyLinePlaceholder":797},[679,72016,72017,72019,72021],{"class":681,"line":944},[679,72018,4505],{"class":693},[679,72020,47668],{"class":4508},[679,72022,4519],{"class":693},[679,72024,72025,72027,72030,72032,72034],{"class":681,"line":959},[679,72026,63707],{"class":685},[679,72028,72029],{"class":693}," { ref, computed, reactive } ",[679,72031,28887],{"class":685},[679,72033,71011],{"class":689},[679,72035,1186],{"class":693},[679,72037,72038],{"class":681,"line":964},[679,72039,889],{"emptyLinePlaceholder":797},[679,72041,72042,72044,72046],{"class":681,"line":977},[679,72043,63260],{"class":685},[679,72045,50460],{"class":685},[679,72047,884],{"class":693},[679,72049,72050,72052],{"class":681,"line":982},[679,72051,70756],{"class":880},[679,72053,2667],{"class":693},[679,72055,72056,72058,72061,72063,72065],{"class":681,"line":988},[679,72057,47094],{"class":685},[679,72059,72060],{"class":931}," page",[679,72062,6883],{"class":685},[679,72064,70423],{"class":880},[679,72066,21218],{"class":693},[679,72068,72069,72071,72073],{"class":681,"line":993},[679,72070,70653],{"class":693},[679,72072,71855],{"class":689},[679,72074,12083],{"class":693},[679,72076,72077,72080,72082],{"class":681,"line":2129},[679,72078,72079],{"class":693}," description: ",[679,72081,3579],{"class":689},[679,72083,12083],{"class":693},[679,72085,72086,72089,72091],{"class":681,"line":2140},[679,72087,72088],{"class":693}," content: ",[679,72090,71889],{"class":689},[679,72092,12083],{"class":693},[679,72094,72095,72098,72100,72102,72104,72107,72109],{"class":681,"line":2145},[679,72096,72097],{"class":693}," wordCount: ",[679,72099,70442],{"class":880},[679,72101,55186],{"class":693},[679,72103,21350],{"class":685},[679,72105,72106],{"class":693}," page.content.",[679,72108,51959],{"class":931},[679,72110,1339],{"class":693},[679,72112,72113],{"class":681,"line":2154},[679,72114,47293],{"class":693},[679,72116,72117],{"class":681,"line":2159},[679,72118,889],{"emptyLinePlaceholder":797},[679,72120,72121,72123],{"class":681,"line":2164},[679,72122,63468],{"class":685},[679,72124,72125],{"class":693}," { page };\n",[679,72127,72128],{"class":681,"line":3134},[679,72129,985],{"class":693},[679,72131,72132],{"class":681,"line":3139},[679,72133,53075],{"class":693},[679,72135,72136,72138,72140],{"class":681,"line":3144},[679,72137,4586],{"class":693},[679,72139,47668],{"class":4508},[679,72141,4519],{"class":693},[651,72143,72144],{},"This is how I have been approaching Ref vs Reactive in my components but I would love to hear from you. Are you doing something similar? Is this approach wrong? Please leave me some feedback below.",[4542,72146,72148],{"id":72147},"creating-composable-logic","Creating Composable Logic",[651,72150,72151,72152,51393,72154,72156,72157,72159],{},"There isn't any wrong answer when using ",[676,72153,70948],{},[676,72155,71550],{}," in your components. They both will create reactive data and as long as you understand how to access that data in your ",[676,72158,70697],{}," method and in your templates you shouldn't have any issues.",[651,72161,72162],{},"When you start writing composable functions though you need to understand the difference. I am going to use the example from the RFC documentation because it does a great job of explaining the side effects.",[651,72164,72165],{},"You have been tasked with creating some logic that will keep track of a user's mouse position. You also need the ability to reuse this logic in any component that needs it. You create a composition function that tracks the x and y coordinates and then returns them to the consumer.",[669,72167,72169],{"className":64365,"code":72168,"language":64367,"meta":674,"style":674},"import { ref, onMounted, onUnmounted } from \"vue\";\n\nexport function useMousePosition() {\n const x = ref(0);\n const y = ref(0);\n\n function update(e) {\n x.value = e.pageX;\n y.value = e.pageY;\n }\n\n onMounted(() => {\n window.addEventListener(\"mousemove\", update);\n });\n\n onUnmounted(() => {\n window.removeEventListener(\"mousemove\", update);\n });\n\n return { x, y };\n}\n",[676,72170,72171,72184,72188,72199,72216,72233,72237,72251,72261,72271,72275,72279,72290,72305,72309,72313,72324,72337,72341,72345,72352],{"__ignoreMap":674},[679,72172,72173,72175,72178,72180,72182],{"class":681,"line":682},[679,72174,1999],{"class":685},[679,72176,72177],{"class":693}," { ref, onMounted, onUnmounted } ",[679,72179,28887],{"class":685},[679,72181,71011],{"class":689},[679,72183,1186],{"class":693},[679,72185,72186],{"class":681,"line":790},[679,72187,889],{"emptyLinePlaceholder":797},[679,72189,72190,72192,72194,72197],{"class":681,"line":892},[679,72191,29245],{"class":685},[679,72193,21700],{"class":685},[679,72195,72196],{"class":880}," useMousePosition",[679,72198,2667],{"class":693},[679,72200,72201,72203,72206,72208,72210,72212,72214],{"class":681,"line":901},[679,72202,46903],{"class":685},[679,72204,72205],{"class":931}," x",[679,72207,6883],{"class":685},[679,72209,54261],{"class":880},[679,72211,745],{"class":693},[679,72213,1060],{"class":931},[679,72215,1208],{"class":693},[679,72217,72218,72220,72223,72225,72227,72229,72231],{"class":681,"line":909},[679,72219,46903],{"class":685},[679,72221,72222],{"class":931}," y",[679,72224,6883],{"class":685},[679,72226,54261],{"class":880},[679,72228,745],{"class":693},[679,72230,1060],{"class":931},[679,72232,1208],{"class":693},[679,72234,72235],{"class":681,"line":918},[679,72236,889],{"emptyLinePlaceholder":797},[679,72238,72239,72242,72245,72247,72249],{"class":681,"line":935},[679,72240,72241],{"class":685}," function",[679,72243,72244],{"class":880}," update",[679,72246,745],{"class":693},[679,72248,9400],{"class":2099},[679,72250,4390],{"class":693},[679,72252,72253,72256,72258],{"class":681,"line":944},[679,72254,72255],{"class":693}," x.value ",[679,72257,686],{"class":685},[679,72259,72260],{"class":693}," e.pageX;\n",[679,72262,72263,72266,72268],{"class":681,"line":959},[679,72264,72265],{"class":693}," y.value ",[679,72267,686],{"class":685},[679,72269,72270],{"class":693}," e.pageY;\n",[679,72272,72273],{"class":681,"line":964},[679,72274,21405],{"class":693},[679,72276,72277],{"class":681,"line":977},[679,72278,889],{"emptyLinePlaceholder":797},[679,72280,72281,72284,72286,72288],{"class":681,"line":982},[679,72282,72283],{"class":880}," onMounted",[679,72285,55186],{"class":693},[679,72287,21350],{"class":685},[679,72289,884],{"class":693},[679,72291,72292,72295,72297,72299,72302],{"class":681,"line":988},[679,72293,72294],{"class":693}," window.",[679,72296,64879],{"class":880},[679,72298,745],{"class":693},[679,72300,72301],{"class":689},"\"mousemove\"",[679,72303,72304],{"class":693},", update);\n",[679,72306,72307],{"class":681,"line":993},[679,72308,56103],{"class":693},[679,72310,72311],{"class":681,"line":2129},[679,72312,889],{"emptyLinePlaceholder":797},[679,72314,72315,72318,72320,72322],{"class":681,"line":2140},[679,72316,72317],{"class":880}," onUnmounted",[679,72319,55186],{"class":693},[679,72321,21350],{"class":685},[679,72323,884],{"class":693},[679,72325,72326,72328,72331,72333,72335],{"class":681,"line":2145},[679,72327,72294],{"class":693},[679,72329,72330],{"class":880},"removeEventListener",[679,72332,745],{"class":693},[679,72334,72301],{"class":689},[679,72336,72304],{"class":693},[679,72338,72339],{"class":681,"line":2154},[679,72340,56103],{"class":693},[679,72342,72343],{"class":681,"line":2159},[679,72344,889],{"emptyLinePlaceholder":797},[679,72346,72347,72349],{"class":681,"line":2164},[679,72348,44767],{"class":685},[679,72350,72351],{"class":693}," { x, y };\n",[679,72353,72354],{"class":681,"line":3134},[679,72355,996],{"class":693},[651,72357,72358],{},"If you want to consume this logic in a component you can call the function, destructure the return object and then return the x and y coordinates to your template.",[669,72360,72362],{"className":4496,"code":72361,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>Use Mouse Demo\u003C/h1>\n \u003Cp>x: {{ x }} | y: {{ y }}\u003C/p>\n\u003C/template>\n\n\u003Cscript>\n import { useMousePosition } from \"./use/useMousePosition\";\n\n export default {\n setup() {\n const { x, y } = useMousePosition();\n return { x, y };\n }\n };\n\u003C/script>\n",[676,72363,72364,72372,72385,72398,72406,72410,72418,72432,72436,72444,72450,72472,72478,72482,72486],{"__ignoreMap":674},[679,72365,72366,72368,72370],{"class":681,"line":682},[679,72367,4505],{"class":693},[679,72369,45982],{"class":4508},[679,72371,4519],{"class":693},[679,72373,72374,72376,72378,72381,72383],{"class":681,"line":790},[679,72375,11738],{"class":693},[679,72377,11859],{"class":4508},[679,72379,72380],{"class":693},">Use Mouse Demo\u003C/",[679,72382,11859],{"class":4508},[679,72384,4519],{"class":693},[679,72386,72387,72389,72391,72394,72396],{"class":681,"line":892},[679,72388,11738],{"class":693},[679,72390,651],{"class":4508},[679,72392,72393],{"class":693},">x: {{ x }} | y: {{ y }}\u003C/",[679,72395,651],{"class":4508},[679,72397,4519],{"class":693},[679,72399,72400,72402,72404],{"class":681,"line":901},[679,72401,4586],{"class":693},[679,72403,45982],{"class":4508},[679,72405,4519],{"class":693},[679,72407,72408],{"class":681,"line":909},[679,72409,889],{"emptyLinePlaceholder":797},[679,72411,72412,72414,72416],{"class":681,"line":918},[679,72413,4505],{"class":693},[679,72415,47668],{"class":4508},[679,72417,4519],{"class":693},[679,72419,72420,72422,72425,72427,72430],{"class":681,"line":935},[679,72421,63707],{"class":685},[679,72423,72424],{"class":693}," { useMousePosition } ",[679,72426,28887],{"class":685},[679,72428,72429],{"class":689}," \"./use/useMousePosition\"",[679,72431,1186],{"class":693},[679,72433,72434],{"class":681,"line":944},[679,72435,889],{"emptyLinePlaceholder":797},[679,72437,72438,72440,72442],{"class":681,"line":959},[679,72439,63260],{"class":685},[679,72441,50460],{"class":685},[679,72443,884],{"class":693},[679,72445,72446,72448],{"class":681,"line":964},[679,72447,70756],{"class":880},[679,72449,2667],{"class":693},[679,72451,72452,72454,72456,72459,72461,72464,72466,72468,72470],{"class":681,"line":977},[679,72453,47094],{"class":685},[679,72455,2998],{"class":693},[679,72457,72458],{"class":931},"x",[679,72460,2797],{"class":693},[679,72462,72463],{"class":931},"y",[679,72465,55483],{"class":693},[679,72467,686],{"class":685},[679,72469,72196],{"class":880},[679,72471,9317],{"class":693},[679,72473,72474,72476],{"class":681,"line":982},[679,72475,63468],{"class":685},[679,72477,72351],{"class":693},[679,72479,72480],{"class":681,"line":988},[679,72481,985],{"class":693},[679,72483,72484],{"class":681,"line":993},[679,72485,53075],{"class":693},[679,72487,72488,72490,72492],{"class":681,"line":2129},[679,72489,4586],{"class":693},[679,72491,47668],{"class":4508},[679,72493,4519],{"class":693},[651,72495,72496],{},"This will work but as you took a look at this function you decided to refactor x and y into a position object:",[669,72498,72500],{"className":64365,"code":72499,"language":64367,"meta":674,"style":674},"import { ref, onMounted, onUnmounted } from \"vue\";\n\nexport function useMousePosition() {\n const pos = {\n x: 0,\n y: 0\n };\n\n function update(e) {\n pos.x = e.pageX;\n pos.y = e.pageY;\n }\n\n // ...\n}\n",[676,72501,72502,72514,72518,72528,72539,72548,72555,72559,72563,72575,72584,72593,72597,72601,72606],{"__ignoreMap":674},[679,72503,72504,72506,72508,72510,72512],{"class":681,"line":682},[679,72505,1999],{"class":685},[679,72507,72177],{"class":693},[679,72509,28887],{"class":685},[679,72511,71011],{"class":689},[679,72513,1186],{"class":693},[679,72515,72516],{"class":681,"line":790},[679,72517,889],{"emptyLinePlaceholder":797},[679,72519,72520,72522,72524,72526],{"class":681,"line":892},[679,72521,29245],{"class":685},[679,72523,21700],{"class":685},[679,72525,72196],{"class":880},[679,72527,2667],{"class":693},[679,72529,72530,72532,72535,72537],{"class":681,"line":901},[679,72531,46903],{"class":685},[679,72533,72534],{"class":931}," pos",[679,72536,6883],{"class":685},[679,72538,884],{"class":693},[679,72540,72541,72544,72546],{"class":681,"line":909},[679,72542,72543],{"class":693}," x: ",[679,72545,1060],{"class":931},[679,72547,12083],{"class":693},[679,72549,72550,72553],{"class":681,"line":918},[679,72551,72552],{"class":693}," y: ",[679,72554,6562],{"class":931},[679,72556,72557],{"class":681,"line":935},[679,72558,53075],{"class":693},[679,72560,72561],{"class":681,"line":944},[679,72562,889],{"emptyLinePlaceholder":797},[679,72564,72565,72567,72569,72571,72573],{"class":681,"line":959},[679,72566,72241],{"class":685},[679,72568,72244],{"class":880},[679,72570,745],{"class":693},[679,72572,9400],{"class":2099},[679,72574,4390],{"class":693},[679,72576,72577,72580,72582],{"class":681,"line":964},[679,72578,72579],{"class":693}," pos.x ",[679,72581,686],{"class":685},[679,72583,72260],{"class":693},[679,72585,72586,72589,72591],{"class":681,"line":977},[679,72587,72588],{"class":693}," pos.y ",[679,72590,686],{"class":685},[679,72592,72270],{"class":693},[679,72594,72595],{"class":681,"line":982},[679,72596,21405],{"class":693},[679,72598,72599],{"class":681,"line":988},[679,72600,889],{"emptyLinePlaceholder":797},[679,72602,72603],{"class":681,"line":993},[679,72604,72605],{"class":1400}," // ...\n",[679,72607,72608],{"class":681,"line":2129},[679,72609,996],{"class":693},[651,72611,72612],{},"The problem with this approach is that the consumer of the composition function must keep the reference to the returned object at all times in order to retain reactivity. This means that the object cannot be destructured or spread:",[669,72614,72616],{"className":64365,"code":72615,"language":64367,"meta":674,"style":674},"// consuming component\nexport default {\n setup() {\n // reactivity lost!\n const { x, y } = useMousePosition();\n return {\n x,\n y\n };\n\n // reactivity lost!\n return {\n ...useMousePosition()\n };\n\n // this is the only way to retain reactivity.\n // you must return `pos` as-is and reference x and y as `pos.x` and `pos.y`\n // in the template.\n return {\n pos: useMousePosition()\n };\n }\n};\n",[676,72617,72618,72623,72631,72637,72642,72662,72668,72673,72678,72682,72686,72690,72696,72705,72709,72713,72718,72723,72728,72734,72743,72747,72751],{"__ignoreMap":674},[679,72619,72620],{"class":681,"line":682},[679,72621,72622],{"class":1400},"// consuming component\n",[679,72624,72625,72627,72629],{"class":681,"line":790},[679,72626,29245],{"class":685},[679,72628,50460],{"class":685},[679,72630,884],{"class":693},[679,72632,72633,72635],{"class":681,"line":892},[679,72634,70409],{"class":880},[679,72636,2667],{"class":693},[679,72638,72639],{"class":681,"line":901},[679,72640,72641],{"class":1400}," // reactivity lost!\n",[679,72643,72644,72646,72648,72650,72652,72654,72656,72658,72660],{"class":681,"line":909},[679,72645,54021],{"class":685},[679,72647,2998],{"class":693},[679,72649,72458],{"class":931},[679,72651,2797],{"class":693},[679,72653,72463],{"class":931},[679,72655,55483],{"class":693},[679,72657,686],{"class":685},[679,72659,72196],{"class":880},[679,72661,9317],{"class":693},[679,72663,72664,72666],{"class":681,"line":918},[679,72665,21478],{"class":685},[679,72667,884],{"class":693},[679,72669,72670],{"class":681,"line":935},[679,72671,72672],{"class":693}," x,\n",[679,72674,72675],{"class":681,"line":944},[679,72676,72677],{"class":693}," y\n",[679,72679,72680],{"class":681,"line":959},[679,72681,50000],{"class":693},[679,72683,72684],{"class":681,"line":964},[679,72685,889],{"emptyLinePlaceholder":797},[679,72687,72688],{"class":681,"line":977},[679,72689,72641],{"class":1400},[679,72691,72692,72694],{"class":681,"line":982},[679,72693,21478],{"class":685},[679,72695,884],{"class":693},[679,72697,72698,72700,72703],{"class":681,"line":988},[679,72699,67238],{"class":685},[679,72701,72702],{"class":880},"useMousePosition",[679,72704,17545],{"class":693},[679,72706,72707],{"class":681,"line":993},[679,72708,50000],{"class":693},[679,72710,72711],{"class":681,"line":2129},[679,72712,889],{"emptyLinePlaceholder":797},[679,72714,72715],{"class":681,"line":2140},[679,72716,72717],{"class":1400}," // this is the only way to retain reactivity.\n",[679,72719,72720],{"class":681,"line":2145},[679,72721,72722],{"class":1400}," // you must return `pos` as-is and reference x and y as `pos.x` and `pos.y`\n",[679,72724,72725],{"class":681,"line":2154},[679,72726,72727],{"class":1400}," // in the template.\n",[679,72729,72730,72732],{"class":681,"line":2159},[679,72731,21478],{"class":685},[679,72733,884],{"class":693},[679,72735,72736,72739,72741],{"class":681,"line":2164},[679,72737,72738],{"class":693}," pos: ",[679,72740,72702],{"class":880},[679,72742,17545],{"class":693},[679,72744,72745],{"class":681,"line":3134},[679,72746,50000],{"class":693},[679,72748,72749],{"class":681,"line":3139},[679,72750,21405],{"class":693},[679,72752,72753],{"class":681,"line":3144},[679,72754,44055],{"class":693},[651,72756,72757,72758,72761],{},"This doesn't mean that you can't use reactive though. There is a ",[676,72759,72760],{},"toRefs()"," method that will convert a reactive object to a plain object, where each property on the resulting object is a ref pointing to the corresponding property in the original object.",[669,72763,72765],{"className":64365,"code":72764,"language":64367,"meta":674,"style":674},"function useMousePosition() {\n const pos = reactive({\n x: 0,\n y: 0\n });\n\n // ...\n return toRefs(pos);\n}\n\n// x & y are now refs!\nconst { x, y } = useMousePosition();\n",[676,72766,72767,72775,72787,72795,72801,72805,72809,72813,72823,72827,72831,72836],{"__ignoreMap":674},[679,72768,72769,72771,72773],{"class":681,"line":682},[679,72770,55109],{"class":685},[679,72772,72196],{"class":880},[679,72774,2667],{"class":693},[679,72776,72777,72779,72781,72783,72785],{"class":681,"line":790},[679,72778,46903],{"class":685},[679,72780,72534],{"class":931},[679,72782,6883],{"class":685},[679,72784,70423],{"class":880},[679,72786,21218],{"class":693},[679,72788,72789,72791,72793],{"class":681,"line":892},[679,72790,72543],{"class":693},[679,72792,1060],{"class":931},[679,72794,12083],{"class":693},[679,72796,72797,72799],{"class":681,"line":901},[679,72798,72552],{"class":693},[679,72800,6562],{"class":931},[679,72802,72803],{"class":681,"line":909},[679,72804,56103],{"class":693},[679,72806,72807],{"class":681,"line":918},[679,72808,889],{"emptyLinePlaceholder":797},[679,72810,72811],{"class":681,"line":935},[679,72812,72605],{"class":1400},[679,72814,72815,72817,72820],{"class":681,"line":944},[679,72816,44767],{"class":685},[679,72818,72819],{"class":880}," toRefs",[679,72821,72822],{"class":693},"(pos);\n",[679,72824,72825],{"class":681,"line":959},[679,72826,996],{"class":693},[679,72828,72829],{"class":681,"line":964},[679,72830,889],{"emptyLinePlaceholder":797},[679,72832,72833],{"class":681,"line":977},[679,72834,72835],{"class":1400},"// x & y are now refs!\n",[679,72837,72838,72840,72842,72844,72846,72848,72850,72852,72854],{"class":681,"line":982},[679,72839,45172],{"class":685},[679,72841,2998],{"class":693},[679,72843,72458],{"class":931},[679,72845,2797],{"class":693},[679,72847,72463],{"class":931},[679,72849,55483],{"class":693},[679,72851,686],{"class":685},[679,72853,72196],{"class":880},[679,72855,9317],{"class":693},[651,72857,72858],{},"As you can see there are some things to consider when creating composition functions. As long as you understand how your functions might be consumed you should be ok.",[4542,72860,72861],{"id":48139},"Summary",[651,72863,72864,72865,72867,72868,72870],{},"When I first started creating components using the Composition API I was confused when to reach for ",[676,72866,70948],{}," and when to favor ",[676,72869,71550],{},". I still might be doing it wrong but until someone tells me I am this is the approach I am going to take. I hope I helped clear up some questions and I would love to hear your feedback below. Thanks for reading, and as always friends...",[651,72872,41105,72873,41109],{},[41107,72874],{},[786,72876,72877],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":72879},[72880,72881,72887,72888],{"id":70578,"depth":790,"text":70579},{"id":70690,"depth":790,"text":70691,"children":72882},[72883,72884,72885,72886],{"id":71127,"depth":892,"text":71128},{"id":71481,"depth":892,"text":71482},{"id":71537,"depth":892,"text":71538},{"id":71788,"depth":892,"text":71789},{"id":72147,"depth":790,"text":72148},{"id":48139,"depth":790,"text":72861},"One question I keep hearing over and over is what method should I use to declare reactive data in the Vue 3 Composition API? In this article, I explain what ref() and reactive() are and try to answer that question.",{"slug":72891,"date":72892,"published":797,"author":798,"tags":72893,"cover":72894,"video":72895},"vue3-ref-vs-reactive","2020-02-12T16:27:46.472Z",[44169],"./vue3-ref-vs-reactive-cover.png","https://www.youtube.com/embed/cRwG96MOHp0",{"title":441,"description":72889},"blog/2020/02/12/vue3-ref-vs-reactive","KVTSHr0WTnz2EGZ6MJdG-lD9EVkEqyGUFdtHWW8IIUE",{"id":72900,"title":438,"body":72901,"description":73551,"extension":793,"meta":73552,"navigation":797,"path":439,"seo":73560,"stem":73561,"__hash__":73562},"content/blog/2020/03/09/spring-unit-vs-integration-test.md",{"type":648,"value":72902,"toc":73543},[72903,72907,72910,72913,72917,72928,72935,72995,73014,73018,73024,73040,73043,73150,73159,73165,73169,73174,73289,73301,73305,73311,73314,73339,73342,73526,73532,73534,73537,73540],[4542,72904,72906],{"id":72905},"demystifying-unit-tests-and-integration-tests-in-spring-boot","Demystifying Unit Tests and Integration Tests in Spring Boot",[651,72908,72909],{},"Hello, friends! In this article, we're going to dive into the world of testing in Spring Boot, specifically looking at unit tests and integration tests. Is there a difference between the two? Should we care? Let's find out!",[651,72911,72912],{},"This topic was sparked by a tweet I sent out recently, pleading with people to stop calling certain tests \"unit tests\" when they involve Spring components such as Dispatcher Servlet and the request/response lifecycle. To me, these are more like integration tests, albeit isolated ones. So, our goal today is to create a simple REST controller, perform a proper unit test, and then build upon that to create an integration test as well.",[4542,72914,72916],{"id":72915},"creating-a-simple-rest-controller","Creating a Simple REST Controller",[651,72918,72919,72920,72923,72924,72927],{},"Before we get started with writing tests, let's create a very simple Spring Boot application with a REST controller. Head over to the ",[812,72921,7117],{"href":7115,"rel":72922},[816]," and create a project. For this example, all we need is the ",[2939,72925,72926],{},"Web"," dependency.",[651,72929,72930,72931,72934],{},"Let's create a basic ",[676,72932,72933],{},"HelloController"," class:",[669,72936,72938],{"className":4107,"code":72937,"language":4109,"meta":674,"style":674},"public class HelloController {\n\n public String hello(String name) {\n return String.format(\"Hello, %s\", name);\n }\n}\n",[676,72939,72940,72951,72955,72970,72987,72991],{"__ignoreMap":674},[679,72941,72942,72944,72946,72949],{"class":681,"line":682},[679,72943,6073],{"class":685},[679,72945,4512],{"class":685},[679,72947,72948],{"class":880}," HelloController",[679,72950,884],{"class":693},[679,72952,72953],{"class":681,"line":790},[679,72954,889],{"emptyLinePlaceholder":797},[679,72956,72957,72959,72961,72964,72966,72968],{"class":681,"line":892},[679,72958,6089],{"class":685},[679,72960,9289],{"class":693},[679,72962,72963],{"class":880},"hello",[679,72965,11400],{"class":693},[679,72967,16334],{"class":2099},[679,72969,4390],{"class":693},[679,72971,72972,72974,72977,72979,72981,72984],{"class":681,"line":901},[679,72973,9444],{"class":685},[679,72975,72976],{"class":693}," String.",[679,72978,17581],{"class":880},[679,72980,745],{"class":693},[679,72982,72983],{"class":689},"\"Hello, %s\"",[679,72985,72986],{"class":693},", name);\n",[679,72988,72989],{"class":681,"line":909},[679,72990,985],{"class":693},[679,72992,72993],{"class":681,"line":918},[679,72994,996],{"class":693},[651,72996,72997,72998,73001,73002,73004,73005,73008,73009,51393,73011,73013],{},"This controller has one method, ",[676,72999,73000],{},"hello()",", which takes a string argument ",[676,73003,16334],{}," and returns a formatted string: ",[676,73006,73007],{},"\"Hello, \" + name",". Notice that we haven't added any Spring annotations yet, such as ",[676,73010,12329],{},[676,73012,22845],{},". We'll get to that later.",[4542,73015,73017],{"id":73016},"writing-a-unit-test","Writing a Unit Test",[651,73019,73020,73021,73023],{},"To create a unit test for our ",[676,73022,72933],{},", follow these steps:",[27665,73025,73026,73031,73037],{},[5332,73027,73028,73029,664],{},"Create a test class for ",[676,73030,72933],{},[5332,73032,73033,73034,73036],{},"Create an instance of ",[676,73035,72933],{}," and invoke its methods.",[5332,73038,73039],{},"Use assertions to check the output of the methods.",[651,73041,73042],{},"Here's a simple example:",[669,73044,73046],{"className":4107,"code":73045,"language":4109,"meta":674,"style":674},"import org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass HelloControllerTest {\n\n @Test\n void hello() {\n HelloController controller = new HelloController();\n String response = controller.hello(\"world\");\n assertEquals(\"Hello, world\", response);\n }\n}\n",[676,73047,73048,73055,73064,73068,73077,73081,73088,73097,73110,73129,73142,73146],{"__ignoreMap":674},[679,73049,73050,73052],{"class":681,"line":682},[679,73051,1999],{"class":685},[679,73053,73054],{"class":693}," org.junit.jupiter.api.Test;\n",[679,73056,73057,73059,73061],{"class":681,"line":790},[679,73058,1999],{"class":685},[679,73060,6092],{"class":685},[679,73062,73063],{"class":693}," org.junit.jupiter.api.Assertions.assertEquals;\n",[679,73065,73066],{"class":681,"line":892},[679,73067,889],{"emptyLinePlaceholder":797},[679,73069,73070,73072,73075],{"class":681,"line":901},[679,73071,877],{"class":685},[679,73073,73074],{"class":880}," HelloControllerTest",[679,73076,884],{"class":693},[679,73078,73079],{"class":681,"line":909},[679,73080,889],{"emptyLinePlaceholder":797},[679,73082,73083,73085],{"class":681,"line":918},[679,73084,6872],{"class":693},[679,73086,73087],{"class":685},"Test\n",[679,73089,73090,73092,73095],{"class":681,"line":935},[679,73091,3314],{"class":685},[679,73093,73094],{"class":880}," hello",[679,73096,2667],{"class":693},[679,73098,73099,73102,73104,73106,73108],{"class":681,"line":944},[679,73100,73101],{"class":693}," HelloController controller ",[679,73103,686],{"class":685},[679,73105,2054],{"class":685},[679,73107,72948],{"class":880},[679,73109,9317],{"class":693},[679,73111,73112,73115,73117,73120,73122,73124,73127],{"class":681,"line":959},[679,73113,73114],{"class":693}," String response ",[679,73116,686],{"class":685},[679,73118,73119],{"class":693}," controller.",[679,73121,72963],{"class":880},[679,73123,745],{"class":693},[679,73125,73126],{"class":689},"\"world\"",[679,73128,1208],{"class":693},[679,73130,73131,73134,73136,73139],{"class":681,"line":964},[679,73132,73133],{"class":880}," assertEquals",[679,73135,745],{"class":693},[679,73137,73138],{"class":689},"\"Hello, world\"",[679,73140,73141],{"class":693},", response);\n",[679,73143,73144],{"class":681,"line":977},[679,73145,985],{"class":693},[679,73147,73148],{"class":681,"line":982},[679,73149,996],{"class":693},[651,73151,73152,73153,73155,73156,73158],{},"In this test, we're simply creating an instance of ",[676,73154,72933],{},", calling its ",[676,73157,73000],{}," method with the input \"world,\" and checking that the expected output is \"Hello, world.\" We don't involve any Spring components in this test, so this is a pure unit test.",[651,73160,73161,73162,73164],{},"The purpose of a unit test is to validate that each unit of the software performs as designed. A unit is the smallest testable part of any software. In our case, the smallest unit is the ",[676,73163,73000],{}," method in our controller. This test ensures that the method works as expected when given a particular input.",[4542,73166,73168],{"id":73167},"adding-spring-components","Adding Spring Components",[651,73170,73171,73172,2391],{},"Now that we have a working unit test, let's add some Spring components to our ",[676,73173,72933],{},[669,73175,73177],{"className":4107,"code":73176,"language":4109,"meta":674,"style":674},"import org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class HelloController {\n\n @GetMapping(\"/hello\")\n public String hello(@RequestParam(defaultValue = \"world\") String name) {\n return String.format(\"Hello, %s\", name);\n }\n}\n",[676,73178,73179,73186,73193,73199,73203,73209,73219,73223,73236,73267,73281,73285],{"__ignoreMap":674},[679,73180,73181,73183],{"class":681,"line":682},[679,73182,1999],{"class":685},[679,73184,73185],{"class":693}," org.springframework.web.bind.annotation.GetMapping;\n",[679,73187,73188,73190],{"class":681,"line":790},[679,73189,1999],{"class":685},[679,73191,73192],{"class":693}," org.springframework.web.bind.annotation.RequestParam;\n",[679,73194,73195,73197],{"class":681,"line":892},[679,73196,1999],{"class":685},[679,73198,12552],{"class":693},[679,73200,73201],{"class":681,"line":901},[679,73202,889],{"emptyLinePlaceholder":797},[679,73204,73205,73207],{"class":681,"line":909},[679,73206,4116],{"class":693},[679,73208,9212],{"class":685},[679,73210,73211,73213,73215,73217],{"class":681,"line":918},[679,73212,6073],{"class":685},[679,73214,4512],{"class":685},[679,73216,72948],{"class":880},[679,73218,884],{"class":693},[679,73220,73221],{"class":681,"line":935},[679,73222,889],{"emptyLinePlaceholder":797},[679,73224,73225,73227,73229,73231,73234],{"class":681,"line":944},[679,73226,6872],{"class":693},[679,73228,20852],{"class":685},[679,73230,745],{"class":693},[679,73232,73233],{"class":689},"\"/hello\"",[679,73235,1339],{"class":693},[679,73237,73238,73240,73242,73244,73247,73250,73252,73255,73257,73260,73263,73265],{"class":681,"line":959},[679,73239,6089],{"class":685},[679,73241,9289],{"class":693},[679,73243,72963],{"class":880},[679,73245,73246],{"class":693},"(@",[679,73248,73249],{"class":685},"RequestParam",[679,73251,745],{"class":693},[679,73253,73254],{"class":931},"defaultValue",[679,73256,6883],{"class":685},[679,73258,73259],{"class":689}," \"world\"",[679,73261,73262],{"class":693},") String ",[679,73264,16334],{"class":2099},[679,73266,4390],{"class":693},[679,73268,73269,73271,73273,73275,73277,73279],{"class":681,"line":964},[679,73270,9444],{"class":685},[679,73272,72976],{"class":693},[679,73274,17581],{"class":880},[679,73276,745],{"class":693},[679,73278,72983],{"class":689},[679,73280,72986],{"class":693},[679,73282,73283],{"class":681,"line":977},[679,73284,985],{"class":693},[679,73286,73287],{"class":681,"line":982},[679,73288,996],{"class":693},[651,73290,73291,73292,2797,73294,48406,73297,73300],{},"We've added the ",[676,73293,12329],{},[676,73295,73296],{},"@GetMapping",[676,73298,73299],{},"@RequestParam"," annotations. Now our controller is ready to accept HTTP GET requests at the \"/hello\" endpoint and respond with the appropriate message.",[4542,73302,73304],{"id":73303},"writing-an-integration-test","Writing an Integration Test",[651,73306,73307,73308,73310],{},"With the Spring components in place, it's time to write an integration test for our ",[676,73309,72933],{},". Unlike a unit test, an integration test involves the Spring framework and tests how the various components work together.",[651,73312,73313],{},"To create an integration test, follow these steps:",[27665,73315,73316,73326,73333],{},[5332,73317,73028,73318,73320,73321,23212,73323,664],{},[676,73319,72933],{}," and annotate it with ",[676,73322,24290],{},[676,73324,73325],{},"@ExtendWith(SpringExtension.class)",[5332,73327,73328,73329,73332],{},"Autowire a ",[676,73330,73331],{},"MockMvc"," instance.",[5332,73334,73335,73336,73338],{},"Perform HTTP requests using the ",[676,73337,73331],{}," instance and check the expected output.",[651,73340,73341],{},"Here's an example integration test:",[669,73343,73345],{"className":4107,"code":73344,"language":4109,"meta":674,"style":674},"import org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\nimport org.springframework.test.web.servlet.MockMvc;\n\n@WebMvcTest(HelloController.class)\n@ExtendWith(SpringExtension.class)\nclass HelloIntegrationTest {\n\n @Autowired\n private MockMvc mockMvc;\n\n @Test\n void hello() throws Exception {\n mockMvc.perform(get(\"/hello\"))\n .andExpect(status().isOk())\n .andExpect(content().string(\"Hello, world\"));\n }\n}\n",[676,73346,73347,73353,73360,73367,73374,73381,73388,73392,73402,73412,73421,73425,73431,73438,73442,73448,73460,73478,73497,73518,73522],{"__ignoreMap":674},[679,73348,73349,73351],{"class":681,"line":682},[679,73350,1999],{"class":685},[679,73352,73054],{"class":693},[679,73354,73355,73357],{"class":681,"line":790},[679,73356,1999],{"class":685},[679,73358,73359],{"class":693}," org.junit.jupiter.api.extension.ExtendWith;\n",[679,73361,73362,73364],{"class":681,"line":892},[679,73363,1999],{"class":685},[679,73365,73366],{"class":693}," org.springframework.beans.factory.annotation.Autowired;\n",[679,73368,73369,73371],{"class":681,"line":901},[679,73370,1999],{"class":685},[679,73372,73373],{"class":693}," org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;\n",[679,73375,73376,73378],{"class":681,"line":909},[679,73377,1999],{"class":685},[679,73379,73380],{"class":693}," org.springframework.test.context.junit.jupiter.SpringExtension;\n",[679,73382,73383,73385],{"class":681,"line":918},[679,73384,1999],{"class":685},[679,73386,73387],{"class":693}," org.springframework.test.web.servlet.MockMvc;\n",[679,73389,73390],{"class":681,"line":935},[679,73391,889],{"emptyLinePlaceholder":797},[679,73393,73394,73396,73399],{"class":681,"line":944},[679,73395,4116],{"class":693},[679,73397,73398],{"class":685},"WebMvcTest",[679,73400,73401],{"class":693},"(HelloController.class)\n",[679,73403,73404,73406,73409],{"class":681,"line":959},[679,73405,4116],{"class":693},[679,73407,73408],{"class":685},"ExtendWith",[679,73410,73411],{"class":693},"(SpringExtension.class)\n",[679,73413,73414,73416,73419],{"class":681,"line":964},[679,73415,877],{"class":685},[679,73417,73418],{"class":880}," HelloIntegrationTest",[679,73420,884],{"class":693},[679,73422,73423],{"class":681,"line":977},[679,73424,889],{"emptyLinePlaceholder":797},[679,73426,73427,73429],{"class":681,"line":982},[679,73428,6872],{"class":693},[679,73430,9257],{"class":685},[679,73432,73433,73435],{"class":681,"line":988},[679,73434,9232],{"class":685},[679,73436,73437],{"class":693}," MockMvc mockMvc;\n",[679,73439,73440],{"class":681,"line":993},[679,73441,889],{"emptyLinePlaceholder":797},[679,73443,73444,73446],{"class":681,"line":2129},[679,73445,6872],{"class":693},[679,73447,73087],{"class":685},[679,73449,73450,73452,73454,73456,73458],{"class":681,"line":2140},[679,73451,3314],{"class":685},[679,73453,73094],{"class":880},[679,73455,6700],{"class":693},[679,73457,9580],{"class":685},[679,73459,10466],{"class":693},[679,73461,73462,73465,73468,73470,73472,73474,73476],{"class":681,"line":2145},[679,73463,73464],{"class":693}," mockMvc.",[679,73466,73467],{"class":880},"perform",[679,73469,745],{"class":693},[679,73471,7626],{"class":880},[679,73473,745],{"class":693},[679,73475,73233],{"class":689},[679,73477,40143],{"class":693},[679,73479,73480,73483,73486,73488,73490,73492,73495],{"class":681,"line":2154},[679,73481,73482],{"class":693}," .",[679,73484,73485],{"class":880},"andExpect",[679,73487,745],{"class":693},[679,73489,24114],{"class":880},[679,73491,10541],{"class":693},[679,73493,73494],{"class":880},"isOk",[679,73496,40172],{"class":693},[679,73498,73499,73501,73503,73505,73507,73509,73512,73514,73516],{"class":681,"line":2159},[679,73500,73482],{"class":693},[679,73502,73485],{"class":880},[679,73504,745],{"class":693},[679,73506,47833],{"class":880},[679,73508,10541],{"class":693},[679,73510,73511],{"class":880},"string",[679,73513,745],{"class":693},[679,73515,73138],{"class":689},[679,73517,1669],{"class":693},[679,73519,73520],{"class":681,"line":2164},[679,73521,985],{"class":693},[679,73523,73524],{"class":681,"line":3134},[679,73525,996],{"class":693},[651,73527,73528,73529,73531],{},"In this test, we use the ",[676,73530,73331],{}," instance to perform an HTTP GET request to the \"/hello\" endpoint and check that the response is \"Hello, world.\" Since this test involves Spring components, it's considered an integration test.",[4542,73533,9042],{"id":9041},[651,73535,73536],{},"In this article, we've covered the differences between unit tests and integration tests in a Spring Boot application. Unit tests are focused on testing the smallest units of functionality, while integration tests ensure that various components work together as expected. Both types of tests are valuable in ensuring the quality of your application, and it's important to know when to use each type.",[651,73538,73539],{},"Remember, it's not about what we call these tests, but rather understanding their purpose and how they fit into your testing strategy. Happy coding, and stay curious!",[786,73541,73542],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":73544},[73545,73546,73547,73548,73549,73550],{"id":72905,"depth":790,"text":72906},{"id":72915,"depth":790,"text":72916},{"id":73016,"depth":790,"text":73017},{"id":73167,"depth":790,"text":73168},{"id":73303,"depth":790,"text":73304},{"id":9041,"depth":790,"text":9042},"In this article, we're going to dive into the world of testing in Spring Boot, specifically looking at unit tests and integration tests.",{"slug":73553,"date":73554,"published":797,"author":798,"tags":73555,"cover":73556,"video":73557,"github":73558,"keywords":73559},"spring-unit-vs-integration-test","2020-03-09T10:00:00.000Z",[7077],"./unit-vs-int.jpeg","https://www.youtube.com/embed/pNiRNRgi5Ws","https://github.com/danvega/unit-vs-int","Java, Spring Boot, Spring Boot Testing, Unit Testing, Integration Testing",{"title":438,"description":73551},"blog/2020/03/09/spring-unit-vs-integration-test","Vo-A0FnUdeDmI8mUiQI4ye_-wiLZU7jP2pxXIt3YVxY",{"id":73564,"title":435,"body":73565,"description":74088,"extension":793,"meta":74089,"navigation":797,"path":436,"seo":74094,"stem":74095,"__hash__":74096},"content/blog/2020/05/16/website-redesign-lessons-learned.md",{"type":648,"value":73566,"toc":74077},[73567,73570,73574,73594,73597,73604,73609,73613,73616,73619,73622,73625,73636,73639,73642,73645,73648,73656,73660,73667,73759,73762,73767,73774,73834,73837,73930,73936,73985,73988,73991,73994,73997,74002,74005,74008,74012,74020,74034,74037,74042,74045,74054,74061,74063,74074],[651,73568,73569],{},"A couple of months ago I decided it was time to update my website. I normally get this feeling every couple of years but this time it was different. I liked the look and feel but there were a list of things that I wanted to improve on.",[4542,73571,73573],{"id":73572},"areas-of-improvement","Areas of improvement",[5316,73575,73576,73579,73582,73585,73588,73591],{},[5332,73577,73578],{},"Design",[5332,73580,73581],{},"Removing CSS frameworks",[5332,73583,73584],{},"Home Page Layout",[5332,73586,73587],{},"Dark Mode",[5332,73589,73590],{},"Responsive Design",[5332,73592,73593],{},"Lighthouse Scores",[5909,73595,73578],{"id":73596},"design",[651,73598,73599,73600,73603],{},"I am not a designer but I like to wear this hat from time to time and pretend I am one. I have gravitated towards ",[812,73601,65982],{"href":65988,"rel":73602},[816]," and I have been using it for a lot of my projects lately. I find it's really easy to use and it allows me prototype my application quickly. Once I have something I like its really easy for me to grab the colors, fonts and assets and build out the design in HTML & CSS. Below are some artboards from a couple of months ago when I decided to start redesigning the site.",[651,73605,73606],{},[660,73607],{"alt":65982,"src":73608},"/images/blog/2020/05/16/danvega_dev_adobexd.png",[5259,73610,73612],{"id":73611},"lesson-learned","Lesson Learned",[651,73614,73615],{},"It might seem like extra work up front to sit down and come up with a design but it pays off in the long run. When I know what I need to build I spend so much less time in development. Looking at a design I know what content is needed so I start with my HTML structure. With my content in place with the semantic structure I use CSS to make my look and feel match my design.",[5909,73617,73581],{"id":73618},"removing-css-frameworks",[651,73620,73621],{},"The previous version of my website used a CSS framework called Bulma. I need to make something clear and say that I have absolutely nothing bad to say about Bulma. If I had to select a CSS framework not named Tailwind CSS Bulma would be my framework of choice. I didn't know Bluma when I started using it and that caused a lot of my problems. If you combine that with a lack of CSS foundational knowledge I had a pretty messy codebase.",[651,73623,73624],{},"Now that I understand layouts in CSS and how to accomplish different designs I find component-based frameworks less useful. In the past few years my frontend skills have gotten better and I thought this was a great time to put them to use. My redesign makes use of a variety of tools that make frontend development a lot of fun",[5316,73626,73627,73630,73632,73634],{},[5332,73628,73629],{},"CSS Variables",[5332,73631,67793],{},[5332,73633,67788],{},[5332,73635,73590],{},[5259,73637,73612],{"id":73638},"lesson-learned-1",[651,73640,73641],{},"Learn the foundations of HTML & CSS. I have been building websites for 20+ years and a lot has changed in the last 5 years. I am constantly hearing the same from other developers who have been doing this for a long time. Learn modern techniques like Flexbox & CSS Grid, they are both awesome and a real joy to work with.",[5909,73643,73584],{"id":73644},"home-page-layout",[651,73646,73647],{},"My previous home page was really boring and I just wanted to add some style to it. I started messing around with some different designs and one thing that always stuck out to me with a total \"cool factor\" were layouts that had some type of wave or diagonal design. I came up with the wave on the home page and now it was time to implement it.",[651,73649,73650,73651,664],{},"I attempted to figure this out on my own and failed a few times. I was trying to use an image that I exported from XD as a background image and had issues with it not being responsive and not working well in dark mode. I finally did some digging around and came across this ",[812,73652,73655],{"href":73653,"rel":73654},"https://blog.prototypr.io/how-to-add-svg-waves-to-your-next-web-project-b720efe1c692",[816],"great tutorial by Richard Zimerman",[5259,73657,73659],{"id":73658},"lessons-learned","Lessons Learned",[651,73661,73662,73663,73666],{},"The trick here was using an SVG and setting it to ",[676,73664,73665],{},"display:block",". I am still pretty new to using SVG images but I am finding them really useful. In this case I combined CSS custom properties with an SVG and now I have the header for the home page that looks nice, is responsive and looks good in dark mode!",[669,73668,73670],{"className":66165,"code":73669,"language":66167,"meta":674,"style":674},".wave-container {\n position: relative;\n background: var(--home-header-background);\n color: #4a4a4a;\n overflow: hidden;\n}\n.wave-container > svg {\n display: block;\n}\n",[676,73671,73672,73679,73689,73706,73718,73728,73732,73744,73755],{"__ignoreMap":674},[679,73673,73674,73677],{"class":681,"line":682},[679,73675,73676],{"class":880},".wave-container",[679,73678,884],{"class":693},[679,73680,73681,73683,73685,73687],{"class":681,"line":790},[679,73682,66654],{"class":931},[679,73684,4282],{"class":693},[679,73686,66659],{"class":931},[679,73688,1186],{"class":693},[679,73690,73691,73694,73696,73699,73701,73704],{"class":681,"line":892},[679,73692,73693],{"class":931}," background",[679,73695,4282],{"class":693},[679,73697,73698],{"class":931},"var",[679,73700,745],{"class":693},[679,73702,73703],{"class":2099},"--home-header-background",[679,73705,1208],{"class":693},[679,73707,73708,73711,73713,73716],{"class":681,"line":901},[679,73709,73710],{"class":931}," color",[679,73712,4282],{"class":693},[679,73714,73715],{"class":931},"#4a4a4a",[679,73717,1186],{"class":693},[679,73719,73720,73722,73724,73726],{"class":681,"line":909},[679,73721,66666],{"class":931},[679,73723,4282],{"class":693},[679,73725,66671],{"class":931},[679,73727,1186],{"class":693},[679,73729,73730],{"class":681,"line":918},[679,73731,996],{"class":693},[679,73733,73734,73736,73739,73742],{"class":681,"line":935},[679,73735,73676],{"class":880},[679,73737,73738],{"class":685}," >",[679,73740,73741],{"class":4508}," svg",[679,73743,884],{"class":693},[679,73745,73746,73748,73750,73753],{"class":681,"line":944},[679,73747,66577],{"class":931},[679,73749,4282],{"class":693},[679,73751,73752],{"class":931},"block",[679,73754,1186],{"class":693},[679,73756,73757],{"class":681,"line":959},[679,73758,996],{"class":693},[5909,73760,73587],{"id":73761},"dark-mode",[651,73763,73764],{},[660,73765],{"alt":73587,"src":73766},"/images/blog/2020/05/16/darkmode.png",[651,73768,73769,73770,73773],{},"I have always loved the idea of having a dark mode on my website. I don't know how much my readers were asking for it but this is my website and I wanted to add it 🤷♂️. I have used CSS Custom Properties (variables) before but maybe not at this scale. The way this works is by setting some custom properties on the ",[676,73771,73772],{},":root"," which will be your default light theme. For each property you create you will create a dark theme property as well.",[669,73775,73777],{"className":66165,"code":73776,"language":66167,"meta":674,"style":674},":root {\n --background: white;\n}\n\n[data-theme=\"dark\"] {\n --background: black;\n}\n",[676,73778,73779,73785,73796,73800,73804,73819,73830],{"__ignoreMap":674},[679,73780,73781,73783],{"class":681,"line":682},[679,73782,73772],{"class":880},[679,73784,884],{"class":693},[679,73786,73787,73790,73792,73794],{"class":681,"line":790},[679,73788,73789],{"class":2099}," --background",[679,73791,4282],{"class":693},[679,73793,49740],{"class":931},[679,73795,1186],{"class":693},[679,73797,73798],{"class":681,"line":892},[679,73799,996],{"class":693},[679,73801,73802],{"class":681,"line":901},[679,73803,889],{"emptyLinePlaceholder":797},[679,73805,73806,73808,73811,73813,73816],{"class":681,"line":909},[679,73807,65476],{"class":693},[679,73809,73810],{"class":880},"data-theme",[679,73812,686],{"class":685},[679,73814,73815],{"class":689},"\"dark\"",[679,73817,73818],{"class":693},"] {\n",[679,73820,73821,73823,73825,73828],{"class":681,"line":918},[679,73822,73789],{"class":2099},[679,73824,4282],{"class":693},[679,73826,73827],{"class":931},"black",[679,73829,1186],{"class":693},[679,73831,73832],{"class":681,"line":935},[679,73833,996],{"class":693},[651,73835,73836],{},"Now in your CSS instead of setting the background color to white or black you refer to the custom property",[669,73838,73840],{"className":66165,"code":73839,"language":66167,"meta":674,"style":674},"body {\n margin: 0;\n padding: 0;\n background-color: var(--background);\n border-top: 8px solid var(--bright-blue);\n font-family: \"Roboto Slab\", serif;\n}\n",[676,73841,73842,73848,73859,73869,73884,73909,73926],{"__ignoreMap":674},[679,73843,73844,73846],{"class":681,"line":682},[679,73845,3006],{"class":4508},[679,73847,884],{"class":693},[679,73849,73850,73853,73855,73857],{"class":681,"line":790},[679,73851,73852],{"class":931}," margin",[679,73854,4282],{"class":693},[679,73856,1060],{"class":931},[679,73858,1186],{"class":693},[679,73860,73861,73863,73865,73867],{"class":681,"line":892},[679,73862,69076],{"class":931},[679,73864,4282],{"class":693},[679,73866,1060],{"class":931},[679,73868,1186],{"class":693},[679,73870,73871,73873,73875,73877,73879,73882],{"class":681,"line":901},[679,73872,69089],{"class":931},[679,73874,4282],{"class":693},[679,73876,73698],{"class":931},[679,73878,745],{"class":693},[679,73880,73881],{"class":2099},"--background",[679,73883,1208],{"class":693},[679,73885,73886,73889,73891,73894,73896,73899,73902,73904,73907],{"class":681,"line":909},[679,73887,73888],{"class":931}," border-top",[679,73890,4282],{"class":693},[679,73892,73893],{"class":931},"8",[679,73895,11797],{"class":685},[679,73897,73898],{"class":931}," solid",[679,73900,73901],{"class":931}," var",[679,73903,745],{"class":693},[679,73905,73906],{"class":2099},"--bright-blue",[679,73908,1208],{"class":693},[679,73910,73911,73914,73916,73919,73921,73924],{"class":681,"line":918},[679,73912,73913],{"class":931}," font-family",[679,73915,4282],{"class":693},[679,73917,73918],{"class":689},"\"Roboto Slab\"",[679,73920,2797],{"class":693},[679,73922,73923],{"class":931},"serif",[679,73925,1186],{"class":693},[679,73927,73928],{"class":681,"line":935},[679,73929,996],{"class":693},[651,73931,73932,73933,73935],{},"Next, I setup some icons using Font Awesome and by default the moon is displayed. When you click on that icon you are enabling dark mode. A little custom JavaScript will set the ",[676,73934,73810],{}," to dark:",[669,73937,73939],{"className":64365,"code":73938,"language":64367,"meta":674,"style":674},"if (document.documentElement.getAttribute(\"data-theme\") === null) {\n document.documentElement.setAttribute(\"data-theme\", \"dark\");\n}\n",[676,73940,73941,73964,73981],{"__ignoreMap":674},[679,73942,73943,73945,73948,73951,73953,73956,73958,73960,73962],{"class":681,"line":682},[679,73944,1217],{"class":685},[679,73946,73947],{"class":693}," (document.documentElement.",[679,73949,73950],{"class":880},"getAttribute",[679,73952,745],{"class":693},[679,73954,73955],{"class":689},"\"data-theme\"",[679,73957,2378],{"class":693},[679,73959,51108],{"class":685},[679,73961,2307],{"class":931},[679,73963,4390],{"class":693},[679,73965,73966,73969,73971,73973,73975,73977,73979],{"class":681,"line":790},[679,73967,73968],{"class":693}," document.documentElement.",[679,73970,47216],{"class":880},[679,73972,745],{"class":693},[679,73974,73955],{"class":689},[679,73976,2797],{"class":693},[679,73978,73815],{"class":689},[679,73980,1208],{"class":693},[679,73982,73983],{"class":681,"line":892},[679,73984,996],{"class":693},[5259,73986,73659],{"id":73987},"lessons-learned-1",[651,73989,73990],{},"If you are going to implement something like dark mode do it right from the start. If you notice yourself defining colors anywhere that don't use a CSS property you probably need to think about doing so. This is something I need to go back and fix up. I feel like I have too many properties defined and can probably condense this down.",[5909,73992,73590],{"id":73993},"responsive-design",[651,73995,73996],{},"I know that both myself and my audience use my website on a variety of form factors so making sure my design was responsive was important. As you can tell by XD designs I didn't have a mobile-first approach and that caused me some issues along the way. It's important to always be thinking about how a layout or design element will scale up or down. I am really thankful for tools like Flexbox and CSS Grid which are really helpful in creating responsive designs.",[651,73998,73999],{},[660,74000],{"alt":73590,"src":74001},"/images/blog/2020/05/16/responsive-design.png",[5259,74003,73659],{"id":74004},"lessons-learned-2",[651,74006,74007],{},"When I design a layout in Adobe XD I need to make sure I have a plan for my responsive designs. The term mobile-first means that your layout starts out with a mobile layout and scales up. This ensures that the mobile design is not just an after thought. Take some time to look at the analytics and understand what devices your visitors are using.",[5909,74009,74011],{"id":74010},"lighthouse-audits","Lighthouse Audits",[651,74013,74014,74015,74019],{},"I knew that once I finished my site I wanted to take a look at the ",[812,74016,74011],{"href":74017,"rel":74018},"https://web.dev/measure/",[816]," and try to improve scores where I could. If you aren't aware the Google Developer Tools has a tab called Audits where you can check your site for:",[5316,74021,74022,74025,74028,74031],{},[5332,74023,74024],{},"Performance",[5332,74026,74027],{},"Accessibility",[5332,74029,74030],{},"Best Practices",[5332,74032,74033],{},"SEO",[651,74035,74036],{},"Right now the site is doing well on some page but still needs some work on others. My advice here is let these audits guide you to improve your site but do not obsess over them.",[651,74038,74039],{},[660,74040],{"alt":74011,"src":74041},"/images/blog/2020/05/16/lighthouse-audits.png",[5259,74043,73659],{"id":74044},"lessons-learned-3",[651,74046,74047,74048,74053],{},"The first big lesson I learned here was that pulling in the entire Font Awesome library for \u003C 10 icons was a big no no. I mean I knew this but I needed to find a way to resolve this and the audit gave me a big kick in the rear to do so. I was just going to grab the icons I needed but what happens if I want to use more in the future? There had to be a way to pull in just the icons I needed and thanks to the Gridsome documentation I came across a wonderful ",[812,74049,74052],{"href":74050,"rel":74051},"https://github.com/FortAwesome/vue-fontawesome",[816],"library from Fort Awesome"," for using Font Awesome in Vue.",[651,74055,74056,74057,74060],{},"I still have some work to do here but I am trying to take my own advice and not obsess over it. I think there are two big things that would really help me out. Right now on the home page all of the images for recent posts are pulling from GraphQL. There is a way to resize them but unlike when you use a ",[676,74058,74059],{},"\u003Cg-image>"," tag this isn't lazy loading the images and I need to figure out a way around this. The other problem is all of my ConvertKit forms are pulled in via JavaScript. I would like to just create these forms myself so I can make sure they are accessible and at the same time it would reduce some page weight.",[5909,74062,9042],{"id":9041},[651,74064,74065,74066,74070,74071,664],{},"I have had a lot of fun working on the redesign for my website and I hope it shows. If you have any questions about anything I did here you can check out the ",[812,74067,74069],{"href":53665,"rel":74068},[816],"source code"," for it here. If you would like me to explain anything in more detail please leave me a comment below or reach out to me on ",[812,74072,51474],{"href":44086,"rel":74073},[816],[786,74075,74076],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":74078},[74079],{"id":73572,"depth":790,"text":73573,"children":74080},[74081,74082,74083,74084,74085,74086,74087],{"id":73596,"depth":892,"text":73578},{"id":73618,"depth":892,"text":73581},{"id":73644,"depth":892,"text":73584},{"id":73761,"depth":892,"text":73587},{"id":73993,"depth":892,"text":73590},{"id":74010,"depth":892,"text":74011},{"id":9041,"depth":892,"text":9042},"I spent the last couple of months redesigning my website and I would like to share with you some of the lessons I learned",{"slug":74090,"date":74091,"published":797,"author":798,"tags":74092,"cover":74093},"website-redesign-lessons-learned","2020-05-16T17:24:14.760Z",[66167,44169,43847],"./website-redesign-lessons-learned-cover.png",{"title":435,"description":74088},"blog/2020/05/16/website-redesign-lessons-learned","pBzWyAoZAUHtJF4d6Am2utRpJa51thYJN8iRgibVEDY",{"id":74098,"title":432,"body":74099,"description":76522,"extension":793,"meta":76523,"navigation":797,"path":433,"seo":76527,"stem":76528,"__hash__":76529},"content/blog/2020/05/29/hello-deno.md",{"type":648,"value":74100,"toc":76502},[74101,74110,74112,74187,74191,74204,74213,74216,74225,74228,74239,74242,74261,74264,74285,74288,74363,74366,74409,74413,74420,74468,74474,75141,75145,75154,75157,75163,75167,75174,75192,75199,75246,75257,75295,75298,75301,75332,75335,75338,75345,75348,75352,75359,75458,75465,75594,75606,75609,75612,75619,75622,75660,75667,75670,75685,75690,75693,75701,75705,75708,75791,75794,75812,75818,75821,75824,75831,75904,75910,76027,76034,76040,76043,76046,76063,76103,76111,76134,76141,76144,76154,76163,76215,76221,76224,76342,76345,76348,76351,76354,76360,76410,76414,76419,76425,76428,76434,76437,76440,76491,76493,76496,76499],[651,74102,74103,74104,74109],{},"After two years of development, ",[812,74105,74108],{"href":74106,"rel":74107},"https://deno.land/v1",[816],"Deno 1.0"," was recently released and it has everyone talking. In this article, I want to give you a quick introduction to Deno, how to install it and how to create your first application. Finally, we will take a look at some of the features that make Deno an exciting project to pay attention to.",[4542,74111,72861],{"id":48139},[5316,74113,74114,74120,74126,74182],{},[5332,74115,74116],{},[812,74117,74119],{"href":74118},"#what-is-deno","What is Deno",[5332,74121,74122],{},[812,74123,74125],{"href":74124},"#installing-deno","Installing Deno",[5332,74127,74128,74132],{},[812,74129,74131],{"href":74130},"#deno-features","Deno Features",[5316,74133,74134,74140,74146,74152,74158,74164,74170,74176],{},[5332,74135,74136],{},[812,74137,74139],{"href":74138},"#secure-runtime","Secure Runtime",[5332,74141,74142],{},[812,74143,74145],{"href":74144},"#typescript-support","TypeScript Support",[5332,74147,74148],{},[812,74149,74151],{"href":74150},"#standard-library","Standard Library",[5332,74153,74154],{},[812,74155,74157],{"href":74156},"#built-in-testing-library","Built-in Testing Library",[5332,74159,74160],{},[812,74161,74163],{"href":74162},"#promises-async--await","Promises, Async & Await",[5332,74165,74166],{},[812,74167,74169],{"href":74168},"#browser-compatible-apis-fetch-window-object","Browser Compatible APIs (fetch, Window object)",[5332,74171,74172],{},[812,74173,74175],{"href":74174},"#ecmascript-modules-es-modules","ECMAScript Modules (ES Modules)",[5332,74177,74178],{},[812,74179,74181],{"href":74180},"#built-in-tools","Built-in tools",[5332,74183,74184],{},[812,74185,9042],{"href":74186},"#conclusion",[4542,74188,74190],{"id":74189},"what-is-deno","What is Deno?",[651,74192,74193,74194,74197,74198,23212,74200,74203],{},"Deno is a ",[2939,74195,74196],{},"secure"," runtime for ",[2939,74199,46520],{},[2939,74201,74202],{},"TypeScript",". Wait, a runtime for JavaScript, isn't that what Node is? Yes, and Deno was created by Ryan Dahl, the creator of Node.",[651,74205,74206,74207,74212],{},"As most developers do we often look back at projects that we built and think about what we might do different if we started it from scratch today. Ryan has a popular talk called ",[812,74208,74211],{"href":74209,"rel":74210},"https://www.youtube.com/watch?v=M3BM9TB-8yA",[816],"10 Things I Regret About Node.js"," and if you haven't had a chance to watch I would check it out.",[651,74214,74215],{},"I know what you're thinking because I was thinking the same thing before I watched that video. Why not just address all of these concerns in Node instead of starting a brand new project. Node has grown into a massive project used by millions and it's not going anywhere. This led Ryan to create a new project called Deno and fix the problems he had with Node.",[651,74217,74218,74219,74224],{},"The first major difference with Deno is that it is written in Rust while Node was written in C++. Like Node it executes JavaScript using ",[812,74220,74223],{"href":74221,"rel":74222},"https://v8.dev/",[816],"V8"," which is Google’s open-source high-performance JavaScript and WebAssembly engine.",[4542,74226,74125],{"id":74227},"installing-deno",[651,74229,74230,74231,74233,74234,664],{},"The first thing you should do is get Deno installed and write a ",[2939,74232,432],{}," script. Deno ships as a single executable with no dependencies. You can install it using the installers below, or download a release binary from the ",[812,74235,74238],{"href":74236,"rel":74237},"https://github.com/denoland/deno/releases",[816],"releases page",[651,74240,74241],{},"Shell (Mac, Linux)",[669,74243,74245],{"className":5851,"code":74244,"language":5853,"meta":674,"style":674},"curl -fsSL https://deno.land/x/install/install.sh | sh\n",[676,74246,74247],{"__ignoreMap":674},[679,74248,74249,74251,74253,74256,74258],{"class":681,"line":682},[679,74250,41991],{"class":880},[679,74252,41994],{"class":931},[679,74254,74255],{"class":689}," https://deno.land/x/install/install.sh",[679,74257,42344],{"class":685},[679,74259,74260],{"class":880}," sh\n",[651,74262,74263],{},"Powershell (Windows)",[669,74265,74267],{"className":5851,"code":74266,"language":5853,"meta":674,"style":674},"iwr https://deno.land/x/install/install.ps1 -useb | iex\n",[676,74268,74269],{"__ignoreMap":674},[679,74270,74271,74274,74277,74280,74282],{"class":681,"line":682},[679,74272,74273],{"class":880},"iwr",[679,74275,74276],{"class":689}," https://deno.land/x/install/install.ps1",[679,74278,74279],{"class":931}," -useb",[679,74281,42344],{"class":685},[679,74283,74284],{"class":880}," iex\n",[651,74286,74287],{},"After you install it you can use the REPL (Read Evaluate Print Loop). This will allow you to test some code right from the command line.",[669,74289,74291],{"className":5851,"code":74290,"language":5853,"meta":674,"style":674},"vega deno $ deno\nDeno 1.0.2\nexit using ctrl+d or close()\n> console.log(\"Hello, Deno 🦕\");\nHello, Deno 🦕\nundefined\n>\n",[676,74292,74293,74305,74313,74331,74343,74354,74359],{"__ignoreMap":674},[679,74294,74295,74297,74300,74302],{"class":681,"line":682},[679,74296,45509],{"class":880},[679,74298,74299],{"class":689}," deno",[679,74301,48993],{"class":693},[679,74303,74304],{"class":689},"deno\n",[679,74306,74307,74310],{"class":681,"line":790},[679,74308,74309],{"class":880},"Deno",[679,74311,74312],{"class":931}," 1.0.2\n",[679,74314,74315,74318,74321,74324,74326,74329],{"class":681,"line":892},[679,74316,74317],{"class":931},"exit",[679,74319,74320],{"class":689}," using",[679,74322,74323],{"class":689}," ctrl+d",[679,74325,6991],{"class":689},[679,74327,74328],{"class":689}," close",[679,74330,17545],{"class":693},[679,74332,74333,74335,74338,74341],{"class":681,"line":901},[679,74334,5860],{"class":685},[679,74336,74337],{"class":693}," console.log(",[679,74339,74340],{"class":880},"\"Hello, Deno 🦕\"",[679,74342,1208],{"class":693},[679,74344,74345,74348,74351],{"class":681,"line":909},[679,74346,74347],{"class":880},"Hello,",[679,74349,74350],{"class":689}," Deno",[679,74352,74353],{"class":689}," 🦕\n",[679,74355,74356],{"class":681,"line":918},[679,74357,74358],{"class":880},"undefined\n",[679,74360,74361],{"class":681,"line":935},[679,74362,4519],{"class":685},[651,74364,74365],{},"While you're in the REPL run the following code 🤓",[669,74367,74369],{"className":64365,"code":74368,"language":64367,"meta":674,"style":674},"\"node\"\n .split(\"\")\n .sort()\n .join(\"\");\n",[676,74370,74371,74376,74388,74396],{"__ignoreMap":674},[679,74372,74373],{"class":681,"line":682},[679,74374,74375],{"class":689},"\"node\"\n",[679,74377,74378,74380,74382,74384,74386],{"class":681,"line":790},[679,74379,46725],{"class":693},[679,74381,55948],{"class":880},[679,74383,745],{"class":693},[679,74385,3579],{"class":689},[679,74387,1339],{"class":693},[679,74389,74390,74392,74394],{"class":681,"line":892},[679,74391,46725],{"class":693},[679,74393,51928],{"class":880},[679,74395,17545],{"class":693},[679,74397,74398,74400,74403,74405,74407],{"class":681,"line":901},[679,74399,46725],{"class":693},[679,74401,74402],{"class":880},"join",[679,74404,745],{"class":693},[679,74406,3579],{"class":689},[679,74408,1208],{"class":693},[5909,74410,74412],{"id":74411},"deno-help","Deno Help",[651,74414,74415,74416,74419],{},"If you're not sure what version of Deno you're running you can use the command ",[676,74417,74418],{},"deno --version"," which will give you the versions of Deno, V8 & TypeScript.",[669,74421,74423],{"className":5851,"code":74422,"language":5853,"meta":674,"style":674},"vega deno $ deno --version\ndeno 1.0.2\nv8 8.4.300\ntypescript 3.9.2\nvega deno $\n",[676,74424,74425,74438,74444,74452,74459],{"__ignoreMap":674},[679,74426,74427,74429,74431,74433,74436],{"class":681,"line":682},[679,74428,45509],{"class":880},[679,74430,74299],{"class":689},[679,74432,48993],{"class":693},[679,74434,74435],{"class":689},"deno",[679,74437,36602],{"class":931},[679,74439,74440,74442],{"class":681,"line":790},[679,74441,74435],{"class":880},[679,74443,74312],{"class":931},[679,74445,74446,74449],{"class":681,"line":892},[679,74447,74448],{"class":880},"v8",[679,74450,74451],{"class":931}," 8.4.300\n",[679,74453,74454,74456],{"class":681,"line":901},[679,74455,28875],{"class":880},[679,74457,74458],{"class":931}," 3.9.2\n",[679,74460,74461,74463,74465],{"class":681,"line":909},[679,74462,45509],{"class":880},[679,74464,74299],{"class":689},[679,74466,74467],{"class":693}," $\n",[651,74469,74470,74471,664],{},"And if you ever forget where the docs are, how to start the REPL or can't remember what sub commands are available just run the command ",[676,74472,74473],{},"deno help",[669,74475,74477],{"className":5851,"code":74476,"language":5853,"meta":674,"style":674},"vega deno $ deno help\ndeno 1.0.2\nA secure JavaScript and TypeScript runtime\n\nDocs: https://deno.land/manual\nModules: https://deno.land/std/ https://deno.land/x/\nBugs: https://github.com/denoland/deno/issues\n\nTo start the REPL:\n deno\n\nTo execute a script:\n deno run https://deno.land/std/examples/welcome.ts\n\nTo evaluate code in the shell:\n deno eval \"console.log(30933 + 404)\"\n\nUSAGE:\n deno [OPTIONS] [SUBCOMMAND]\n\nOPTIONS:\n -h, --help Prints help information\n -L, --log-level \u003Clog-level> Set log level [possible values: debug, info]\n -q, --quiet Suppress diagnostic output\n -V, --version Prints version information\n\nSUBCOMMANDS:\n bundle Bundle module and dependencies into single file\n cache Cache the dependencies\n completions Generate shell completions\n doc Show documentation for a module\n eval Eval script\n fmt Format source files\n help Prints this message or the help of the given subcommand(s)\n info Show info about cache or info related to source file\n install Install script as an executable\n repl Read Eval Print Loop\n run Run a program given a filename or url to the module\n test Run tests\n types Print runtime TypeScript declarations\n upgrade Upgrade deno executable to given version\n\nENVIRONMENT VARIABLES:\n DENO_DIR Set deno's base directory (defaults to $HOME/.deno)\n DENO_INSTALL_ROOT Set deno install's output directory\n (defaults to $HOME/.deno/bin)\n NO_COLOR Set to disable color\n HTTP_PROXY Proxy address for HTTP requests\n (module downloads, fetch)\n HTTPS_PROXY Same but for HTTPS\nvega deno $\n",[676,74478,74479,74492,74498,74516,74520,74528,74539,74547,74551,74563,74568,74572,74584,74594,74598,74614,74624,74628,74633,74641,74645,74650,74667,74705,74722,74738,74742,74747,74772,74785,74799,74817,74828,74842,74877,74906,74924,74941,74972,74983,74999,75018,75022,75030,75041,75051,75069,75084,75103,75117,75133],{"__ignoreMap":674},[679,74480,74481,74483,74485,74487,74489],{"class":681,"line":682},[679,74482,45509],{"class":880},[679,74484,74299],{"class":689},[679,74486,48993],{"class":693},[679,74488,74435],{"class":689},[679,74490,74491],{"class":689}," help\n",[679,74493,74494,74496],{"class":681,"line":790},[679,74495,74435],{"class":880},[679,74497,74312],{"class":931},[679,74499,74500,74502,74505,74508,74510,74513],{"class":681,"line":892},[679,74501,7704],{"class":880},[679,74503,74504],{"class":689}," secure",[679,74506,74507],{"class":689}," JavaScript",[679,74509,22346],{"class":689},[679,74511,74512],{"class":689}," TypeScript",[679,74514,74515],{"class":689}," runtime\n",[679,74517,74518],{"class":681,"line":901},[679,74519,889],{"emptyLinePlaceholder":797},[679,74521,74522,74525],{"class":681,"line":909},[679,74523,74524],{"class":880},"Docs:",[679,74526,74527],{"class":689}," https://deno.land/manual\n",[679,74529,74530,74533,74536],{"class":681,"line":918},[679,74531,74532],{"class":880},"Modules:",[679,74534,74535],{"class":689}," https://deno.land/std/",[679,74537,74538],{"class":689}," https://deno.land/x/\n",[679,74540,74541,74544],{"class":681,"line":935},[679,74542,74543],{"class":880},"Bugs:",[679,74545,74546],{"class":689}," https://github.com/denoland/deno/issues\n",[679,74548,74549],{"class":681,"line":944},[679,74550,889],{"emptyLinePlaceholder":797},[679,74552,74553,74556,74558,74560],{"class":681,"line":959},[679,74554,74555],{"class":880},"To",[679,74557,42765],{"class":689},[679,74559,22351],{"class":689},[679,74561,74562],{"class":689}," REPL:\n",[679,74564,74565],{"class":681,"line":964},[679,74566,74567],{"class":880}," deno\n",[679,74569,74570],{"class":681,"line":977},[679,74571,889],{"emptyLinePlaceholder":797},[679,74573,74574,74576,74579,74581],{"class":681,"line":982},[679,74575,74555],{"class":880},[679,74577,74578],{"class":689}," execute",[679,74580,21697],{"class":689},[679,74582,74583],{"class":689}," script:\n",[679,74585,74586,74589,74591],{"class":681,"line":988},[679,74587,74588],{"class":880}," deno",[679,74590,16486],{"class":689},[679,74592,74593],{"class":689}," https://deno.land/std/examples/welcome.ts\n",[679,74595,74596],{"class":681,"line":993},[679,74597,889],{"emptyLinePlaceholder":797},[679,74599,74600,74602,74605,74607,74609,74611],{"class":681,"line":2129},[679,74601,74555],{"class":880},[679,74603,74604],{"class":689}," evaluate",[679,74606,42353],{"class":689},[679,74608,6997],{"class":689},[679,74610,22351],{"class":689},[679,74612,74613],{"class":689}," shell:\n",[679,74615,74616,74618,74621],{"class":681,"line":2140},[679,74617,74588],{"class":880},[679,74619,74620],{"class":689}," eval",[679,74622,74623],{"class":689}," \"console.log(30933 + 404)\"\n",[679,74625,74626],{"class":681,"line":2145},[679,74627,889],{"emptyLinePlaceholder":797},[679,74629,74630],{"class":681,"line":2154},[679,74631,74632],{"class":880},"USAGE:\n",[679,74634,74635,74638],{"class":681,"line":2159},[679,74636,74637],{"class":880}," deno",[679,74639,74640],{"class":693}," [OPTIONS] [SUBCOMMAND]\n",[679,74642,74643],{"class":681,"line":2164},[679,74644,889],{"emptyLinePlaceholder":797},[679,74646,74647],{"class":681,"line":3134},[679,74648,74649],{"class":880},"OPTIONS:\n",[679,74651,74652,74655,74658,74661,74664],{"class":681,"line":3139},[679,74653,74654],{"class":880}," -h,",[679,74656,74657],{"class":931}," --help",[679,74659,74660],{"class":689}," Prints",[679,74662,74663],{"class":689}," help",[679,74665,74666],{"class":689}," information\n",[679,74668,74669,74672,74675,74677,74680,74683,74685,74688,74690,74693,74696,74699,74702],{"class":681,"line":3144},[679,74670,74671],{"class":880}," -L,",[679,74673,74674],{"class":931}," --log-level",[679,74676,48609],{"class":685},[679,74678,74679],{"class":689},"log-leve",[679,74681,74682],{"class":693},"l",[679,74684,5860],{"class":685},[679,74686,74687],{"class":689}," Set",[679,74689,56466],{"class":689},[679,74691,74692],{"class":689}," level",[679,74694,74695],{"class":693}," [possible ",[679,74697,74698],{"class":689},"values:",[679,74700,74701],{"class":689}," debug,",[679,74703,74704],{"class":689}," info]\n",[679,74706,74707,74710,74713,74716,74719],{"class":681,"line":3149},[679,74708,74709],{"class":880}," -q,",[679,74711,74712],{"class":931}," --quiet",[679,74714,74715],{"class":689}," Suppress",[679,74717,74718],{"class":689}," diagnostic",[679,74720,74721],{"class":689}," output\n",[679,74723,74724,74727,74730,74733,74736],{"class":681,"line":3169},[679,74725,74726],{"class":880}," -V,",[679,74728,74729],{"class":931}," --version",[679,74731,74732],{"class":689}," Prints",[679,74734,74735],{"class":689}," version",[679,74737,74666],{"class":689},[679,74739,74740],{"class":681,"line":3185},[679,74741,889],{"emptyLinePlaceholder":797},[679,74743,74744],{"class":681,"line":3194},[679,74745,74746],{"class":880},"SUBCOMMANDS:\n",[679,74748,74749,74752,74755,74758,74760,74763,74766,74769],{"class":681,"line":3199},[679,74750,74751],{"class":880}," bundle",[679,74753,74754],{"class":689}," Bundle",[679,74756,74757],{"class":689}," module",[679,74759,22346],{"class":689},[679,74761,74762],{"class":689}," dependencies",[679,74764,74765],{"class":689}," into",[679,74767,74768],{"class":689}," single",[679,74770,74771],{"class":689}," file\n",[679,74773,74774,74777,74780,74782],{"class":681,"line":3212},[679,74775,74776],{"class":880}," cache",[679,74778,74779],{"class":689}," Cache",[679,74781,22351],{"class":689},[679,74783,74784],{"class":689}," dependencies\n",[679,74786,74787,74790,74793,74796],{"class":681,"line":3217},[679,74788,74789],{"class":880}," completions",[679,74791,74792],{"class":689}," Generate",[679,74794,74795],{"class":689}," shell",[679,74797,74798],{"class":689}," completions\n",[679,74800,74801,74804,74807,74810,74812,74814],{"class":681,"line":3222},[679,74802,74803],{"class":880}," doc",[679,74805,74806],{"class":689}," Show",[679,74808,74809],{"class":689}," documentation",[679,74811,6818],{"class":689},[679,74813,21697],{"class":689},[679,74815,74816],{"class":689}," module\n",[679,74818,74819,74822,74825],{"class":681,"line":3227},[679,74820,74821],{"class":931}," eval",[679,74823,74824],{"class":689}," Eval",[679,74826,74827],{"class":689}," script\n",[679,74829,74830,74833,74836,74839],{"class":681,"line":3232},[679,74831,74832],{"class":880}," fmt",[679,74834,74835],{"class":689}," Format",[679,74837,74838],{"class":689}," source",[679,74840,74841],{"class":689}," files\n",[679,74843,74844,74847,74850,74852,74855,74857,74859,74861,74863,74865,74868,74871,74873,74875],{"class":681,"line":3499},[679,74845,74846],{"class":880}," help",[679,74848,74849],{"class":689}," Prints",[679,74851,21353],{"class":689},[679,74853,74854],{"class":689}," message",[679,74856,6991],{"class":689},[679,74858,22351],{"class":689},[679,74860,74663],{"class":689},[679,74862,19757],{"class":689},[679,74864,22351],{"class":689},[679,74866,74867],{"class":689}," given",[679,74869,74870],{"class":689}," subcommand",[679,74872,745],{"class":693},[679,74874,20413],{"class":880},[679,74876,1339],{"class":693},[679,74878,74879,74882,74885,74888,74891,74893,74895,74897,74900,74902,74904],{"class":681,"line":3509},[679,74880,74881],{"class":880}," info",[679,74883,74884],{"class":689}," Show",[679,74886,74887],{"class":689}," info",[679,74889,74890],{"class":689}," about",[679,74892,37177],{"class":689},[679,74894,6991],{"class":689},[679,74896,74887],{"class":689},[679,74898,74899],{"class":689}," related",[679,74901,21703],{"class":689},[679,74903,74838],{"class":689},[679,74905,74771],{"class":689},[679,74907,74908,74911,74914,74916,74918,74921],{"class":681,"line":3516},[679,74909,74910],{"class":880}," install",[679,74912,74913],{"class":689}," Install",[679,74915,52506],{"class":689},[679,74917,24745],{"class":689},[679,74919,74920],{"class":689}," an",[679,74922,74923],{"class":689}," executable\n",[679,74925,74926,74929,74932,74935,74938],{"class":681,"line":3531},[679,74927,74928],{"class":880}," repl",[679,74930,74931],{"class":689}," Read",[679,74933,74934],{"class":689}," Eval",[679,74936,74937],{"class":689}," Print",[679,74939,74940],{"class":689}," Loop\n",[679,74942,74943,74946,74949,74951,74954,74956,74958,74961,74963,74966,74968,74970],{"class":681,"line":3536},[679,74944,74945],{"class":880}," run",[679,74947,74948],{"class":689}," Run",[679,74950,21697],{"class":689},[679,74952,74953],{"class":689}," program",[679,74955,74867],{"class":689},[679,74957,21697],{"class":689},[679,74959,74960],{"class":689}," filename",[679,74962,6991],{"class":689},[679,74964,74965],{"class":689}," url",[679,74967,21703],{"class":689},[679,74969,22351],{"class":689},[679,74971,74816],{"class":689},[679,74973,74974,74977,74980],{"class":681,"line":3541},[679,74975,74976],{"class":931}," test",[679,74978,74979],{"class":689}," Run",[679,74981,74982],{"class":689}," tests\n",[679,74984,74985,74988,74991,74994,74996],{"class":681,"line":3546},[679,74986,74987],{"class":880}," types",[679,74989,74990],{"class":689}," Print",[679,74992,74993],{"class":689}," runtime",[679,74995,74512],{"class":689},[679,74997,74998],{"class":689}," declarations\n",[679,75000,75001,75004,75007,75009,75012,75014,75016],{"class":681,"line":3551},[679,75002,75003],{"class":880}," upgrade",[679,75005,75006],{"class":689}," Upgrade",[679,75008,74299],{"class":689},[679,75010,75011],{"class":689}," executable",[679,75013,21703],{"class":689},[679,75015,74867],{"class":689},[679,75017,45584],{"class":689},[679,75019,75020],{"class":681,"line":3557},[679,75021,889],{"emptyLinePlaceholder":797},[679,75023,75024,75027],{"class":681,"line":3567},[679,75025,75026],{"class":880},"ENVIRONMENT",[679,75028,75029],{"class":689}," VARIABLES:\n",[679,75031,75032,75035,75038],{"class":681,"line":3574},[679,75033,75034],{"class":880}," DENO_DIR",[679,75036,75037],{"class":689}," Set",[679,75039,75040],{"class":689}," deno's base directory (defaults to $HOME/.deno)\n",[679,75042,75043,75046,75048],{"class":681,"line":3589},[679,75044,75045],{"class":689}," DENO_INSTALL_ROOT Set deno install's",[679,75047,62384],{"class":689},[679,75049,75050],{"class":689}," directory\n",[679,75052,75053,75056,75059,75061,75064,75067],{"class":681,"line":3594},[679,75054,75055],{"class":693}," (",[679,75057,75058],{"class":880},"defaults",[679,75060,21703],{"class":689},[679,75062,75063],{"class":693}," $HOME",[679,75065,75066],{"class":689},"/.deno/bin",[679,75068,1339],{"class":693},[679,75070,75071,75074,75076,75078,75081],{"class":681,"line":3602},[679,75072,75073],{"class":880}," NO_COLOR",[679,75075,75037],{"class":689},[679,75077,21703],{"class":689},[679,75079,75080],{"class":689}," disable",[679,75082,75083],{"class":689}," color\n",[679,75085,75086,75089,75092,75095,75097,75100],{"class":681,"line":3608},[679,75087,75088],{"class":880}," HTTP_PROXY",[679,75090,75091],{"class":689}," Proxy",[679,75093,75094],{"class":689}," address",[679,75096,6818],{"class":689},[679,75098,75099],{"class":689}," HTTP",[679,75101,75102],{"class":689}," requests\n",[679,75104,75105,75107,75109,75112,75115],{"class":681,"line":3619},[679,75106,75055],{"class":693},[679,75108,27515],{"class":880},[679,75110,75111],{"class":689}," downloads,",[679,75113,75114],{"class":689}," fetch",[679,75116,1339],{"class":693},[679,75118,75119,75122,75125,75128,75130],{"class":681,"line":3624},[679,75120,75121],{"class":880}," HTTPS_PROXY",[679,75123,75124],{"class":689}," Same",[679,75126,75127],{"class":689}," but",[679,75129,6818],{"class":689},[679,75131,75132],{"class":689}," HTTPS\n",[679,75134,75135,75137,75139],{"class":681,"line":3629},[679,75136,45509],{"class":880},[679,75138,74299],{"class":689},[679,75140,74467],{"class":693},[5909,75142,75144],{"id":75143},"setting-up-your-development-environment","Setting up your development environment",[651,75146,75147,75148,75153],{},"Now that Deno is installed there is one last thing to do before you write that first program. You can check out ",[812,75149,75152],{"href":75150,"rel":75151},"https://deno.land/manual/getting_started/setup_your_environment",[816],"the manual"," for complete instructions on setting up your development environment.",[651,75155,75156],{},"If you're a Visual Studio Code user (of course you are 😜) there are a few Deno extensions in the Marketplace. I haven't had a chance to look at all of them yet but the link below is the official extension from the Deno team and it's the one I am using.",[651,75158,75159],{},[812,75160,75161],{"href":75161,"rel":75162},"https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno",[816],[5909,75164,75166],{"id":75165},"hello-deno","Hello, Deno!",[651,75168,75169,75170,75173],{},"In your editor of choice create a new file called ",[676,75171,75172],{},"HelloWorld.ts"," and enter the following code",[669,75175,75177],{"className":64365,"code":75176,"language":64367,"meta":674,"style":674},"console.log(\"Welcome to Deno 🦕\");\n",[676,75178,75179],{"__ignoreMap":674},[679,75180,75181,75183,75185,75187,75190],{"class":681,"line":682},[679,75182,45224],{"class":693},[679,75184,21374],{"class":880},[679,75186,745],{"class":693},[679,75188,75189],{"class":689},"\"Welcome to Deno 🦕\"",[679,75191,1208],{"class":693},[651,75193,75194,75195,75198],{},"To run the program type ",[676,75196,75197],{},"deno run HelloWorld.ts"," from the command line. When you run that command you should get the following output.",[669,75200,75202],{"className":5851,"code":75201,"language":5853,"meta":674,"style":674},"vega hello-world $ deno run HelloWorld.ts\nCompile file:///Users/vega/dev/deno/hello-world/HelloWorld.ts\nWelcome to Deno 🦕\nvega hello-world $\n",[676,75203,75204,75220,75228,75238],{"__ignoreMap":674},[679,75205,75206,75208,75211,75213,75215,75217],{"class":681,"line":682},[679,75207,45509],{"class":880},[679,75209,75210],{"class":689}," hello-world",[679,75212,48993],{"class":693},[679,75214,74435],{"class":689},[679,75216,16486],{"class":689},[679,75218,75219],{"class":689}," HelloWorld.ts\n",[679,75221,75222,75225],{"class":681,"line":790},[679,75223,75224],{"class":880},"Compile",[679,75226,75227],{"class":689}," file:///Users/vega/dev/deno/hello-world/HelloWorld.ts\n",[679,75229,75230,75232,75234,75236],{"class":681,"line":892},[679,75231,69648],{"class":880},[679,75233,21703],{"class":689},[679,75235,74350],{"class":689},[679,75237,74353],{"class":689},[679,75239,75240,75242,75244],{"class":681,"line":901},[679,75241,45509],{"class":880},[679,75243,75210],{"class":689},[679,75245,74467],{"class":693},[651,75247,75248,75249,75252,75253,75256],{},"You just wrote some JavaScript inside of a TypeScript file. Deno saw the ",[676,75250,75251],{},".ts"," extension and used the built-in TypeScript compiler to compile your application. As you just saw you are not forced into writing TypeScript but it is nice that it is supported out of the box and, we will talk more about that later in this article. If you change the extension to ",[676,75254,75255],{},".js"," and run it you will the same output minus the compilation process.",[669,75258,75260],{"className":5851,"code":75259,"language":5853,"meta":674,"style":674},"vega hello-world $ deno run HelloWorld.js\nWelcome to Deno 🦕\nvega hello-world $\n",[676,75261,75262,75277,75287],{"__ignoreMap":674},[679,75263,75264,75266,75268,75270,75272,75274],{"class":681,"line":682},[679,75265,45509],{"class":880},[679,75267,75210],{"class":689},[679,75269,48993],{"class":693},[679,75271,74435],{"class":689},[679,75273,16486],{"class":689},[679,75275,75276],{"class":689}," HelloWorld.js\n",[679,75278,75279,75281,75283,75285],{"class":681,"line":790},[679,75280,69648],{"class":880},[679,75282,21703],{"class":689},[679,75284,74350],{"class":689},[679,75286,74353],{"class":689},[679,75288,75289,75291,75293],{"class":681,"line":892},[679,75290,45509],{"class":880},[679,75292,75210],{"class":689},[679,75294,74467],{"class":693},[4542,75296,74131],{"id":75297},"deno-features",[651,75299,75300],{},"Now that you have an introduction to Deno and have your first script I want to dive into some of the features. Here are some things I think make Deno an exciting project to pay attention to:",[5316,75302,75303,75306,75309,75312,75315,75318,75321,75324,75329],{},[5332,75304,75305],{},"✔ Secure Runtime",[5332,75307,75308],{},"✔ TypeScript Support",[5332,75310,75311],{},"✔ Standard Library",[5332,75313,75314],{},"✔ Built-in Testing Library",[5332,75316,75317],{},"✔ Promises, Async & Await",[5332,75319,75320],{},"✔ Browser Compatible APIs",[5332,75322,75323],{},"✔ ES Modules",[5332,75325,75326,75327],{},"✔ No ",[676,75328,60242],{},[5332,75330,75331],{},"✔ Built-in tools",[5909,75333,74139],{"id":75334},"secure-runtime",[651,75336,75337],{},"The Node Package Manager (NPM) allows you to quickly find a package to solve just about any problem. A big problem here is that we as developers have gotten lazy when it comes to questioning if a package is necessary.",[651,75339,75340,75341,75344],{},"We have gotten to the point where if we need some code to perform some task we will just ",[676,75342,75343],{},"npm i write-my-code-for-me",". I'm not saying you can't install that library, but take the time and ask yourself is this something I really need and if so what is this code doing. That library you just downloaded has been given full permission to do whatever it wants and that can lead to some real problems.",[651,75346,75347],{},"Deno on the other hand executes code in a sandbox. This means that by default Deno will not have permission to access the network, file system, and the environment.",[5259,75349,75351],{"id":75350},"http-server-demo","Http Server Demo",[651,75353,75354,75355,75358],{},"Below is a script that will start up a simple http server on port 8000 and respond to a request with a simple key/value pair in JSON. Create a new file called ",[676,75356,75357],{},"server.ts"," and add in the following code:",[669,75360,75363],{"className":75361,"code":75362,"language":29038,"meta":674,"style":674},"language-ts shiki shiki-themes github-light github-dark github-light","import { serve } from \"https://deno.land/std@0.50.0/http/server.ts\";\nconst s = serve({ port: 8000 });\nconsole.log(\"http://localhost:8000/\");\nfor await (const req of s) {\n req.respond({ body: JSON.stringify({ msg: \"Hello, World!\" }) });\n}\n",[676,75364,75365,75379,75398,75411,75429,75454],{"__ignoreMap":674},[679,75366,75367,75369,75372,75374,75377],{"class":681,"line":682},[679,75368,1999],{"class":685},[679,75370,75371],{"class":693}," { serve } ",[679,75373,28887],{"class":685},[679,75375,75376],{"class":689}," \"https://deno.land/std@0.50.0/http/server.ts\"",[679,75378,1186],{"class":693},[679,75380,75381,75383,75386,75388,75390,75393,75396],{"class":681,"line":790},[679,75382,45172],{"class":685},[679,75384,75385],{"class":931}," s",[679,75387,6883],{"class":685},[679,75389,37382],{"class":880},[679,75391,75392],{"class":693},"({ port: ",[679,75394,75395],{"class":931},"8000",[679,75397,49189],{"class":693},[679,75399,75400,75402,75404,75406,75409],{"class":681,"line":892},[679,75401,45224],{"class":693},[679,75403,21374],{"class":880},[679,75405,745],{"class":693},[679,75407,75408],{"class":689},"\"http://localhost:8000/\"",[679,75410,1208],{"class":693},[679,75412,75413,75415,75417,75419,75421,75424,75426],{"class":681,"line":901},[679,75414,1466],{"class":685},[679,75416,55488],{"class":685},[679,75418,4193],{"class":693},[679,75420,45172],{"class":685},[679,75422,75423],{"class":931}," req",[679,75425,19757],{"class":685},[679,75427,75428],{"class":693}," s) {\n",[679,75430,75431,75434,75437,75440,75442,75444,75446,75449,75451],{"class":681,"line":909},[679,75432,75433],{"class":693}," req.",[679,75435,75436],{"class":880},"respond",[679,75438,75439],{"class":693},"({ body: ",[679,75441,67370],{"class":931},[679,75443,664],{"class":693},[679,75445,56185],{"class":880},[679,75447,75448],{"class":693},"({ msg: ",[679,75450,71855],{"class":689},[679,75452,75453],{"class":693}," }) });\n",[679,75455,75456],{"class":681,"line":918},[679,75457,996],{"class":693},[651,75459,75460,75461,75464],{},"If you try and run this script using ",[676,75462,75463],{},"deno run server.ts"," you will get the following error:",[669,75466,75468],{"className":5851,"code":75467,"language":5853,"meta":674,"style":674},"vega basic-server $ deno run server.ts\nCompile file:///Users/vega/dev/deno/basic-server/server.ts\nerror: Uncaught PermissionDenied: network access to \"0.0.0.0:8000\", run again with the --allow-net flag\n at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)\n at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)\n at Object.listen ($deno$/ops/net.ts:51:10)\n at listen ($deno$/net.ts:152:22)\n at serve (https://deno.land/std@0.50.0/http/server.ts:261:20)\n at file:///Users/vega/dev/deno/basic-server/server.ts:2:11\nvega basic-server $\n",[676,75469,75470,75486,75493,75530,75540,75550,75560,75570,75579,75586],{"__ignoreMap":674},[679,75471,75472,75474,75477,75479,75481,75483],{"class":681,"line":682},[679,75473,45509],{"class":880},[679,75475,75476],{"class":689}," basic-server",[679,75478,48993],{"class":693},[679,75480,74435],{"class":689},[679,75482,16486],{"class":689},[679,75484,75485],{"class":689}," server.ts\n",[679,75487,75488,75490],{"class":681,"line":790},[679,75489,75224],{"class":880},[679,75491,75492],{"class":689}," file:///Users/vega/dev/deno/basic-server/server.ts\n",[679,75494,75495,75498,75501,75504,75507,75510,75512,75515,75517,75520,75522,75524,75527],{"class":681,"line":892},[679,75496,75497],{"class":880},"error:",[679,75499,75500],{"class":689}," Uncaught",[679,75502,75503],{"class":689}," PermissionDenied:",[679,75505,75506],{"class":689}," network",[679,75508,75509],{"class":689}," access",[679,75511,21703],{"class":689},[679,75513,75514],{"class":689}," \"0.0.0.0:8000\",",[679,75516,16486],{"class":689},[679,75518,75519],{"class":689}," again",[679,75521,44505],{"class":689},[679,75523,22351],{"class":689},[679,75525,75526],{"class":931}," --allow-net",[679,75528,75529],{"class":689}," flag\n",[679,75531,75532,75534,75537],{"class":681,"line":901},[679,75533,8024],{"class":880},[679,75535,75536],{"class":689}," unwrapResponse",[679,75538,75539],{"class":693}," ($deno$/ops/dispatch_json.ts:43:11)\n",[679,75541,75542,75544,75547],{"class":681,"line":909},[679,75543,8024],{"class":880},[679,75545,75546],{"class":689}," Object.sendSync",[679,75548,75549],{"class":693}," ($deno$/ops/dispatch_json.ts:72:10)\n",[679,75551,75552,75554,75557],{"class":681,"line":918},[679,75553,8024],{"class":880},[679,75555,75556],{"class":689}," Object.listen",[679,75558,75559],{"class":693}," ($deno$/ops/net.ts:51:10)\n",[679,75561,75562,75564,75567],{"class":681,"line":935},[679,75563,8024],{"class":880},[679,75565,75566],{"class":689}," listen",[679,75568,75569],{"class":693}," ($deno$/net.ts:152:22)\n",[679,75571,75572,75574,75576],{"class":681,"line":944},[679,75573,8024],{"class":880},[679,75575,37382],{"class":689},[679,75577,75578],{"class":693}," (https://deno.land/std@0.50.0/http/server.ts:261:20)\n",[679,75580,75581,75583],{"class":681,"line":959},[679,75582,8024],{"class":880},[679,75584,75585],{"class":689}," file:///Users/vega/dev/deno/basic-server/server.ts:2:11\n",[679,75587,75588,75590,75592],{"class":681,"line":964},[679,75589,45509],{"class":880},[679,75591,75476],{"class":689},[679,75593,74467],{"class":693},[651,75595,75596,75597,75600,75601,664],{},"To run the script you will need to run the command ",[676,75598,75599],{},"deno run --allow-net server.ts",". This is what Deno means when it says that it is secure by default. If you're interested there is a list of permissions in the ",[812,75602,75605],{"href":75603,"rel":75604},"https://deno.land/manual/getting_started/permissions#permissions-list",[816],"Deno Manual",[5909,75607,74145],{"id":75608},"typescript-support",[651,75610,75611],{},"I mentioned this a couple of times already but Deno supports both JavaScript and TypeScript as first-class languages. Coming from a Java background I appreciate the benefits of types especially working on a team in larger projects. If you're not a fan of TypeScript, stick with JavaScript.",[651,75613,75614,75615,75618],{},"There are many frameworks & runtimes that support TypeScript but they have to provide additional tooling to do so. Deno is designed with TypeScript in mind and the standard modules are all written in TypeScript. The ",[676,75616,75617],{},"deno types"," command provides type declarations for everything provided by Deno.",[651,75620,75621],{},"Because both JavaScript and TypeScript are supported it means that you need to provide fully qualified names, including the extension.",[669,75623,75625],{"className":75361,"code":75624,"language":29038,"meta":674,"style":674},"import { serve } from \"https://deno.land/std@0.53.0/http/server.ts\"; // RIGHT\nimport { serve } from \"https://deno.land/std@0.53.0/http/server\"; // WRONG\n",[676,75626,75627,75644],{"__ignoreMap":674},[679,75628,75629,75631,75633,75635,75638,75641],{"class":681,"line":682},[679,75630,1999],{"class":685},[679,75632,75371],{"class":693},[679,75634,28887],{"class":685},[679,75636,75637],{"class":689}," \"https://deno.land/std@0.53.0/http/server.ts\"",[679,75639,75640],{"class":693},"; ",[679,75642,75643],{"class":1400},"// RIGHT\n",[679,75645,75646,75648,75650,75652,75655,75657],{"class":681,"line":790},[679,75647,1999],{"class":685},[679,75649,75371],{"class":693},[679,75651,28887],{"class":685},[679,75653,75654],{"class":689}," \"https://deno.land/std@0.53.0/http/server\"",[679,75656,75640],{"class":693},[679,75658,75659],{"class":1400},"// WRONG\n",[651,75661,75662,75663,664],{},"If you want to learn more about using external type definitions or customizing the TypeScript compiler options checkout the ",[812,75664,75605],{"href":75665,"rel":75666},"https://deno.land/manual/getting_started/typescript",[816],[5909,75668,74151],{"id":75669},"standard-library",[651,75671,75672,75673,75678,75679,75684],{},"I recently started learning the ",[812,75674,75677],{"href":75675,"rel":75676},"https://golang.org/",[816],"Go Programming Language"," and when I started diving into the ",[812,75680,75683],{"href":75681,"rel":75682},"https://deno.land/std",[816],"Deno Standard Library"," it felt familiar to me. The reason for this is that Deno was originally written in Go and to this day draws a lot of inspiration from the language. If you look in the documentation for the standard library it even says the following:",[1004,75686,75687],{},[651,75688,75689],{},"deno_std is a loose port of Go's standard library. When in doubt, simply port Go's source code, documentation, and tests.",[651,75691,75692],{},"As I mentioned earlier the standard library is written entirely in TypeScript and it is async by default, more on that in a bit. The standard library modules do not have external dependencies and they are reviewed by the Deno core team.",[651,75694,75695,75696,75700],{},"These modules are tagged in accordance with Deno releases. So, for example, the v0.53.0 tag is guaranteed to work with Deno v0.53.0. You can link to v0.53.0 using the URL ",[812,75697,75698],{"href":75698,"rel":75699},"https://deno.land/std@v0.53.0/",[816],". Not specifying a tag will link to the master branch. It is strongly recommended that you link to tagged releases to avoid unintended updates.",[5259,75702,75704],{"id":75703},"uuid-example","UUID Example",[651,75706,75707],{},"If you wanted to use UUID from the standard library the following example would always pull the latest version from master.",[669,75709,75711],{"className":75361,"code":75710,"language":29038,"meta":674,"style":674},"import { v4 } from \"https://deno.land/std/uuid/mod.ts\";\n\n// Generate a v4 uuid\nconst myUUID = v4.generate();\n\n// Validate a v4 uuid\nconst isValid = v4.validate(myUUID);\n\nconsole.log(isValid);\n",[676,75712,75713,75727,75731,75736,75753,75757,75762,75778,75782],{"__ignoreMap":674},[679,75714,75715,75717,75720,75722,75725],{"class":681,"line":682},[679,75716,1999],{"class":685},[679,75718,75719],{"class":693}," { v4 } ",[679,75721,28887],{"class":685},[679,75723,75724],{"class":689}," \"https://deno.land/std/uuid/mod.ts\"",[679,75726,1186],{"class":693},[679,75728,75729],{"class":681,"line":790},[679,75730,889],{"emptyLinePlaceholder":797},[679,75732,75733],{"class":681,"line":892},[679,75734,75735],{"class":1400},"// Generate a v4 uuid\n",[679,75737,75738,75740,75743,75745,75748,75751],{"class":681,"line":901},[679,75739,45172],{"class":685},[679,75741,75742],{"class":931}," myUUID",[679,75744,6883],{"class":685},[679,75746,75747],{"class":693}," v4.",[679,75749,75750],{"class":880},"generate",[679,75752,9317],{"class":693},[679,75754,75755],{"class":681,"line":909},[679,75756,889],{"emptyLinePlaceholder":797},[679,75758,75759],{"class":681,"line":918},[679,75760,75761],{"class":1400},"// Validate a v4 uuid\n",[679,75763,75764,75766,75768,75770,75772,75775],{"class":681,"line":935},[679,75765,45172],{"class":685},[679,75767,20408],{"class":931},[679,75769,6883],{"class":685},[679,75771,75747],{"class":693},[679,75773,75774],{"class":880},"validate",[679,75776,75777],{"class":693},"(myUUID);\n",[679,75779,75780],{"class":681,"line":944},[679,75781,889],{"emptyLinePlaceholder":797},[679,75783,75784,75786,75788],{"class":681,"line":959},[679,75785,45224],{"class":693},[679,75787,21374],{"class":880},[679,75789,75790],{"class":693},"(isValid);\n",[651,75792,75793],{},"You should be specific with the version number to avoid uninteded updates",[669,75795,75797],{"className":75361,"code":75796,"language":29038,"meta":674,"style":674},"import { v4 } from \"https://deno.land/std@0.53.0/uuid/mod.ts\";\n",[676,75798,75799],{"__ignoreMap":674},[679,75800,75801,75803,75805,75807,75810],{"class":681,"line":682},[679,75802,1999],{"class":685},[679,75804,75719],{"class":693},[679,75806,28887],{"class":685},[679,75808,75809],{"class":689}," \"https://deno.land/std@0.53.0/uuid/mod.ts\"",[679,75811,1186],{"class":693},[651,75813,75814,75815,664],{},"Deno provides a standard library at ",[812,75816,75681],{"href":75681,"rel":75817},[816],[5909,75819,74157],{"id":75820},"built-in-testing-library",[651,75822,75823],{},"Deno has a built-in test runner that you can use for testing JavaScript or TypeScript code. I for one think this helps to improve the developer experience. When you don't have to investigate what testing framework to use and the language can just provide one out as part of the runtime it might help developers write more tests. This doesn't mean that you can't go use your favorite testing framework but it does make it easier to test out of the box.",[651,75825,75826,75827,75830],{},"Create a new file called ",[676,75828,75829],{},"HelloWorldTest.ts"," with the following code:",[669,75832,75834],{"className":75361,"code":75833,"language":29038,"meta":674,"style":674},"import { assertEquals } from \"https://deno.land/std/testing/asserts.ts\";\n\nDeno.test(\"hello world\", () => {\n const x = 1 + 2;\n assertEquals(x, 3);\n});\n",[676,75835,75836,75850,75854,75872,75888,75900],{"__ignoreMap":674},[679,75837,75838,75840,75843,75845,75848],{"class":681,"line":682},[679,75839,1999],{"class":685},[679,75841,75842],{"class":693}," { assertEquals } ",[679,75844,28887],{"class":685},[679,75846,75847],{"class":689}," \"https://deno.land/std/testing/asserts.ts\"",[679,75849,1186],{"class":693},[679,75851,75852],{"class":681,"line":790},[679,75853,889],{"emptyLinePlaceholder":797},[679,75855,75856,75859,75861,75863,75866,75868,75870],{"class":681,"line":892},[679,75857,75858],{"class":693},"Deno.",[679,75860,44529],{"class":880},[679,75862,745],{"class":693},[679,75864,75865],{"class":689},"\"hello world\"",[679,75867,64887],{"class":693},[679,75869,21350],{"class":685},[679,75871,884],{"class":693},[679,75873,75874,75876,75878,75880,75882,75884,75886],{"class":681,"line":901},[679,75875,46903],{"class":685},[679,75877,72205],{"class":931},[679,75879,6883],{"class":685},[679,75881,48606],{"class":931},[679,75883,3059],{"class":685},[679,75885,21871],{"class":931},[679,75887,1186],{"class":693},[679,75889,75890,75893,75896,75898],{"class":681,"line":909},[679,75891,75892],{"class":880}," assertEquals",[679,75894,75895],{"class":693},"(x, ",[679,75897,66599],{"class":931},[679,75899,1208],{"class":693},[679,75901,75902],{"class":681,"line":918},[679,75903,46842],{"class":693},[651,75905,75906,75907],{},"You can run the test from the command line using the command: ",[676,75908,75909],{},"deno test HelloWorldTest.ts",[669,75911,75913],{"className":5851,"code":75912,"language":5853,"meta":674,"style":674},"vega test $ deno test HelloWorldTest.ts\nCompile file:///Users/vega/dev/deno/test/.deno.test.ts\nrunning 1 tests\ntest hello world ... ok (3ms)\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (3ms)\n\nvega test $\n",[676,75914,75915,75931,75938,75947,75965,75969,76015,76019],{"__ignoreMap":674},[679,75916,75917,75919,75922,75924,75926,75928],{"class":681,"line":682},[679,75918,45509],{"class":880},[679,75920,75921],{"class":689}," test",[679,75923,48993],{"class":693},[679,75925,74435],{"class":689},[679,75927,75921],{"class":689},[679,75929,75930],{"class":689}," HelloWorldTest.ts\n",[679,75932,75933,75935],{"class":681,"line":790},[679,75934,75224],{"class":880},[679,75936,75937],{"class":689}," file:///Users/vega/dev/deno/test/.deno.test.ts\n",[679,75939,75940,75943,75945],{"class":681,"line":892},[679,75941,75942],{"class":880},"running",[679,75944,48606],{"class":931},[679,75946,74982],{"class":689},[679,75948,75949,75951,75953,75956,75959,75962],{"class":681,"line":901},[679,75950,44529],{"class":931},[679,75952,73094],{"class":689},[679,75954,75955],{"class":689}," world",[679,75957,75958],{"class":689}," ...",[679,75960,75961],{"class":689}," ok",[679,75963,75964],{"class":693}," (3ms)\n",[679,75966,75967],{"class":681,"line":909},[679,75968,889],{"emptyLinePlaceholder":797},[679,75970,75971,75973,75976,75979,75981,75984,75986,75988,75990,75992,75994,75997,75999,76001,76004,76006,76008,76011,76013],{"class":681,"line":918},[679,75972,44529],{"class":931},[679,75974,75975],{"class":689}," result:",[679,75977,75978],{"class":689}," ok.",[679,75980,48606],{"class":931},[679,75982,75983],{"class":689}," passed",[679,75985,75640],{"class":693},[679,75987,1060],{"class":880},[679,75989,19733],{"class":689},[679,75991,75640],{"class":693},[679,75993,1060],{"class":880},[679,75995,75996],{"class":689}," ignored",[679,75998,75640],{"class":693},[679,76000,1060],{"class":880},[679,76002,76003],{"class":689}," measured",[679,76005,75640],{"class":693},[679,76007,1060],{"class":880},[679,76009,76010],{"class":689}," filtered",[679,76012,42079],{"class":689},[679,76014,75964],{"class":693},[679,76016,76017],{"class":681,"line":935},[679,76018,889],{"emptyLinePlaceholder":797},[679,76020,76021,76023,76025],{"class":681,"line":944},[679,76022,45509],{"class":880},[679,76024,75921],{"class":689},[679,76026,74467],{"class":693},[651,76028,76029,76030,76033],{},"The method ",[676,76031,76032],{},"assertEquals"," came from the standard library and if you take a look at it there are a number of helpful assertions.",[651,76035,76036],{},[812,76037,76038],{"href":76038,"rel":76039},"https://deno.land/std/testing",[816],[5909,76041,74163],{"id":76042},"promises-async-await",[651,76044,76045],{},"For a technology built for events, Node.js didn’t do a great job with them. It came out before the concepts of Promises or the async/await patterns, so had to come up with its own version. Deno takes full advantage of Promises and the standard library is asynchronous by default.",[651,76047,76048,76049,76056,76057,76060,76061,70687],{},"Deno also provides ",[812,76050,76053],{"href":76051,"rel":76052},"https://github.com/tc39/proposal-top-level-await",[816],[676,76054,76055],{},"top-level-await"," which really helps remove some boilerplate code. In a Vanilla JavaScript if you want to use ",[676,76058,76059],{},"await"," it needs to be wrapped in ",[676,76062,55212],{},[669,76064,76066],{"className":64365,"code":76065,"language":64367,"meta":674,"style":674},"async function loadUsers() {\n const users = await fetch(\"https://users.com\");\n}\n",[676,76067,76068,76079,76099],{"__ignoreMap":674},[679,76069,76070,76072,76074,76077],{"class":681,"line":682},[679,76071,55212],{"class":685},[679,76073,21700],{"class":685},[679,76075,76076],{"class":880}," loadUsers",[679,76078,2667],{"class":693},[679,76080,76081,76083,76086,76088,76090,76092,76094,76097],{"class":681,"line":790},[679,76082,46903],{"class":685},[679,76084,76085],{"class":931}," users",[679,76087,6883],{"class":685},[679,76089,55488],{"class":685},[679,76091,75114],{"class":880},[679,76093,745],{"class":693},[679,76095,76096],{"class":689},"\"https://users.com\"",[679,76098,1208],{"class":693},[679,76100,76101],{"class":681,"line":892},[679,76102,996],{"class":693},[651,76104,76105,76106,76108,76109,70687],{},"With ",[676,76107,76055],{}," support you don't have to wrap this in ",[676,76110,55212],{},[669,76112,76114],{"className":64365,"code":76113,"language":64367,"meta":674,"style":674},"const users = await fetch(\"https://users.com\");\n",[676,76115,76116],{"__ignoreMap":674},[679,76117,76118,76120,76122,76124,76126,76128,76130,76132],{"class":681,"line":682},[679,76119,45172],{"class":685},[679,76121,76085],{"class":931},[679,76123,6883],{"class":685},[679,76125,55488],{"class":685},[679,76127,75114],{"class":880},[679,76129,745],{"class":693},[679,76131,76096],{"class":689},[679,76133,1208],{"class":693},[651,76135,76136,76137,76140],{},"But wait, isn't ",[676,76138,76139],{},"fetch()"," a Browser API?",[5909,76142,74169],{"id":76143},"browser-compatible-apis-fetch-window-object",[651,76145,76146,76147,76149,76150,76153],{},"Deno has Brower Compatible API's like ",[676,76148,76139],{}," and the ",[676,76151,76152],{},"Window"," object built into the runtime. This is important because as a JavaScript developer that already understands fetch you don't have to learn something new or install a third-party library. For APIs where a web standard already exists, like fetch for HTTP requests, Deno uses these rather than inventing a new proprietary API.",[651,76155,76156,76157,76159,76160,76162],{},"If you combine ",[676,76158,76139],{}," with ",[676,76161,76055],{}," it becomes simple and clean to make a request and get some data back.",[669,76164,76166],{"className":75361,"code":76165,"language":29038,"meta":674,"style":674},"const todos = await fetch(\n \"https://jsonplaceholder.typicode.com/todos\"\n).then(response => response.json());\nconsole.table(todos);\n",[676,76167,76168,76183,76188,76206],{"__ignoreMap":674},[679,76169,76170,76172,76175,76177,76179,76181],{"class":681,"line":682},[679,76171,45172],{"class":685},[679,76173,76174],{"class":931}," todos",[679,76176,6883],{"class":685},[679,76178,55488],{"class":685},[679,76180,75114],{"class":880},[679,76182,21337],{"class":693},[679,76184,76185],{"class":681,"line":790},[679,76186,76187],{"class":689}," \"https://jsonplaceholder.typicode.com/todos\"\n",[679,76189,76190,76192,76194,76196,76198,76200,76202,76204],{"class":681,"line":892},[679,76191,47213],{"class":693},[679,76193,46728],{"class":880},[679,76195,745],{"class":693},[679,76197,10447],{"class":2099},[679,76199,44760],{"class":685},[679,76201,46743],{"class":693},[679,76203,28441],{"class":880},[679,76205,9431],{"class":693},[679,76207,76208,76210,76212],{"class":681,"line":901},[679,76209,45224],{"class":693},[679,76211,1031],{"class":880},[679,76213,76214],{"class":693},"(todos);\n",[651,76216,76217],{},[660,76218],{"alt":76219,"src":76220},"Brower Compatible Fetch API","/images/blog/2020/05/29/todos-table.png",[651,76222,76223],{},"The subset of Deno programs which are written completely in JavaScript and do not use the global Deno namespace (or feature test for it), ought to also be able to be run in a modern web browser without change. Deno 1.0 provides the following web-compatible APIs.",[5316,76225,76226,76228,76231,76234,76237,76240,76243,76245,76248,76250,76252,76255,76258,76261,76264,76267,76270,76273,76276,76279,76282,76285,76288,76291,76294,76297,76300,76303,76306,76309,76312,76315,76318,76321,76324,76327,76330,76333,76336,76339],{},[5332,76227,64879],{},[5332,76229,76230],{},"atob",[5332,76232,76233],{},"btoa",[5332,76235,76236],{},"clearInterval",[5332,76238,76239],{},"clearTimeout",[5332,76241,76242],{},"dispatchEvent",[5332,76244,46713],{},[5332,76246,76247],{},"queueMicrotask",[5332,76249,72330],{},[5332,76251,57657],{},[5332,76253,76254],{},"setTimeout",[5332,76256,76257],{},"AbortSignal",[5332,76259,76260],{},"Blob",[5332,76262,76263],{},"File",[5332,76265,76266],{},"FormData",[5332,76268,76269],{},"Headers",[5332,76271,76272],{},"ReadableStream",[5332,76274,76275],{},"Request",[5332,76277,76278],{},"Response",[5332,76280,76281],{},"URL",[5332,76283,76284],{},"URLSearchParams",[5332,76286,76287],{},"console",[5332,76289,76290],{},"isConsoleInstance",[5332,76292,76293],{},"location",[5332,76295,76296],{},"onload",[5332,76298,76299],{},"onunload",[5332,76301,76302],{},"self",[5332,76304,76305],{},"window",[5332,76307,76308],{},"AbortController",[5332,76310,76311],{},"CustomEvent",[5332,76313,76314],{},"DOMException",[5332,76316,76317],{},"ErrorEvent",[5332,76319,76320],{},"Event",[5332,76322,76323],{},"EventTarget",[5332,76325,76326],{},"MessageEvent",[5332,76328,76329],{},"TextDecoder",[5332,76331,76332],{},"TextEncoder",[5332,76334,76335],{},"Worker",[5332,76337,76338],{},"ImportMeta",[5332,76340,76341],{},"Location",[5909,76343,74175],{"id":76344},"ecmascript-modules-es-modules",[651,76346,76347],{},"This is probably the biggest fundamental difference between Node and Deno. Node created it's own module system called Common JS. The way this works is the code that is being executed and code that it depends on both live in the same physical location so the server can stop what its doing, load the dependency and pick up where it left off. This is known as synchronous module loading and because this is done from disk it is extremely efficient.",[651,76349,76350],{},"The browser works very different from this model. When we execute our code we are doing so on our user's device. Those devices are not on the same machine as it's dependencies, in fact they are far across the network. So when our code that is running and it needs to load a dependency it can't just stop what its doing because the process of fetching it across the network can be very slow.",[651,76352,76353],{},"Node and the browser are two different runtimes and Common JS could never work in the browser. So the browser needed its own module system and this is where the design specification for ES Modules came from. Deno uses the official ECMAScript module standard rather than legacy CommonJS.",[651,76355,76356,76357,76359],{},"When your application depends on another module you can bring it into a script using the ",[676,76358,1999],{}," keyword. The module is referenced using a URL or file path and includes a mandatory file extension.",[669,76361,76363],{"className":75361,"code":76362,"language":29038,"meta":674,"style":674},"import * as log from \"https://deno.land/std/log/mod.ts\";\nimport { serve } from \"https://deno.land/std/http/server.ts\";\nimport { foo } from \"./app.ts\";\n",[676,76364,76365,76383,76396],{"__ignoreMap":674},[679,76366,76367,76369,76371,76373,76376,76378,76381],{"class":681,"line":682},[679,76368,1999],{"class":685},[679,76370,14963],{"class":931},[679,76372,24745],{"class":685},[679,76374,76375],{"class":693}," log ",[679,76377,28887],{"class":685},[679,76379,76380],{"class":689}," \"https://deno.land/std/log/mod.ts\"",[679,76382,1186],{"class":693},[679,76384,76385,76387,76389,76391,76394],{"class":681,"line":790},[679,76386,1999],{"class":685},[679,76388,75371],{"class":693},[679,76390,28887],{"class":685},[679,76392,76393],{"class":689}," \"https://deno.land/std/http/server.ts\"",[679,76395,1186],{"class":693},[679,76397,76398,76400,76403,76405,76408],{"class":681,"line":892},[679,76399,1999],{"class":685},[679,76401,76402],{"class":693}," { foo } ",[679,76404,28887],{"class":685},[679,76406,76407],{"class":689}," \"./app.ts\"",[679,76409,1186],{"class":693},[5259,76411,76413],{"id":76412},"goodbye-node_modules","Goodbye 👋🏻 node_modules",[651,76415,76416,76417,64811],{},"You just saw an example of importing an ES Module directly from a URL or file path. This means that we no longer need a package manager like NPM (Node Package Manager) or the project bloat that comes along with the ",[676,76418,60242],{},[651,76420,76421],{},[660,76422],{"alt":76423,"src":76424},"Node Modules","/images/blog/2020/05/29/node-modules.jpeg",[651,76426,76427],{},"This is probably going to be something that developers debate about the most when it comes to Deno. Rather than relying on a central repository, it is decentralized. This means that anyone can host any type of file on the web, anywhere. There are pros and cons to this and I will need more time to share my personal thoughts on this. If you're looking for a collection of Third Party Modules you can find them on the official Deno website.",[651,76429,76430],{},[812,76431,76432],{"href":76432,"rel":76433},"https://deno.land/x",[816],[5909,76435,74181],{"id":76436},"built-in-tools",[651,76438,76439],{},"Another feature I am really excited about is the set of built-in tools. This is a collection of tools that will help improve the developer experience.",[5316,76441,76442,76449,76456,76463,76470,76477,76484],{},[5332,76443,76444,76445,76448],{},"bundler (",[676,76446,76447],{},"deno bundle",") will output a single JavaScript file, which includes all dependencies of the specified input. For example:",[5332,76450,76451,76452,76455],{},"debugger (",[676,76453,76454],{},"--inspect, --inspect-brk",") will support debugging using Chrome Devtools or other clients that support the protocol (eg. VSCode).",[5332,76457,76458,76459,76462],{},"dependency inspector (",[676,76460,76461],{},"deno info",") will inspect ES module and all of its dependencies.",[5332,76464,76465,76466,76469],{},"documentation generator (",[676,76467,76468],{},"deno doc",") will generate documentation for Deno",[5332,76471,76472,76473,76476],{},"formatter (",[676,76474,76475],{},"deno fmt",") a built-in code formatter that auto-formats TypeScript and JavaScript code.",[5332,76478,76479,76480,76483],{},"test runner (",[676,76481,76482],{},"deno test",") a built-in test runner that you can use for testing JavaScript or TypeScript code.",[5332,76485,76486,76487,76490],{},"linter (",[676,76488,76489],{},"deno lint",") coming soon",[4542,76492,9042],{"id":9041},[651,76494,76495],{},"I know a lot of developers reading this might be asking the question \"Is this going to replace Node\". The short answer is no and I think that is probably the wrong question to be asking. Node is a well-established runtime and relied upon by many so It won't be going away anytime soon.",[651,76497,76498],{},"The question you should be asking yourself is \"Do the features in Deno solve any of the problems that I currently have with Node?\" and if the answer to that is yes or you just like learning something new, give Deno a look.",[786,76500,76501],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":76503},[76504,76505,76506,76511,76521],{"id":48139,"depth":790,"text":72861},{"id":74189,"depth":790,"text":74190},{"id":74227,"depth":790,"text":74125,"children":76507},[76508,76509,76510],{"id":74411,"depth":892,"text":74412},{"id":75143,"depth":892,"text":75144},{"id":75165,"depth":892,"text":75166},{"id":75297,"depth":790,"text":74131,"children":76512},[76513,76514,76515,76516,76517,76518,76519,76520],{"id":75334,"depth":892,"text":74139},{"id":75608,"depth":892,"text":74145},{"id":75669,"depth":892,"text":74151},{"id":75820,"depth":892,"text":74157},{"id":76042,"depth":892,"text":74163},{"id":76143,"depth":892,"text":74169},{"id":76344,"depth":892,"text":74175},{"id":76436,"depth":892,"text":74181},{"id":9041,"depth":790,"text":9042},"An introduction to Deno which is a secure runtime for JavaScript and TypeScript",{"slug":75165,"date":76524,"published":797,"author":798,"tags":76525,"cover":76526},"2020-05-29T12:00:00.000Z",[74435,25134],"./hello-deno-cover.png",{"title":432,"description":76522},"blog/2020/05/29/hello-deno","iUdkDX_HaFJLopKLaEN1Ipor9wdFKhZtl_1crDGiAjw",{"id":76531,"title":429,"body":76532,"description":77416,"extension":793,"meta":77417,"navigation":797,"path":430,"seo":77422,"stem":77423,"__hash__":77424},"content/blog/2020/06/03/deno-standard-in-out.md",{"type":648,"value":76533,"toc":77407},[76534,76543,76552,76556,76567,76571,76587,76607,76610,76701,76723,76727,76733,76751,76765,76774,76797,76815,76848,76851,76855,76863,76904,76920,76971,76974,77025,77035,77063,77066,77153,77157,77160,77302,77305,77389,77391,77400,77404],[651,76535,76536,76537,76542],{},"I recently posted a ",[812,76538,76541],{"href":76539,"rel":76540},"https://www.danvega.dev/blog/2020/05/29/hello-deno/",[816],"introduction to Deno"," where I walk through how to get Deno installed and the features I think make it an exciting project. The next step for me was to start building something with Deno.",[651,76544,76545,76546,76551],{},"The first project I took a look at was a node script I used to ",[812,76547,76550],{"href":76548,"rel":76549},"https://www.danvega.dev/blog/2019/04/23/gridsome-blog-post-generator/",[816],"generate new blog posts"," in Gridsome. Before I tackle the entire script I want to use this tutorial to discuss how you can work with standard input and output in Deno.",[4542,76553,76555],{"id":76554},"standard-input-standard-output","Standard Input & Standard Output",[651,76557,76558,76559,76562,76563,76566],{},"Before you write any code I think it is important for you to understand what standard in and standard out are. Standard output, sometimes abbreviated ",[676,76560,76561],{},"stdout",", refers to the standardized stream to which a program writes its output data. Standard input, sometimes abbreviated ",[676,76564,76565],{},"stdin"," is a stream from which a program reads its input data. Without getting into low level abstractions this is way to read and write data.",[4542,76568,76570],{"id":76569},"deno-stdin-stdout","Deno stdin & stdout",[651,76572,76573,76574,51393,76580,76586],{},"Now that you have an understanding of what standard in & out are let's talk about them in the context of Deno. If you want to get a handle for ",[812,76575,76578],{"href":76576,"rel":76577},"https://doc.deno.land/https/github.com/denoland/deno/releases/latest/download/lib.deno.d.ts#Deno.stdin",[816],[676,76579,76565],{},[812,76581,76584],{"href":76582,"rel":76583},"https://doc.deno.land/https/github.com/denoland/deno/releases/latest/download/lib.deno.d.ts#Deno.stdout",[816],[676,76585,76561],{}," you can use the properties from the Deno Runtime API:",[669,76588,76591],{"className":76589,"code":76590,"language":35538,"meta":674,"style":674},"language-properties shiki shiki-themes github-light github-dark github-light","Deno.stdin;\nDeno.stdout;\n",[676,76592,76593,76600],{"__ignoreMap":674},[679,76594,76595,76598],{"class":681,"line":682},[679,76596,76597],{"class":693},"Deno.stdin",[679,76599,1186],{"class":1400},[679,76601,76602,76605],{"class":681,"line":790},[679,76603,76604],{"class":693},"Deno.stdout",[679,76606,1186],{"class":1400},[651,76608,76609],{},"If you take a look at the source for Deno you will see the following:",[669,76611,76613],{"className":75361,"code":76612,"language":29038,"meta":674,"style":674},"/** A handle for `stdin`. */\nexport const stdin: Reader & ReaderSync & Closer & { rid: number };\n/** A handle for `stdout`. */\nexport const stdout: Writer & WriterSync & Closer & { rid: number };\n",[676,76614,76615,76620,76661,76666],{"__ignoreMap":674},[679,76616,76617],{"class":681,"line":682},[679,76618,76619],{"class":1400},"/** A handle for `stdin`. */\n",[679,76621,76622,76624,76627,76630,76632,76635,76638,76641,76643,76646,76648,76650,76653,76655,76658],{"class":681,"line":790},[679,76623,29245],{"class":685},[679,76625,76626],{"class":685}," const",[679,76628,76629],{"class":931}," stdin",[679,76631,2391],{"class":685},[679,76633,76634],{"class":880}," Reader",[679,76636,76637],{"class":685}," &",[679,76639,76640],{"class":880}," ReaderSync",[679,76642,76637],{"class":685},[679,76644,76645],{"class":880}," Closer",[679,76647,76637],{"class":685},[679,76649,2998],{"class":693},[679,76651,76652],{"class":2099},"rid",[679,76654,2391],{"class":685},[679,76656,76657],{"class":931}," number",[679,76659,76660],{"class":693}," };\n",[679,76662,76663],{"class":681,"line":892},[679,76664,76665],{"class":1400},"/** A handle for `stdout`. */\n",[679,76667,76668,76670,76672,76675,76677,76680,76682,76685,76687,76689,76691,76693,76695,76697,76699],{"class":681,"line":901},[679,76669,29245],{"class":685},[679,76671,76626],{"class":685},[679,76673,76674],{"class":931}," stdout",[679,76676,2391],{"class":685},[679,76678,76679],{"class":880}," Writer",[679,76681,76637],{"class":685},[679,76683,76684],{"class":880}," WriterSync",[679,76686,76637],{"class":685},[679,76688,76645],{"class":880},[679,76690,76637],{"class":685},[679,76692,2998],{"class":693},[679,76694,76652],{"class":2099},[679,76696,2391],{"class":685},[679,76698,76657],{"class":931},[679,76700,76660],{"class":693},[651,76702,76703,76704,76706,76707,2797,76710,23212,76713,76716,76717,76722],{},"If you're new to TypeScript that probably looks a little confusing but don't let it scare you. Deno is declaring a variable called ",[676,76705,76565],{}," that is a type that combines ",[676,76708,76709],{},"Reader",[676,76711,76712],{},"ReaderSync",[676,76714,76715],{},"Closer"," and an object with a property called rid. In TypeScript these are called ",[812,76718,76721],{"href":76719,"rel":76720},"https://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types",[816],"Intersection Types"," and they are pretty darn cool once you start using them.",[5909,76724,76726],{"id":76725},"deno-standard-output","Deno Standard Output",[651,76728,76729,76730,664],{},"So you know about standard out and how to get a handle on it in Deno. I want to come back to the problem at hand and that is replacing a blog post generator I wrote in Node. To do so the first task will be to ask the user (me) for some information about the post. Deno is trying to stay compatible with Browser APIs so one option we have is to just use ",[676,76731,76732],{},"console.log()",[669,76734,76736],{"className":64365,"code":76735,"language":64367,"meta":674,"style":674},"console.log(\"Post Title:\");\n",[676,76737,76738],{"__ignoreMap":674},[679,76739,76740,76742,76744,76746,76749],{"class":681,"line":682},[679,76741,45224],{"class":693},[679,76743,21374],{"class":880},[679,76745,745],{"class":693},[679,76747,76748],{"class":689},"\"Post Title:\"",[679,76750,1208],{"class":693},[651,76752,76753,76754,76756,76757,76760,76761,76764],{},"The problem with this is you are always going to get a new line feed so the user won't be able to type a response on the same line which isn't what I want. No worries though, now that we know ",[676,76755,76561],{}," is of type ",[676,76758,76759],{},"Writer"," which exports a single method ",[676,76762,76763],{},"write()"," we can just use that.",[651,76766,76767,76768,76770,76771,76773],{},"With that said, ",[676,76769,76763],{}," doesn't take a simple string so it won't be that easy. This method will write p.byteLength bytes from p to the underlying data stream. This method returns a promise but with ",[676,76772,76055],{}," support this will be easy.",[669,76775,76777],{"className":75361,"code":76776,"language":29038,"meta":674,"style":674},"write(p: Uint8Array): Promise\u003Cnumber>\n",[676,76778,76779],{"__ignoreMap":674},[679,76780,76781,76784,76787,76790,76792,76795],{"class":681,"line":682},[679,76782,76783],{"class":880},"write",[679,76785,76786],{"class":693},"(p: Uint8Array): ",[679,76788,76789],{"class":931},"Promise",[679,76791,4505],{"class":685},[679,76793,76794],{"class":693},"number",[679,76796,4519],{"class":685},[651,76798,76799,76800,76806,76807,76810,76811,76814],{},"So now we just need to figure out how to pass in a simple string to the write method. It turns out there is a class in the Runtime API to help us out with this called ",[812,76801,76804],{"href":76802,"rel":76803},"https://doc.deno.land/https/github.com/denoland/deno/releases/latest/download/lib.deno.d.ts#TextEncoder",[816],[676,76805,76332],{},". This class has a method called ",[676,76808,76809],{},"encode()"," that will return a ",[676,76812,76813],{},"Uint8Array"," which is exactly what we are looking for! Putting this all together we can prompt the user for the post title:",[669,76816,76818],{"className":75361,"code":76817,"language":29038,"meta":674,"style":674},"await Deno.stdout.write(new TextEncoder().encode(\"Post Title: \"));\n",[676,76819,76820],{"__ignoreMap":674},[679,76821,76822,76824,76827,76829,76831,76833,76836,76838,76841,76843,76846],{"class":681,"line":682},[679,76823,76059],{"class":685},[679,76825,76826],{"class":693}," Deno.stdout.",[679,76828,76783],{"class":880},[679,76830,745],{"class":693},[679,76832,8930],{"class":685},[679,76834,76835],{"class":880}," TextEncoder",[679,76837,10541],{"class":693},[679,76839,76840],{"class":880},"encode",[679,76842,745],{"class":693},[679,76844,76845],{"class":689},"\"Post Title: \"",[679,76847,1669],{"class":693},[651,76849,76850],{},"If you were to run that it will print prompt for the post title but because there is nothing to follow the program just ends. Now we need to actually read in the response.",[5909,76852,76854],{"id":76853},"deno-standard-input","Deno Standard Input",[651,76856,76857,76858,76860,76861,664],{},"Looking back at the source for ",[676,76859,76565],{}," you can see that is of type ",[676,76862,76709],{},[669,76864,76866],{"className":75361,"code":76865,"language":29038,"meta":674,"style":674},"/** A handle for `stdin`. */\nexport const stdin: Reader & ReaderSync & Closer & { rid: number };\n",[676,76867,76868,76872],{"__ignoreMap":674},[679,76869,76870],{"class":681,"line":682},[679,76871,76619],{"class":1400},[679,76873,76874,76876,76878,76880,76882,76884,76886,76888,76890,76892,76894,76896,76898,76900,76902],{"class":681,"line":790},[679,76875,29245],{"class":685},[679,76877,76626],{"class":685},[679,76879,76629],{"class":931},[679,76881,2391],{"class":685},[679,76883,76634],{"class":880},[679,76885,76637],{"class":685},[679,76887,76640],{"class":880},[679,76889,76637],{"class":685},[679,76891,76645],{"class":880},[679,76893,76637],{"class":685},[679,76895,2998],{"class":693},[679,76897,76652],{"class":2099},[679,76899,2391],{"class":685},[679,76901,76657],{"class":931},[679,76903,76660],{"class":693},[651,76905,76906,76907,76909,76910,76912,76913,76915,76916,76919],{},"Reader is an interface that has a single method that takes in a ",[676,76908,76813],{},". It resolves to the number of bytes read (",[676,76911,1060],{}," \u003C ",[676,76914,8722],{}," \u003C= ",[676,76917,76918],{},"p.byteLength",") and rejects if any error encountered.",[669,76921,76923],{"className":75361,"code":76922,"language":29038,"meta":674,"style":674},"export interface Reader {\n read(p: Uint8Array): Promise\u003Cnumber | null>;\n}\n",[676,76924,76925,76935,76967],{"__ignoreMap":674},[679,76926,76927,76929,76931,76933],{"class":681,"line":682},[679,76928,29245],{"class":685},[679,76930,6994],{"class":685},[679,76932,76634],{"class":880},[679,76934,884],{"class":693},[679,76936,76937,76940,76942,76944,76946,76949,76951,76953,76956,76958,76960,76962,76964],{"class":681,"line":790},[679,76938,76939],{"class":880}," read",[679,76941,745],{"class":693},[679,76943,651],{"class":2099},[679,76945,2391],{"class":685},[679,76947,76948],{"class":880}," Uint8Array",[679,76950,50653],{"class":693},[679,76952,2391],{"class":685},[679,76954,76955],{"class":880}," Promise",[679,76957,4505],{"class":693},[679,76959,76794],{"class":931},[679,76961,42344],{"class":685},[679,76963,2307],{"class":931},[679,76965,76966],{"class":693},">;\n",[679,76968,76969],{"class":681,"line":892},[679,76970,996],{"class":693},[651,76972,76973],{},"The first thing you need to do is create a buffer and pass that in to the read method. Remember the read method will return a Promise with number of bytes read so we are going to wait for that result.",[669,76975,76977],{"className":75361,"code":76976,"language":29038,"meta":674,"style":674},"const buf = new Uint8Array(1024);\nconst n = \u003Cnumber>await Deno.stdin.read(buf);\n",[676,76978,76979,76999],{"__ignoreMap":674},[679,76980,76981,76983,76986,76988,76990,76992,76994,76997],{"class":681,"line":682},[679,76982,45172],{"class":685},[679,76984,76985],{"class":931}," buf",[679,76987,6883],{"class":685},[679,76989,2054],{"class":685},[679,76991,76948],{"class":880},[679,76993,745],{"class":693},[679,76995,76996],{"class":931},"1024",[679,76998,1208],{"class":693},[679,77000,77001,77003,77006,77008,77010,77012,77014,77016,77019,77022],{"class":681,"line":790},[679,77002,45172],{"class":685},[679,77004,77005],{"class":931}," n",[679,77007,6883],{"class":685},[679,77009,48609],{"class":693},[679,77011,76794],{"class":931},[679,77013,5860],{"class":693},[679,77015,76059],{"class":685},[679,77017,77018],{"class":693}," Deno.stdin.",[679,77020,77021],{"class":880},"read",[679,77023,77024],{"class":693},"(buf);\n",[651,77026,77027,77028,77030,77031,77034],{},"Finally you need a way to turn that byte array into a string. The ",[676,77029,76329],{}," class has a method ",[676,77032,77033],{},"decode()"," that can help us out with that.",[669,77036,77038],{"className":75361,"code":77037,"language":29038,"meta":674,"style":674},"decode(input?: BufferSource, options?: { stream: false }): string\n",[676,77039,77040],{"__ignoreMap":674},[679,77041,77042,77045,77048,77050,77053,77055,77058,77060],{"class":681,"line":682},[679,77043,77044],{"class":880},"decode",[679,77046,77047],{"class":693},"(input",[679,77049,2422],{"class":685},[679,77051,77052],{"class":693}," BufferSource, options",[679,77054,2422],{"class":685},[679,77056,77057],{"class":693}," { stream: ",[679,77059,1135],{"class":931},[679,77061,77062],{"class":693}," }): string\n",[651,77064,77065],{},"You will pass in the byte array and extract the positions that contain a value.",[669,77067,77069],{"className":75361,"code":77068,"language":29038,"meta":674,"style":674},"const buf = new Uint8Array(1024);\nconst n = \u003Cnumber>await Deno.stdin.read(buf);\nconst title = new TextDecoder().decode(buf.subarray(0, n)).trim();\nconsole.log(title);\n",[676,77070,77071,77089,77111,77145],{"__ignoreMap":674},[679,77072,77073,77075,77077,77079,77081,77083,77085,77087],{"class":681,"line":682},[679,77074,45172],{"class":685},[679,77076,76985],{"class":931},[679,77078,6883],{"class":685},[679,77080,2054],{"class":685},[679,77082,76948],{"class":880},[679,77084,745],{"class":693},[679,77086,76996],{"class":931},[679,77088,1208],{"class":693},[679,77090,77091,77093,77095,77097,77099,77101,77103,77105,77107,77109],{"class":681,"line":790},[679,77092,45172],{"class":685},[679,77094,77005],{"class":931},[679,77096,6883],{"class":685},[679,77098,48609],{"class":693},[679,77100,76794],{"class":931},[679,77102,5860],{"class":693},[679,77104,76059],{"class":685},[679,77106,77018],{"class":693},[679,77108,77021],{"class":880},[679,77110,77024],{"class":693},[679,77112,77113,77115,77117,77119,77121,77124,77126,77128,77131,77134,77136,77138,77141,77143],{"class":681,"line":892},[679,77114,45172],{"class":685},[679,77116,5353],{"class":931},[679,77118,6883],{"class":685},[679,77120,2054],{"class":685},[679,77122,77123],{"class":880}," TextDecoder",[679,77125,10541],{"class":693},[679,77127,77044],{"class":880},[679,77129,77130],{"class":693},"(buf.",[679,77132,77133],{"class":880},"subarray",[679,77135,745],{"class":693},[679,77137,1060],{"class":931},[679,77139,77140],{"class":693},", n)).",[679,77142,55969],{"class":880},[679,77144,9317],{"class":693},[679,77146,77147,77149,77151],{"class":681,"line":901},[679,77148,45224],{"class":693},[679,77150,21374],{"class":880},[679,77152,55747],{"class":693},[4542,77154,77156],{"id":77155},"deno-writing-standard-out-and-reading-standard-in","Deno: Writing Standard Out and Reading Standard In",[651,77158,77159],{},"We could just repeat those same steps for each field we wanted to prompt the user for but that would get pretty repetitive. Whenever you start duplicating code an alarm should go off in your head and mine did here. Let's wrap our logic up into a function:",[669,77161,77163],{"className":75361,"code":77162,"language":29038,"meta":674,"style":674},"/*\n * Prompt for a response\n */\nasync function prompt(message: string = \"\") {\n const buf = new Uint8Array(1024);\n await Deno.stdout.write(new TextEncoder().encode(message + \": \"));\n const n = \u003Cnumber>await Deno.stdin.read(buf);\n return new TextDecoder().decode(buf.subarray(0, n)).trim();\n}\n",[676,77164,77165,77170,77175,77180,77203,77221,77250,77272,77298],{"__ignoreMap":674},[679,77166,77167],{"class":681,"line":682},[679,77168,77169],{"class":1400},"/*\n",[679,77171,77172],{"class":681,"line":790},[679,77173,77174],{"class":1400}," * Prompt for a response\n",[679,77176,77177],{"class":681,"line":892},[679,77178,77179],{"class":1400}," */\n",[679,77181,77182,77184,77186,77189,77191,77193,77195,77197,77199,77201],{"class":681,"line":901},[679,77183,55212],{"class":685},[679,77185,21700],{"class":685},[679,77187,77188],{"class":880}," prompt",[679,77190,745],{"class":693},[679,77192,20198],{"class":2099},[679,77194,2391],{"class":685},[679,77196,44488],{"class":931},[679,77198,6883],{"class":685},[679,77200,1183],{"class":689},[679,77202,4390],{"class":693},[679,77204,77205,77207,77209,77211,77213,77215,77217,77219],{"class":681,"line":909},[679,77206,46903],{"class":685},[679,77208,76985],{"class":931},[679,77210,6883],{"class":685},[679,77212,2054],{"class":685},[679,77214,76948],{"class":880},[679,77216,745],{"class":693},[679,77218,76996],{"class":931},[679,77220,1208],{"class":693},[679,77222,77223,77226,77228,77230,77232,77234,77236,77238,77240,77243,77245,77248],{"class":681,"line":918},[679,77224,77225],{"class":685}," await",[679,77227,76826],{"class":693},[679,77229,76783],{"class":880},[679,77231,745],{"class":693},[679,77233,8930],{"class":685},[679,77235,76835],{"class":880},[679,77237,10541],{"class":693},[679,77239,76840],{"class":880},[679,77241,77242],{"class":693},"(message ",[679,77244,3065],{"class":685},[679,77246,77247],{"class":689}," \": \"",[679,77249,1669],{"class":693},[679,77251,77252,77254,77256,77258,77260,77262,77264,77266,77268,77270],{"class":681,"line":935},[679,77253,46903],{"class":685},[679,77255,77005],{"class":931},[679,77257,6883],{"class":685},[679,77259,48609],{"class":693},[679,77261,76794],{"class":931},[679,77263,5860],{"class":693},[679,77265,76059],{"class":685},[679,77267,77018],{"class":693},[679,77269,77021],{"class":880},[679,77271,77024],{"class":693},[679,77273,77274,77276,77278,77280,77282,77284,77286,77288,77290,77292,77294,77296],{"class":681,"line":944},[679,77275,44767],{"class":685},[679,77277,2054],{"class":685},[679,77279,77123],{"class":880},[679,77281,10541],{"class":693},[679,77283,77044],{"class":880},[679,77285,77130],{"class":693},[679,77287,77133],{"class":880},[679,77289,745],{"class":693},[679,77291,1060],{"class":931},[679,77293,77140],{"class":693},[679,77295,55969],{"class":880},[679,77297,9317],{"class":693},[679,77299,77300],{"class":681,"line":959},[679,77301,996],{"class":693},[651,77303,77304],{},"And these are the values I would like to ask the user for when I run the script:",[669,77306,77308],{"className":64365,"code":77307,"language":64367,"meta":674,"style":674},"const title = await prompt(\"Post Title\");\nconst manualSlug = await prompt(\"Slug (Leave blank to generate)\");\nconst excerpt = await prompt(\"Post Excerpt\");\nconst tags = await prompt(\"Tags (comma separated)\");\n",[676,77309,77310,77329,77349,77369],{"__ignoreMap":674},[679,77311,77312,77314,77316,77318,77320,77322,77324,77327],{"class":681,"line":682},[679,77313,45172],{"class":685},[679,77315,5353],{"class":931},[679,77317,6883],{"class":685},[679,77319,55488],{"class":685},[679,77321,77188],{"class":880},[679,77323,745],{"class":693},[679,77325,77326],{"class":689},"\"Post Title\"",[679,77328,1208],{"class":693},[679,77330,77331,77333,77336,77338,77340,77342,77344,77347],{"class":681,"line":790},[679,77332,45172],{"class":685},[679,77334,77335],{"class":931}," manualSlug",[679,77337,6883],{"class":685},[679,77339,55488],{"class":685},[679,77341,77188],{"class":880},[679,77343,745],{"class":693},[679,77345,77346],{"class":689},"\"Slug (Leave blank to generate)\"",[679,77348,1208],{"class":693},[679,77350,77351,77353,77356,77358,77360,77362,77364,77367],{"class":681,"line":892},[679,77352,45172],{"class":685},[679,77354,77355],{"class":931}," excerpt",[679,77357,6883],{"class":685},[679,77359,55488],{"class":685},[679,77361,77188],{"class":880},[679,77363,745],{"class":693},[679,77365,77366],{"class":689},"\"Post Excerpt\"",[679,77368,1208],{"class":693},[679,77370,77371,77373,77376,77378,77380,77382,77384,77387],{"class":681,"line":901},[679,77372,45172],{"class":685},[679,77374,77375],{"class":931}," tags",[679,77377,6883],{"class":685},[679,77379,55488],{"class":685},[679,77381,77188],{"class":880},[679,77383,745],{"class":693},[679,77385,77386],{"class":689},"\"Tags (comma separated)\"",[679,77388,1208],{"class":693},[4542,77390,9042],{"id":9041},[651,77392,77393,77394,77399],{},"I hope this article helped you understand how to use standard in and standard out in Deno. I want to give a huge shout out to József Sallai who create the ",[812,77395,77398],{"href":77396,"rel":77397},"https://github.com/jozsefsallai/ask/blob/master/README.md",[816],"ask library",". It was a combination of digging through that code and the Deno documentation that I was able to piece this together.",[651,77401,41105,77402,41109],{},[41107,77403],{},[786,77405,77406],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":77408},[77409,77410,77414,77415],{"id":76554,"depth":790,"text":76555},{"id":76569,"depth":790,"text":76570,"children":77411},[77412,77413],{"id":76725,"depth":892,"text":76726},{"id":76853,"depth":892,"text":76854},{"id":77155,"depth":790,"text":77156},{"id":9041,"depth":790,"text":9042},"In this tutorial I will show you how to work with standard input and output in Deno.",{"slug":77418,"date":77419,"published":797,"author":798,"tags":77420,"cover":77421},"deno-standard-in-out","2020-06-03T20:00:00.500Z",[74309,46520],"./deno_stdin_stdout-cover.png",{"title":429,"description":77416},"blog/2020/06/03/deno-standard-in-out","hp3-POkFIWVOUiemrJFKVg0L-jC_DMRsS-Gj_CAPduM",{"id":77426,"title":426,"body":77427,"description":78038,"extension":793,"meta":78039,"navigation":797,"path":427,"seo":78046,"stem":78047,"__hash__":78048},"content/blog/2020/06/04/spring-boot-rest-service.md",{"type":648,"value":77428,"toc":78028},[77429,77433,77442,77446,77449,77457,77460,77464,77467,77470,77509,77514,77517,77521,77524,77531,77558,77705,77711,77715,77722,77727,77777,77927,77933,77937,77951,77965,77969,77972,77975,77997,78003,78007,78010,78019,78022,78025],[4542,77430,77432],{"id":77431},"a-comprehensive-guide-to-building-restful-web-service-with-spring-boot","A Comprehensive Guide to Building RESTful Web Service with Spring Boot",[651,77434,77435,77436,77441],{},"Today, we'll be going over a tutorial on how to build a RESTful web service using Spring Boot. This is perfect for visual learners who may prefer to start with a tutorial before diving into the actual ",[812,77437,77440],{"href":77438,"rel":77439},"https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/",[816],"Spring.io documentation",". So, let's dive into the tutorial and start building our web service.",[4542,77443,77445],{"id":77444},"building-a-restful-web-service-prerequisites","Building a RESTful Web Service: Prerequisites",[651,77447,77448],{},"Before we get started, let's make sure you have the following tools installed and ready to go:",[5316,77450,77451,77454],{},[5332,77452,77453],{},"A favorite text editor or IDE (we'll be using IntelliJ Ultimate Edition)",[5332,77455,77456],{},"JDK 1.8 or later",[651,77458,77459],{},"With those in place, we're ready to get started on building our web service!",[4542,77461,77463],{"id":77462},"step-1-setting-up-the-project","Step 1: Setting Up the Project",[651,77465,77466],{},"To ensure a smooth start, we'll begin by creating our project using the Spring Initializer. This will automatically configure our build system, dependencies, and folder structure for us.",[651,77468,77469],{},"Here's what to do:",[27665,77471,77472,77479,77487,77497,77506],{},[5332,77473,77474,77475],{},"Head to ",[812,77476,77478],{"href":7115,"rel":77477},[816],"start.spring.io",[5332,77480,77481,77482,2797,77484,77486],{},"Select ",[676,77483,38919],{},[676,77485,36422],{},", and choose the latest stable version of Spring Boot",[5332,77488,77489,77490,77493,77494],{},"Set the Group to ",[676,77491,77492],{},"com.example"," and Artifact to ",[676,77495,77496],{},"rest-service",[5332,77498,77499,77500,77502,77503],{},"Add the ",[676,77501,72926],{}," dependency and click ",[676,77504,77505],{},"Generate",[5332,77507,77508],{},"Download and unzip the generated project",[651,77510,77511],{},[660,77512],{"alt":7117,"src":77513},"/images/blog/2020/06/04/start-spring-io.png",[651,77515,77516],{},"Once that's done, open the project in your favorite IDE, and you're ready for the next step.",[4542,77518,77520],{"id":77519},"step-2-creating-a-model","Step 2: Creating a Model",[651,77522,77523],{},"With our project structure in place, we need to create a model class to describe the data we'll be working with. In this case, it will be a simple greeting with an ID and some content.",[651,77525,77526,77527,77530],{},"To create a ",[676,77528,77529],{},"Greeting"," class with the appropriate fields, follow these steps:",[27665,77532,77533,77540,77545,77555],{},[5332,77534,77535,77536,77539],{},"Navigate to the ",[676,77537,77538],{},"main"," package in your project",[5332,77541,77542,77543],{},"Create a new class named ",[676,77544,77529],{},[5332,77546,77547,77548,23212,77551,77554],{},"Add ",[676,77549,77550],{},"private final long id",[676,77552,77553],{},"private final String content"," as fields",[5332,77556,77557],{},"Generate a constructor and getters for the fields",[669,77559,77561],{"className":4107,"code":77560,"language":4109,"meta":674,"style":674},"public class Greeting {\n\n private final long id;\n private final String content;\n\n public Greeting(long id, String content) {\n this.id = id;\n this.content = content;\n }\n\n public long getId() {\n return id;\n }\n\n public String getContent() {\n return content;\n }\n\n}\n",[676,77562,77563,77574,77578,77589,77598,77602,77621,77631,77643,77647,77651,77662,77668,77672,77676,77687,77693,77697,77701],{"__ignoreMap":674},[679,77564,77565,77567,77569,77572],{"class":681,"line":682},[679,77566,6073],{"class":685},[679,77568,4512],{"class":685},[679,77570,77571],{"class":880}," Greeting",[679,77573,884],{"class":693},[679,77575,77576],{"class":681,"line":790},[679,77577,889],{"emptyLinePlaceholder":797},[679,77579,77580,77583,77585,77587],{"class":681,"line":892},[679,77581,77582],{"class":685}," private",[679,77584,12768],{"class":685},[679,77586,35740],{"class":685},[679,77588,11318],{"class":693},[679,77590,77591,77593,77595],{"class":681,"line":901},[679,77592,77582],{"class":685},[679,77594,12768],{"class":685},[679,77596,77597],{"class":693}," String content;\n",[679,77599,77600],{"class":681,"line":909},[679,77601,889],{"emptyLinePlaceholder":797},[679,77603,77604,77607,77609,77611,77613,77615,77617,77619],{"class":681,"line":918},[679,77605,77606],{"class":685}," public",[679,77608,77571],{"class":880},[679,77610,745],{"class":693},[679,77612,1088],{"class":685},[679,77614,5578],{"class":2099},[679,77616,20006],{"class":693},[679,77618,47833],{"class":2099},[679,77620,4390],{"class":693},[679,77622,77623,77625,77627,77629],{"class":681,"line":935},[679,77624,27825],{"class":931},[679,77626,11350],{"class":693},[679,77628,686],{"class":685},[679,77630,11318],{"class":693},[679,77632,77633,77635,77638,77640],{"class":681,"line":944},[679,77634,27825],{"class":931},[679,77636,77637],{"class":693},".content ",[679,77639,686],{"class":685},[679,77641,77642],{"class":693}," content;\n",[679,77644,77645],{"class":681,"line":959},[679,77646,21405],{"class":693},[679,77648,77649],{"class":681,"line":964},[679,77650,889],{"emptyLinePlaceholder":797},[679,77652,77653,77655,77657,77660],{"class":681,"line":977},[679,77654,77606],{"class":685},[679,77656,35740],{"class":685},[679,77658,77659],{"class":880}," getId",[679,77661,2667],{"class":693},[679,77663,77664,77666],{"class":681,"line":982},[679,77665,21478],{"class":685},[679,77667,11318],{"class":693},[679,77669,77670],{"class":681,"line":988},[679,77671,21405],{"class":693},[679,77673,77674],{"class":681,"line":993},[679,77675,889],{"emptyLinePlaceholder":797},[679,77677,77678,77680,77682,77685],{"class":681,"line":2129},[679,77679,77606],{"class":685},[679,77681,9289],{"class":693},[679,77683,77684],{"class":880},"getContent",[679,77686,2667],{"class":693},[679,77688,77689,77691],{"class":681,"line":2140},[679,77690,21478],{"class":685},[679,77692,77642],{"class":693},[679,77694,77695],{"class":681,"line":2145},[679,77696,21405],{"class":693},[679,77698,77699],{"class":681,"line":2154},[679,77700,889],{"emptyLinePlaceholder":797},[679,77702,77703],{"class":681,"line":2159},[679,77704,996],{"class":693},[651,77706,77707,77708,77710],{},"And that's it for our ",[676,77709,77529],{}," class! Now, let's move on to creating the controller that will define the behavior of our web service.",[4542,77712,77714],{"id":77713},"step-3-creating-a-greeting-controller","Step 3: Creating a Greeting Controller",[651,77716,77717,77718,77721],{},"In this step, we'll create our ",[676,77719,77720],{},"GreetingController"," class, which will handle the interaction between our web service and its users.",[651,77723,77724,77725,2391],{},"Here's how to set up the ",[676,77726,77720],{},[27665,77728,77729,77733,77737,77742,77752,77760],{},[5332,77730,77535,77731,77539],{},[676,77732,77538],{},[5332,77734,77542,77735],{},[676,77736,77720],{},[5332,77738,77499,77739,77741],{},[676,77740,12329],{}," annotation at the beginning of the class",[5332,77743,77744,77745,77748,77749],{},"Create a ",[676,77746,77747],{},"private static final String template"," with an initial value of ",[676,77750,77751],{},"Hello, %s!",[5332,77753,77744,77754,77748,77757],{},[676,77755,77756],{},"private final AtomicLong counter",[676,77758,77759],{},"new AtomicLong()",[5332,77761,77762,77763,77766,77767,77770,77771,77773,77774],{},"Add a ",[676,77764,77765],{},"greeting"," method with a ",[676,77768,77769],{},"@GetMapping(\"/greeting\")"," annotation and a single ",[676,77772,4758],{}," parameter annotated with ",[676,77775,77776],{},"@RequestParam(value = \"name\", defaultValue = \"World\")",[669,77778,77780],{"className":4107,"code":77779,"language":4109,"meta":674,"style":674},"@RestController\npublic class GreetingController {\n\n private static final String template = \"Hello, %s!\";\n private final AtomicLong counter = new AtomicLong();\n\n @GetMapping(\"/greeting\")\n public Greeting greeting(@RequestParam(value = \"name\", defaultValue = \"World\") String name) {\n return new Greeting(counter.incrementAndGet(), String.format(template, name));\n }\n\n}\n\n",[676,77781,77782,77788,77799,77803,77821,77839,77843,77856,77893,77915,77919,77923],{"__ignoreMap":674},[679,77783,77784,77786],{"class":681,"line":682},[679,77785,4116],{"class":693},[679,77787,9212],{"class":685},[679,77789,77790,77792,77794,77797],{"class":681,"line":790},[679,77791,6073],{"class":685},[679,77793,4512],{"class":685},[679,77795,77796],{"class":880}," GreetingController",[679,77798,884],{"class":693},[679,77800,77801],{"class":681,"line":892},[679,77802,889],{"emptyLinePlaceholder":797},[679,77804,77805,77807,77809,77811,77814,77816,77819],{"class":681,"line":901},[679,77806,9232],{"class":685},[679,77808,6092],{"class":685},[679,77810,12768],{"class":685},[679,77812,77813],{"class":693}," String template ",[679,77815,686],{"class":685},[679,77817,77818],{"class":689}," \"Hello, %s!\"",[679,77820,1186],{"class":693},[679,77822,77823,77825,77827,77830,77832,77834,77837],{"class":681,"line":909},[679,77824,9232],{"class":685},[679,77826,12768],{"class":685},[679,77828,77829],{"class":693}," AtomicLong counter ",[679,77831,686],{"class":685},[679,77833,2054],{"class":685},[679,77835,77836],{"class":880}," AtomicLong",[679,77838,9317],{"class":693},[679,77840,77841],{"class":681,"line":918},[679,77842,889],{"emptyLinePlaceholder":797},[679,77844,77845,77847,77849,77851,77854],{"class":681,"line":935},[679,77846,6872],{"class":693},[679,77848,20852],{"class":685},[679,77850,745],{"class":693},[679,77852,77853],{"class":689},"\"/greeting\"",[679,77855,1339],{"class":693},[679,77857,77858,77860,77863,77865,77867,77869,77871,77873,77875,77878,77880,77882,77884,77887,77889,77891],{"class":681,"line":944},[679,77859,6089],{"class":685},[679,77861,77862],{"class":693}," Greeting ",[679,77864,77765],{"class":880},[679,77866,73246],{"class":693},[679,77868,73249],{"class":685},[679,77870,745],{"class":693},[679,77872,19934],{"class":931},[679,77874,6883],{"class":685},[679,77876,77877],{"class":689}," \"name\"",[679,77879,2797],{"class":693},[679,77881,73254],{"class":931},[679,77883,6883],{"class":685},[679,77885,77886],{"class":689}," \"World\"",[679,77888,73262],{"class":693},[679,77890,16334],{"class":2099},[679,77892,4390],{"class":693},[679,77894,77895,77897,77899,77901,77904,77907,77910,77912],{"class":681,"line":959},[679,77896,9444],{"class":685},[679,77898,2054],{"class":685},[679,77900,77571],{"class":880},[679,77902,77903],{"class":693},"(counter.",[679,77905,77906],{"class":880},"incrementAndGet",[679,77908,77909],{"class":693},"(), String.",[679,77911,17581],{"class":880},[679,77913,77914],{"class":693},"(template, name));\n",[679,77916,77917],{"class":681,"line":964},[679,77918,985],{"class":693},[679,77920,77921],{"class":681,"line":977},[679,77922,889],{"emptyLinePlaceholder":797},[679,77924,77925],{"class":681,"line":982},[679,77926,996],{"class":693},[651,77928,77929,77930,77932],{},"Now that we've set up our ",[676,77931,77720],{},", let's start the application and test our endpoint.",[4542,77934,77936],{"id":77935},"step-4-testing-the-application","Step 4: Testing the Application",[651,77938,77939,77940,77942,77943,77946,77947,77950],{},"To test our application, run the ",[676,77941,77538],{}," method in your ",[676,77944,77945],{},"RestServiceApplication"," class to start the server. You can then access the ",[676,77948,77949],{},"/greeting"," endpoint using a browser or tools like Postman.",[651,77952,77953,77954,77957,77958,77960,77961,77964],{},"Once the application is up and running, navigate to ",[676,77955,77956],{},"http://localhost:8080/greeting"," and verify that you receive the expected JSON response with an ID and content. You can also test the ",[676,77959,16334],{}," parameter by appending ",[676,77962,77963],{},"?name=YourName"," to the endpoint URL.",[4542,77966,77968],{"id":77967},"step-5-building-an-executable-jar","Step 5: Building an Executable JAR",[651,77970,77971],{},"Now that our web service is functional, it's time to package it as an executable JAR. This will allow us to easily deploy and run the web service on any machine with Java installed.",[651,77973,77974],{},"To package the application as a JAR, follow these steps:",[27665,77976,77977,77980,77990],{},[5332,77978,77979],{},"Open a terminal and navigate to your project directory",[5332,77981,77982,77983,4193,77986,77989],{},"Run ",[676,77984,77985],{},"./mvnw package",[676,77987,77988],{},"mvnw.cmd package"," on Windows) to build the JAR file",[5332,77991,77992,77993,77996],{},"Verify that a JAR file is generated in the ",[676,77994,77995],{},"target"," directory",[651,77998,77999,78000,664],{},"You can now run the web service using the command ",[676,78001,78002],{},"java -jar target/rest-service-0.0.1-SNAPSHOT.jar",[4542,78004,78006],{"id":78005},"wrapping-up","Wrapping Up",[651,78008,78009],{},"Congratulations! You've just built a simple RESTful web service using Spring Boot!",[651,78011,78012,78013,78018],{},"This tutorial covered the basics of creating a RESTful web service, but there's plenty more to learn. The ",[812,78014,78017],{"href":78015,"rel":78016},"https://spring.io/guides",[816],"Spring.io guides"," provide numerous tutorials and examples to help you continue building your skills and expanding your knowledge of Spring Boot and its many features.",[651,78020,78021],{},"Remember to leave a comment below if you enjoyed this tutorial, and let us know if you'd like to see more like this!",[651,78023,78024],{},"Happy coding!",[786,78026,78027],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":78029},[78030,78031,78032,78033,78034,78035,78036,78037],{"id":77431,"depth":790,"text":77432},{"id":77444,"depth":790,"text":77445},{"id":77462,"depth":790,"text":77463},{"id":77519,"depth":790,"text":77520},{"id":77713,"depth":790,"text":77714},{"id":77935,"depth":790,"text":77936},{"id":77967,"depth":790,"text":77968},{"id":78005,"depth":790,"text":78006},"In this tutorial you will learn how to build a REST API in Java using Spring Boot.",{"slug":78040,"date":78041,"published":797,"author":798,"tags":78042,"cover":78043,"video":78044,"keywords":78045},"spring-boot-rest-service","2020-06-04T08:00:00.000Z",[7077],"./spring-boot-rest-service.jpeg","https://www.youtube.com/embed/MWLe1tqPmUo","Java, Spring Framework, Spring Boot, Spring REST, REST API",{"title":426,"description":78038},"blog/2020/06/04/spring-boot-rest-service","Etou7xeGremRJHzsxr7HUryrTsUMhHDtiBQN5zvJFo0",{"id":78050,"title":423,"body":78051,"description":78257,"extension":793,"meta":78258,"navigation":797,"path":424,"seo":78264,"stem":78265,"__hash__":78266},"content/blog/2020/07/30/interactive-learning-from-oreilly.md",{"type":648,"value":78052,"toc":78248},[78053,78068,78072,78075,78078,78081,78087,78095,78099,78102,78105,78109,78112,78149,78153,78156,78193,78197,78200,78237,78239],[651,78054,78055,78056,78061,78062,78067],{},"I'm happy to announce that a few projects I have been working on with ",[812,78057,78060],{"href":78058,"rel":78059},"https://learning.oreilly.com/",[816],"O'Reilly Media"," are now live.\nFirst off I want to thank everyone at O'Reilly for all of their help launching these projects. For me, O'Reilly has long\nbeen the gold standard for technical training materials. Their physical books line my bookshelf and I also have a\nsubscription to the ",[812,78063,78066],{"href":78064,"rel":78065},"https://learning.oreilly.com",[816],"O'Reilly Learning Platform"," which I highly recommend. When I got\napproached to collaborate with them on a new type of learning platform I was really excited.",[4542,78069,78071],{"id":78070},"interactive-learning-powered-by-katacoda","Interactive Learning powered by Katacoda",[651,78073,78074],{},"Before I dive into the specific projects that I worked on it might help to have a little context on what the interactive learning\nplatform is. We all know that you can buy all the books or watch all the videos you want but if you want to truly learn\nsomething you have to learn by doing.",[651,78076,78077],{},"Interactive Learning is a short, 5-15 minutes hands-on learning experience. There is no development environment to set up-just write\nand run code as you learn, right in your browser. Each interactive learning tutorial is called a scenario and you can have\nthree to five scenarios grouped together that will make up a set.",[651,78079,78080],{},"Below is an example from one of the scenarios I built for getting started with Maven. Each step has a tutorial, a code\neditor and a terminal to run commands. This layout is customizable based on what you are teaching but this made sense\nfor this particular scenario.",[651,78082,78083],{},[660,78084],{"alt":78085,"src":78086},"Tutorial / Editor / Terminal","/images/blog/2020/07/30/tutorial_editor_terminal.png",[651,78088,78089,78090,78094],{},"If you want to learn more about Interactive Learning or browse there hundreds of scenarios head over to ",[812,78091,78092],{"href":78092,"rel":78093},"https://learning.oreilly.com/interactive",[816],".\nYou will need an O'Reilly account to access these but as someone who had an account before working on these I can tell\nyou that it is well worth the money.",[4542,78096,78098],{"id":78097},"my-interactive-learning-sets","My Interactive Learning sets",[651,78100,78101],{},"Now that you know a little more about what Interactive Learning scenarios are I can tell you about the sets that I put together.\nI think my biggest challenge creating these was that it was a new system to me with its own rules and boundaries. The first\nset I put together took a long time but soon after getting used to things I was able to get into a grove.",[651,78103,78104],{},"Below are each of the sets that I put together, links to each of the scenarios and a description of the set.",[5909,78106,78108],{"id":78107},"spring-boot-creating-your-first-rest-api","Spring Boot: Creating your first REST API",[651,78110,78111],{},"In this set you will how to create a new Spring Boot project that will be a REST API. If you are new to Spring Boot\nand creating REST APIs this is a perfect place to get started.",[5316,78113,78114,78121,78128,78135,78142],{},[5332,78115,78116],{},[812,78117,78120],{"href":78118,"rel":78119},"https://learning.oreilly.com/scenarios/spring-boot-create/9781492088547/",[816],"Spring Boot: Create a New Project",[5332,78122,78123],{},[812,78124,78127],{"href":78125,"rel":78126},"https://learning.oreilly.com/scenarios/spring-boot-create/9781492088554/",[816],"Spring Boot: Create a REST Controller",[5332,78129,78130],{},[812,78131,78134],{"href":78132,"rel":78133},"https://learning.oreilly.com/scenarios/spring-boot-dependency/9781492088561/",[816],"Spring Boot: Dependency Injection",[5332,78136,78137],{},[812,78138,78141],{"href":78139,"rel":78140},"https://learning.oreilly.com/scenarios/spring-boot-handling/9781492088578/",[816],"Spring Boot: Handling JSON",[5332,78143,78144],{},[812,78145,78148],{"href":78146,"rel":78147},"https://learning.oreilly.com/scenarios/spring-boot-handling/9781492088585/",[816],"Spring Boot: Handling Errors",[5909,78150,78152],{"id":78151},"spring-boot-connecting-to-a-database","Spring Boot: Connecting to a database",[651,78154,78155],{},"In this set you will learn how to connect, populate, access and manipulate date from a database in a Spring Boot application.\nThis is perfect for anyone who is just getting started with Spring Boot and want to learn how to interact with a database.",[5316,78157,78158,78165,78172,78179,78186],{},[5332,78159,78160],{},[812,78161,78164],{"href":78162,"rel":78163},"https://learning.oreilly.com/scenarios/spring-boot-connect/9781492088608/",[816],"Spring Boot: Connect to a Database",[5332,78166,78167],{},[812,78168,78171],{"href":78169,"rel":78170},"https://learning.oreilly.com/scenarios/spring-boot-populate/9781492088615/",[816],"Spring Boot: Populate a Database",[5332,78173,78174],{},[812,78175,78178],{"href":78176,"rel":78177},"https://learning.oreilly.com/scenarios/spring-boot-accessing/9781492088622/",[816],"Spring Boot: Accessing Data with JdbcTemplate",[5332,78180,78181],{},[812,78182,78185],{"href":78183,"rel":78184},"https://learning.oreilly.com/scenarios/spring-boot-manipulating/9781492088639/",[816],"Spring Boot: Manipulating Data with JdbcTemplate",[5332,78187,78188],{},[812,78189,78192],{"href":78190,"rel":78191},"https://learning.oreilly.com/scenarios/spring-boot-using/9781492088646/",[816],"Spring Boot: Using the Command Line Runner Interface",[5909,78194,78196],{"id":78195},"maven-first-steps","Maven: First Steps",[651,78198,78199],{},"In this set you will learn what Maven is and how to use it in Java projects. Maven is a tool for building and managing your Java-based projects\nand is the most used tool among Java developers. This set is perfect for anyone who wants to understand how to use Maven.",[5316,78201,78202,78209,78216,78223,78230],{},[5332,78203,78204],{},[812,78205,78208],{"href":78206,"rel":78207},"https://learning.oreilly.com/scenarios/maven-install-maven/9781492088660/",[816],"Maven: Install Maven and Create Your First Application",[5332,78210,78211],{},[812,78212,78215],{"href":78213,"rel":78214},"https://learning.oreilly.com/scenarios/maven-compiling-packaging/9781492088677/",[816],"Maven: Compiling, Packaging, and Running Your Application",[5332,78217,78218],{},[812,78219,78222],{"href":78220,"rel":78221},"https://learning.oreilly.com/scenarios/maven-dependency-management/9781492088684/",[816],"Maven: Dependency Management",[5332,78224,78225],{},[812,78226,78229],{"href":78227,"rel":78228},"https://learning.oreilly.com/scenarios/maven-using-maven/9781492088691/",[816],"Maven: Using Maven Plugins",[5332,78231,78232],{},[812,78233,78236],{"href":78234,"rel":78235},"https://learning.oreilly.com/scenarios/maven-using-maven/9781492088707/",[816],"Maven: Using Maven Archetypes",[4542,78238,9042],{"id":9041},[651,78240,78241,78242,78247],{},"I had a lot of fun putting these together and now that I know what I am doing and how to approach building out each scenario\nI am excited to put together more of them. I have put together some suggestions for new scenarios and I will let all of you\nknow if I build any more. Thanks again to O'Reilly for giving me the opportunity to work on these. Having an ",[812,78243,78246],{"href":78244,"rel":78245},"https://learning.oreilly.com/search/?query=author%3A%22Dan%20Vega%22&extended_publisher_data=true&highlight=true&include_assessments=false&include_case_studies=true&include_courses=true&include_orioles=true&include_playlists=true&include_collections=true&include_notebooks=true&is_academic_institution_account=false&source=suggestion&sort=relevance&facet_json=true&page=0&include_scenarios=true&include_sandboxes=true",[816],"author page","\non their platform with some of my work is a dream come true.",{"title":674,"searchDepth":790,"depth":790,"links":78249},[78250,78251,78256],{"id":78070,"depth":790,"text":78071},{"id":78097,"depth":790,"text":78098,"children":78252},[78253,78254,78255],{"id":78107,"depth":892,"text":78108},{"id":78151,"depth":892,"text":78152},{"id":78195,"depth":892,"text":78196},{"id":9041,"depth":790,"text":9042},"O'Reilly has a new hands-on learning platform called Interactive Learning. In this tutorial I will tell you all about it and the tutorials I created for it.",{"slug":78259,"date":78260,"published":797,"author":798,"tags":78261,"cover":78263},"interactive-learning-from-oreilly","2020-07-30T12:50:24.664Z",[4109,7055,78262],"maven","./oreilly-interactive-learning-cover.png",{"title":423,"description":78257},"blog/2020/07/30/interactive-learning-from-oreilly","1BYiFiU63hBQXcUxFs9SdC_2yCk5wQT1e0tbPyIC1wc",{"id":78268,"title":420,"body":78269,"description":79320,"extension":793,"meta":79321,"navigation":797,"path":421,"seo":79326,"stem":79327,"__hash__":79328},"content/blog/2020/12/16/reassign-standard-in-out.md",{"type":648,"value":78270,"toc":79315},[78271,78274,78277,78281,78288,78304,78376,78396,78489,78495,78528,78720,78723,78727,78730,78744,78751,78813,78837,78925,78945,79081,79087,79299,79301,79308,79312],[651,78272,78273],{},"When you have been writing software for as long as I have you see a lot of the same problems over and over and it can be a bit repetitive at times. That is why when I run across a problem I haven't had to solve for before I get pretty excited.",[651,78275,78276],{},"Recently I was working on a project where I had to write unit tests for an exercise where students were learning the basics of File I/O in Java. The basis of the exercise was that they would read some information in from the command line and then write the responses to the console in a particular format. Pretty standard I/O but writing tests for this is not something that I have had to do before.",[4542,78278,78280],{"id":78279},"java-standard-in-and-standard-out","Java Standard In and Standard Out",[651,78282,78283,78284,78287],{},"Before we dive into the tests let's take a look at the application. Ask the user for their first name, last name and email address and save each response. All of the work for this application will be done in the ",[676,78285,78286],{},"main()"," method.",[651,78289,40060,78290,78293,78294,51880,78297,78300,78301,78303],{},[676,78291,78292],{},"Scanner"," class is used to parse input from a number of different sources, one of which can be an ",[676,78295,78296],{},"Input Stream",[676,78298,78299],{},"System"," class has a static instance variable ",[676,78302,1472],{}," that represents the standard input stream (input from the keyboard).",[669,78305,78307],{"className":4107,"code":78306,"language":4109,"meta":674,"style":674},"public class Application {\n\n public static void main(String[] args) {\n\n Scanner scanner = new Scanner(System.in);\n\n }\n\n}\n",[676,78308,78309,78319,78323,78343,78347,78360,78364,78368,78372],{"__ignoreMap":674},[679,78310,78311,78313,78315,78317],{"class":681,"line":682},[679,78312,6073],{"class":685},[679,78314,4512],{"class":685},[679,78316,16878],{"class":880},[679,78318,884],{"class":693},[679,78320,78321],{"class":681,"line":790},[679,78322,889],{"emptyLinePlaceholder":797},[679,78324,78325,78327,78329,78331,78333,78335,78337,78339,78341],{"class":681,"line":892},[679,78326,6089],{"class":685},[679,78328,6092],{"class":685},[679,78330,6095],{"class":685},[679,78332,6098],{"class":880},[679,78334,745],{"class":693},[679,78336,4758],{"class":685},[679,78338,16901],{"class":693},[679,78340,6108],{"class":2099},[679,78342,4390],{"class":693},[679,78344,78345],{"class":681,"line":901},[679,78346,889],{"emptyLinePlaceholder":797},[679,78348,78349,78352,78354,78356,78358],{"class":681,"line":909},[679,78350,78351],{"class":693}," Scanner scanner ",[679,78353,686],{"class":685},[679,78355,2054],{"class":685},[679,78357,15515],{"class":880},[679,78359,15518],{"class":693},[679,78361,78362],{"class":681,"line":918},[679,78363,889],{"emptyLinePlaceholder":797},[679,78365,78366],{"class":681,"line":935},[679,78367,985],{"class":693},[679,78369,78370],{"class":681,"line":944},[679,78371,889],{"emptyLinePlaceholder":797},[679,78373,78374],{"class":681,"line":959},[679,78375,996],{"class":693},[651,78377,78378,78379,78382,78383,78385,78386,78389,78390,78392,78393,78395],{},"You can ask the user a question to standard out (console) using ",[676,78380,78381],{},"System.out",". Now that you have an instance of a ",[676,78384,78292],{}," there are methods for obtaining user input of different types. The ",[676,78387,78388],{},"nextLine()"," method of the ",[676,78391,78292],{}," class will return a ",[676,78394,4758],{},". We can use this same approach for all three required inputs because we expect to get back Strings for all of them.",[669,78397,78399],{"className":4107,"code":78398,"language":4109,"meta":674,"style":674},"System.out.println(\"What is your first name?\");\nString firstName = scanner.nextLine();\n\nSystem.out.println(\"What is your last name?\");\nString lastName = scanner.nextLine();\n\nSystem.out.println(\"What is your email address?\");\nString email = scanner.nextLine();\n",[676,78400,78401,78415,78429,78433,78446,78459,78463,78476],{"__ignoreMap":674},[679,78402,78403,78406,78408,78410,78413],{"class":681,"line":682},[679,78404,78405],{"class":693},"System.out.",[679,78407,1729],{"class":880},[679,78409,745],{"class":693},[679,78411,78412],{"class":689},"\"What is your first name?\"",[679,78414,1208],{"class":693},[679,78416,78417,78420,78422,78425,78427],{"class":681,"line":790},[679,78418,78419],{"class":693},"String firstName ",[679,78421,686],{"class":685},[679,78423,78424],{"class":693}," scanner.",[679,78426,15635],{"class":880},[679,78428,9317],{"class":693},[679,78430,78431],{"class":681,"line":892},[679,78432,889],{"emptyLinePlaceholder":797},[679,78434,78435,78437,78439,78441,78444],{"class":681,"line":901},[679,78436,78405],{"class":693},[679,78438,1729],{"class":880},[679,78440,745],{"class":693},[679,78442,78443],{"class":689},"\"What is your last name?\"",[679,78445,1208],{"class":693},[679,78447,78448,78451,78453,78455,78457],{"class":681,"line":909},[679,78449,78450],{"class":693},"String lastName ",[679,78452,686],{"class":685},[679,78454,78424],{"class":693},[679,78456,15635],{"class":880},[679,78458,9317],{"class":693},[679,78460,78461],{"class":681,"line":918},[679,78462,889],{"emptyLinePlaceholder":797},[679,78464,78465,78467,78469,78471,78474],{"class":681,"line":935},[679,78466,78405],{"class":693},[679,78468,1729],{"class":880},[679,78470,745],{"class":693},[679,78472,78473],{"class":689},"\"What is your email address?\"",[679,78475,1208],{"class":693},[679,78477,78478,78481,78483,78485,78487],{"class":681,"line":944},[679,78479,78480],{"class":693},"String email ",[679,78482,686],{"class":685},[679,78484,78424],{"class":693},[679,78486,15635],{"class":880},[679,78488,9317],{"class":693},[651,78490,78491,78492,664],{},"Finally, print the information that was obtained from the user in the format of ",[7300,78493,78494],{},"First Name, Last Name, Email Address",[669,78496,78498],{"className":4107,"code":78497,"language":4109,"meta":674,"style":674},"System.out.println(firstName + \",\" + lastName + \",\" + email);\n",[676,78499,78500],{"__ignoreMap":674},[679,78501,78502,78504,78506,78509,78511,78514,78516,78519,78521,78523,78525],{"class":681,"line":682},[679,78503,78405],{"class":693},[679,78505,1729],{"class":880},[679,78507,78508],{"class":693},"(firstName ",[679,78510,3065],{"class":685},[679,78512,78513],{"class":689}," \",\"",[679,78515,3059],{"class":685},[679,78517,78518],{"class":693}," lastName ",[679,78520,3065],{"class":685},[679,78522,78513],{"class":689},[679,78524,3059],{"class":685},[679,78526,78527],{"class":693}," email);\n",[669,78529,78531],{"className":4107,"code":78530,"language":4109,"meta":674,"style":674},"public class Application {\n\n public static void main(String[] args) {\n\n Scanner scanner = new Scanner(System.in);\n\n // Ask for user input\n System.out.println(\"What is your first name?\");\n String firstName = scanner.nextLine();\n\n System.out.println(\"What is your last name?\");\n String lastName = scanner.nextLine();\n\n System.out.println(\"What is your email address?\");\n String email = scanner.nextLine();\n\n // print to the console in the format of firstname,lastname,email\n System.out.println(firstName + \",\" + lastName + \",\" + email);\n }\n\n}\n",[676,78532,78533,78543,78547,78567,78571,78583,78587,78592,78604,78617,78621,78633,78646,78650,78662,78675,78679,78684,78708,78712,78716],{"__ignoreMap":674},[679,78534,78535,78537,78539,78541],{"class":681,"line":682},[679,78536,6073],{"class":685},[679,78538,4512],{"class":685},[679,78540,16878],{"class":880},[679,78542,884],{"class":693},[679,78544,78545],{"class":681,"line":790},[679,78546,889],{"emptyLinePlaceholder":797},[679,78548,78549,78551,78553,78555,78557,78559,78561,78563,78565],{"class":681,"line":892},[679,78550,6089],{"class":685},[679,78552,6092],{"class":685},[679,78554,6095],{"class":685},[679,78556,6098],{"class":880},[679,78558,745],{"class":693},[679,78560,4758],{"class":685},[679,78562,16901],{"class":693},[679,78564,6108],{"class":2099},[679,78566,4390],{"class":693},[679,78568,78569],{"class":681,"line":901},[679,78570,889],{"emptyLinePlaceholder":797},[679,78572,78573,78575,78577,78579,78581],{"class":681,"line":909},[679,78574,78351],{"class":693},[679,78576,686],{"class":685},[679,78578,2054],{"class":685},[679,78580,15515],{"class":880},[679,78582,15518],{"class":693},[679,78584,78585],{"class":681,"line":918},[679,78586,889],{"emptyLinePlaceholder":797},[679,78588,78589],{"class":681,"line":935},[679,78590,78591],{"class":1400}," // Ask for user input\n",[679,78593,78594,78596,78598,78600,78602],{"class":681,"line":944},[679,78595,9592],{"class":693},[679,78597,1729],{"class":880},[679,78599,745],{"class":693},[679,78601,78412],{"class":689},[679,78603,1208],{"class":693},[679,78605,78606,78609,78611,78613,78615],{"class":681,"line":959},[679,78607,78608],{"class":693}," String firstName ",[679,78610,686],{"class":685},[679,78612,78424],{"class":693},[679,78614,15635],{"class":880},[679,78616,9317],{"class":693},[679,78618,78619],{"class":681,"line":964},[679,78620,889],{"emptyLinePlaceholder":797},[679,78622,78623,78625,78627,78629,78631],{"class":681,"line":977},[679,78624,9592],{"class":693},[679,78626,1729],{"class":880},[679,78628,745],{"class":693},[679,78630,78443],{"class":689},[679,78632,1208],{"class":693},[679,78634,78635,78638,78640,78642,78644],{"class":681,"line":982},[679,78636,78637],{"class":693}," String lastName ",[679,78639,686],{"class":685},[679,78641,78424],{"class":693},[679,78643,15635],{"class":880},[679,78645,9317],{"class":693},[679,78647,78648],{"class":681,"line":988},[679,78649,889],{"emptyLinePlaceholder":797},[679,78651,78652,78654,78656,78658,78660],{"class":681,"line":993},[679,78653,9592],{"class":693},[679,78655,1729],{"class":880},[679,78657,745],{"class":693},[679,78659,78473],{"class":689},[679,78661,1208],{"class":693},[679,78663,78664,78667,78669,78671,78673],{"class":681,"line":2129},[679,78665,78666],{"class":693}," String email ",[679,78668,686],{"class":685},[679,78670,78424],{"class":693},[679,78672,15635],{"class":880},[679,78674,9317],{"class":693},[679,78676,78677],{"class":681,"line":2140},[679,78678,889],{"emptyLinePlaceholder":797},[679,78680,78681],{"class":681,"line":2145},[679,78682,78683],{"class":1400}," // print to the console in the format of firstname,lastname,email\n",[679,78685,78686,78688,78690,78692,78694,78696,78698,78700,78702,78704,78706],{"class":681,"line":2154},[679,78687,9592],{"class":693},[679,78689,1729],{"class":880},[679,78691,78508],{"class":693},[679,78693,3065],{"class":685},[679,78695,78513],{"class":689},[679,78697,3059],{"class":685},[679,78699,78518],{"class":693},[679,78701,3065],{"class":685},[679,78703,78513],{"class":689},[679,78705,3059],{"class":685},[679,78707,78527],{"class":693},[679,78709,78710],{"class":681,"line":2159},[679,78711,985],{"class":693},[679,78713,78714],{"class":681,"line":2164},[679,78715,889],{"emptyLinePlaceholder":797},[679,78717,78718],{"class":681,"line":3134},[679,78719,996],{"class":693},[651,78721,78722],{},"As I said earlier, from an application point pretty standard stuff.",[4542,78724,78726],{"id":78725},"writing-tests-for-standard-in-and-out","Writing tests for standard in and out",[651,78728,78729],{},"While the application is fairly basic the first thing I had to ask myself was how I am I going to emulate input from the keyboard. How was I going to read from standard out? What happens in the next exercise when I ask them to print the contents of that string to a file?",[651,78731,78732,78733,78735,78736,78743],{},"If you weren't aware you can reassign standard in and standard out and this is something that is fairly common in most programming languages. If you look in the ",[676,78734,78299],{}," class you will find a method ",[812,78737,78740],{"href":78738,"rel":78739},"https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setIn(java.io.InputStream)",[816],[676,78741,78742],{},"public static void setIn(InputStream in)"," which will allow you to set the new standard input stream.",[651,78745,78746,78747,78750],{},"I start off by creating a test where when I provide valid user input and if everything goes well I should see the input in the correct format sent to standard out. I'll start by creating a string with responses to the three questions. Make sure to use ",[676,78748,78749],{},"System.lineSeparator()"," and not to hard cord a new line feed.",[669,78752,78754],{"className":4107,"code":78753,"language":4109,"meta":674,"style":674},"@Test\npublic void validUserInput_ShouldResultInExpectedOutput() {\n String userInput = String.format(\"Dan%sVega%sdanvega@gmail.com\",\n System.lineSeparator(),\n System.lineSeparator());\n}\n",[676,78755,78756,78762,78773,78791,78801,78809],{"__ignoreMap":674},[679,78757,78758,78760],{"class":681,"line":682},[679,78759,4116],{"class":693},[679,78761,73087],{"class":685},[679,78763,78764,78766,78768,78771],{"class":681,"line":790},[679,78765,6073],{"class":685},[679,78767,6095],{"class":685},[679,78769,78770],{"class":880}," validUserInput_ShouldResultInExpectedOutput",[679,78772,2667],{"class":693},[679,78774,78775,78778,78780,78782,78784,78786,78789],{"class":681,"line":892},[679,78776,78777],{"class":693}," String userInput ",[679,78779,686],{"class":685},[679,78781,72976],{"class":693},[679,78783,17581],{"class":880},[679,78785,745],{"class":693},[679,78787,78788],{"class":689},"\"Dan%sVega%sdanvega@gmail.com\"",[679,78790,12083],{"class":693},[679,78792,78793,78796,78799],{"class":681,"line":901},[679,78794,78795],{"class":693}," System.",[679,78797,78798],{"class":880},"lineSeparator",[679,78800,56208],{"class":693},[679,78802,78803,78805,78807],{"class":681,"line":909},[679,78804,78795],{"class":693},[679,78806,78798],{"class":880},[679,78808,9431],{"class":693},[679,78810,78811],{"class":681,"line":918},[679,78812,996],{"class":693},[651,78814,78815,78816,78819,78820,78823,78824,78826,78827,78830,78831,78833,78834,664],{},"Next you need to create something that is of type ",[676,78817,78818],{},"InputStream"," so you can reassign standard in. There is a class ",[676,78821,78822],{},"ByteArrayInputStream"," that will work and one of the available constructors will take a byte array. You can use the ",[676,78825,4758],{}," class's ",[676,78828,78829],{},"getBytes()"," method to turn the ",[676,78832,4758],{}," into a ",[676,78835,78836],{},"byte[]",[669,78838,78840],{"className":4107,"code":78839,"language":4109,"meta":674,"style":674},"@Test\npublic void validUserInput_ShouldResultInExpectedOutput() {\n String userInput = String.format(\"Dan%sVega%sdanvega@gmail.com\",\n System.lineSeparator(),\n System.lineSeparator());\n ByteArrayInputStream bais = new ByteArrayInputStream(userInput.getBytes());\n System.setIn(bais);\n}\n",[676,78841,78842,78848,78858,78874,78882,78890,78910,78921],{"__ignoreMap":674},[679,78843,78844,78846],{"class":681,"line":682},[679,78845,4116],{"class":693},[679,78847,73087],{"class":685},[679,78849,78850,78852,78854,78856],{"class":681,"line":790},[679,78851,6073],{"class":685},[679,78853,6095],{"class":685},[679,78855,78770],{"class":880},[679,78857,2667],{"class":693},[679,78859,78860,78862,78864,78866,78868,78870,78872],{"class":681,"line":892},[679,78861,78777],{"class":693},[679,78863,686],{"class":685},[679,78865,72976],{"class":693},[679,78867,17581],{"class":880},[679,78869,745],{"class":693},[679,78871,78788],{"class":689},[679,78873,12083],{"class":693},[679,78875,78876,78878,78880],{"class":681,"line":901},[679,78877,78795],{"class":693},[679,78879,78798],{"class":880},[679,78881,56208],{"class":693},[679,78883,78884,78886,78888],{"class":681,"line":909},[679,78885,78795],{"class":693},[679,78887,78798],{"class":880},[679,78889,9431],{"class":693},[679,78891,78892,78895,78897,78899,78902,78905,78908],{"class":681,"line":918},[679,78893,78894],{"class":693}," ByteArrayInputStream bais ",[679,78896,686],{"class":685},[679,78898,2054],{"class":685},[679,78900,78901],{"class":880}," ByteArrayInputStream",[679,78903,78904],{"class":693},"(userInput.",[679,78906,78907],{"class":880},"getBytes",[679,78909,9431],{"class":693},[679,78911,78912,78915,78918],{"class":681,"line":935},[679,78913,78914],{"class":693}," System.",[679,78916,78917],{"class":880},"setIn",[679,78919,78920],{"class":693},"(bais);\n",[679,78922,78923],{"class":681,"line":944},[679,78924,996],{"class":693},[651,78926,78927,78928,77030,78930,78937,78938,78941,78942,664],{},"With standard in reassigned you will need to the same for standard out. Create a string that represents what the expected result is. The ",[676,78929,78299],{},[812,78931,78934],{"href":78932,"rel":78933},"https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setOut(java.io.PrintStream)",[816],[676,78935,78936],{},"public static void setOut(PrintStream out)"," that will allow you to reassign the standard output stream. You can create an instance of ",[676,78939,78940],{},"PrintStream"," using a ",[676,78943,78944],{},"ByteArrayOutputStream",[669,78946,78948],{"className":4107,"code":78947,"language":4109,"meta":674,"style":674},"@Test\npublic void validUserInput_ShouldResultInExpectedOutput() {\n String userInput = String.format(\"Dan%sVega%sdanvega@gmail.com\",\n System.lineSeparator(),\n System.lineSeparator());\n ByteArrayInputStream bais = new ByteArrayInputStream(userInput.getBytes());\n System.setIn(bais);\n\n String expected = \"Dan,Vega,danvega@gmail.com\";\n ByteArrayOutputStream baos = new ByteArrayOutputStream();\n PrintStream printStream = new PrintStream(baos);\n System.setOut(printStream);\n}\n",[676,78949,78950,78956,78966,78982,78990,78998,79014,79022,79026,79038,79052,79067,79077],{"__ignoreMap":674},[679,78951,78952,78954],{"class":681,"line":682},[679,78953,4116],{"class":693},[679,78955,73087],{"class":685},[679,78957,78958,78960,78962,78964],{"class":681,"line":790},[679,78959,6073],{"class":685},[679,78961,6095],{"class":685},[679,78963,78770],{"class":880},[679,78965,2667],{"class":693},[679,78967,78968,78970,78972,78974,78976,78978,78980],{"class":681,"line":892},[679,78969,78777],{"class":693},[679,78971,686],{"class":685},[679,78973,72976],{"class":693},[679,78975,17581],{"class":880},[679,78977,745],{"class":693},[679,78979,78788],{"class":689},[679,78981,12083],{"class":693},[679,78983,78984,78986,78988],{"class":681,"line":901},[679,78985,78795],{"class":693},[679,78987,78798],{"class":880},[679,78989,56208],{"class":693},[679,78991,78992,78994,78996],{"class":681,"line":909},[679,78993,78795],{"class":693},[679,78995,78798],{"class":880},[679,78997,9431],{"class":693},[679,78999,79000,79002,79004,79006,79008,79010,79012],{"class":681,"line":918},[679,79001,78894],{"class":693},[679,79003,686],{"class":685},[679,79005,2054],{"class":685},[679,79007,78901],{"class":880},[679,79009,78904],{"class":693},[679,79011,78907],{"class":880},[679,79013,9431],{"class":693},[679,79015,79016,79018,79020],{"class":681,"line":935},[679,79017,78914],{"class":693},[679,79019,78917],{"class":880},[679,79021,78920],{"class":693},[679,79023,79024],{"class":681,"line":944},[679,79025,889],{"emptyLinePlaceholder":797},[679,79027,79028,79031,79033,79036],{"class":681,"line":959},[679,79029,79030],{"class":693}," String expected ",[679,79032,686],{"class":685},[679,79034,79035],{"class":689}," \"Dan,Vega,danvega@gmail.com\"",[679,79037,1186],{"class":693},[679,79039,79040,79043,79045,79047,79050],{"class":681,"line":964},[679,79041,79042],{"class":693}," ByteArrayOutputStream baos ",[679,79044,686],{"class":685},[679,79046,2054],{"class":685},[679,79048,79049],{"class":880}," ByteArrayOutputStream",[679,79051,9317],{"class":693},[679,79053,79054,79057,79059,79061,79064],{"class":681,"line":977},[679,79055,79056],{"class":693}," PrintStream printStream ",[679,79058,686],{"class":685},[679,79060,2054],{"class":685},[679,79062,79063],{"class":880}," PrintStream",[679,79065,79066],{"class":693},"(baos);\n",[679,79068,79069,79071,79074],{"class":681,"line":982},[679,79070,78914],{"class":693},[679,79072,79073],{"class":880},"setOut",[679,79075,79076],{"class":693},"(printStream);\n",[679,79078,79079],{"class":681,"line":988},[679,79080,996],{"class":693},[651,79082,79083,79084,79086],{},"Next you can call the applications main method. If the student completed the exercise they would have printed out a few things to standard out. The last one would have been the users first name, last name, email address. You can use the ",[676,79085,78944],{}," to return an array of strings which will be each of the lines printed out to the console with the last one being the one you are interested in. Finally, your assertion can test if the expected output matches the actual output.",[669,79088,79090],{"className":4107,"code":79089,"language":4109,"meta":674,"style":674},"@Test\npublic void validUserInput_ShouldResultInExpectedOutput() {\n String userInput = String.format(\"Dan%sVega%sdanvega@gmail.com\",\n System.lineSeparator(),\n System.lineSeparator());\n ByteArrayInputStream bais = new ByteArrayInputStream(userInput.getBytes());\n System.setIn(bais);\n\n String expected = \"Dan,Vega,danvega@gmail.com\";\n ByteArrayOutputStream baos = new ByteArrayOutputStream();\n PrintStream printStream = new PrintStream(baos);\n System.setOut(printStream);\n\n Application.main(null); // call the main method\n\n String[] lines = baos.toString().split(System.lineSeparator());\n String actual = lines[lines.length-1];\n\n // checkout output\n Assert.assertEquals(expected,actual);\n}\n",[676,79091,79092,79098,79108,79124,79132,79140,79156,79164,79168,79178,79190,79202,79210,79214,79231,79235,79260,79276,79280,79285,79295],{"__ignoreMap":674},[679,79093,79094,79096],{"class":681,"line":682},[679,79095,4116],{"class":693},[679,79097,73087],{"class":685},[679,79099,79100,79102,79104,79106],{"class":681,"line":790},[679,79101,6073],{"class":685},[679,79103,6095],{"class":685},[679,79105,78770],{"class":880},[679,79107,2667],{"class":693},[679,79109,79110,79112,79114,79116,79118,79120,79122],{"class":681,"line":892},[679,79111,78777],{"class":693},[679,79113,686],{"class":685},[679,79115,72976],{"class":693},[679,79117,17581],{"class":880},[679,79119,745],{"class":693},[679,79121,78788],{"class":689},[679,79123,12083],{"class":693},[679,79125,79126,79128,79130],{"class":681,"line":901},[679,79127,78795],{"class":693},[679,79129,78798],{"class":880},[679,79131,56208],{"class":693},[679,79133,79134,79136,79138],{"class":681,"line":909},[679,79135,78795],{"class":693},[679,79137,78798],{"class":880},[679,79139,9431],{"class":693},[679,79141,79142,79144,79146,79148,79150,79152,79154],{"class":681,"line":918},[679,79143,78894],{"class":693},[679,79145,686],{"class":685},[679,79147,2054],{"class":685},[679,79149,78901],{"class":880},[679,79151,78904],{"class":693},[679,79153,78907],{"class":880},[679,79155,9431],{"class":693},[679,79157,79158,79160,79162],{"class":681,"line":935},[679,79159,78914],{"class":693},[679,79161,78917],{"class":880},[679,79163,78920],{"class":693},[679,79165,79166],{"class":681,"line":944},[679,79167,889],{"emptyLinePlaceholder":797},[679,79169,79170,79172,79174,79176],{"class":681,"line":959},[679,79171,79030],{"class":693},[679,79173,686],{"class":685},[679,79175,79035],{"class":689},[679,79177,1186],{"class":693},[679,79179,79180,79182,79184,79186,79188],{"class":681,"line":964},[679,79181,79042],{"class":693},[679,79183,686],{"class":685},[679,79185,2054],{"class":685},[679,79187,79049],{"class":880},[679,79189,9317],{"class":693},[679,79191,79192,79194,79196,79198,79200],{"class":681,"line":977},[679,79193,79056],{"class":693},[679,79195,686],{"class":685},[679,79197,2054],{"class":685},[679,79199,79063],{"class":880},[679,79201,79066],{"class":693},[679,79203,79204,79206,79208],{"class":681,"line":982},[679,79205,78914],{"class":693},[679,79207,79073],{"class":880},[679,79209,79076],{"class":693},[679,79211,79212],{"class":681,"line":988},[679,79213,889],{"emptyLinePlaceholder":797},[679,79215,79216,79219,79221,79223,79225,79228],{"class":681,"line":993},[679,79217,79218],{"class":693}," Application.",[679,79220,77538],{"class":880},[679,79222,745],{"class":693},[679,79224,1146],{"class":931},[679,79226,79227],{"class":693},"); ",[679,79229,79230],{"class":1400},"// call the main method\n",[679,79232,79233],{"class":681,"line":2129},[679,79234,889],{"emptyLinePlaceholder":797},[679,79236,79237,79239,79242,79244,79247,79249,79251,79253,79256,79258],{"class":681,"line":2140},[679,79238,895],{"class":685},[679,79240,79241],{"class":693},"[] lines ",[679,79243,686],{"class":685},[679,79245,79246],{"class":693}," baos.",[679,79248,14391],{"class":880},[679,79250,10541],{"class":693},[679,79252,55948],{"class":880},[679,79254,79255],{"class":693},"(System.",[679,79257,78798],{"class":880},[679,79259,9431],{"class":693},[679,79261,79262,79265,79267,79270,79272,79274],{"class":681,"line":2145},[679,79263,79264],{"class":693}," String actual ",[679,79266,686],{"class":685},[679,79268,79269],{"class":693}," lines[lines.length",[679,79271,6453],{"class":685},[679,79273,1557],{"class":931},[679,79275,47519],{"class":693},[679,79277,79278],{"class":681,"line":2154},[679,79279,889],{"emptyLinePlaceholder":797},[679,79281,79282],{"class":681,"line":2159},[679,79283,79284],{"class":1400}," // checkout output\n",[679,79286,79287,79290,79292],{"class":681,"line":2164},[679,79288,79289],{"class":693}," Assert.",[679,79291,76032],{"class":880},[679,79293,79294],{"class":693},"(expected,actual);\n",[679,79296,79297],{"class":681,"line":3134},[679,79298,996],{"class":693},[4542,79300,9042],{"id":9041},[651,79302,79303,79304],{},"If you already knew this existed this article was pointless and chances are you aren't reading this. For the rest of you I hope you found this as cool as I did. If you want the code for this article you can find it over on ",[812,79305,17458],{"href":79306,"rel":79307},"https://github.com/danvega/sys-in-out-tests",[816],[651,79309,41105,79310,41109],{},[41107,79311],{},[786,79313,79314],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":79316},[79317,79318,79319],{"id":78279,"depth":790,"text":78280},{"id":78725,"depth":790,"text":78726},{"id":9041,"depth":790,"text":9042},"In this tutorial I will show you how you can test standard in and out in Java.",{"slug":79322,"date":79323,"published":797,"author":798,"tags":79324,"cover":79325},"reassign-standard-in-out","2020-12-16T15:17:39.619Z",[4109],"./testing-standard-in-out-java-cover.png",{"title":420,"description":79320},"blog/2020/12/16/reassign-standard-in-out","_x6gUiP16MncMvRsWjmtyxMZYq4VxJIZkbRRd9SLpbw",{"id":79330,"title":417,"body":79331,"description":79639,"extension":793,"meta":79640,"navigation":797,"path":418,"seo":79645,"stem":79646,"__hash__":79647},"content/blog/2021/01/07/happy-new-year-2021.md",{"type":648,"value":79332,"toc":79618},[79333,79336,79340,79354,79357,79363,79366,79372,79376,79381,79395,79398,79401,79404,79412,79418,79430,79434,79437,79443,79447,79456,79465,79468,79472,79481,79484,79507,79511,79520,79524,79527,79531,79534,79547,79550,79559,79562,79567,79571,79579,79583,79586,79588,79591,79593,79596,79598,79601,79603,79606,79610,79613,79615],[651,79334,79335],{},"I think we can all agree that we would like to put 2020 in the rear-view mirror and begin to look forward to the new year. In this post, I am going to share some exciting personal news, what I'm looking forward to learning, and some goals I want to set for myself this year.",[4542,79337,79339],{"id":79338},"goodbye-tech-elevator","Goodbye Tech Elevator",[651,79341,79342,79343,79347,79348,79353],{},"Leaving an employer is never easy and it's especially hard when you love the company you work for. ",[812,79344,79346],{"href":41135,"rel":79345},[816],"Tech Elevator's"," mission is near and dear to my heart and I will continue to be an advocate for them. If you're looking into ",[812,79349,79352],{"href":79350,"rel":79351},"https://www.switchup.org/rankings/best-coding-bootcamps",[816],"coding bootcamps"," I honestly think they are one of the best in the country and you should check them out.",[651,79355,79356],{},"I was a curriculum developer for 2.5 years and love everything about the content the curriculum team was able to produce. As a team, we refined our process over that time and I went from someone who was a content creator to someone who understands what a professional curriculum looks like.",[651,79358,79359,79360],{},"I owe a lot of this to my boss, co-founder of Tech Elevator, David Wintrich. Not only did I learn a lot from him but he was one of the best people I have ever had the privilege to work for. As I am writing this it's clear to me how much I am going to miss him. ",[2939,79361,79362],{},"#MapleHeights",[651,79364,79365],{},"I haven't been in the office since the first week of March and it's starting to hit me that I won't be going back. I love the energy that the students and my coworkers brought to that space every single day. I'm going to miss the morning coffee chats with so many wonderful people. There are so many great people at Tech Elevator and I hope that I left a good impression on them and that we all stay in touch. 💔",[651,79367,79368],{},[660,79369],{"alt":79370,"src":79371},"Tech Elevator Cleveland","/images/blog/2021/01/07/techelevator.jpg",[4542,79373,79375],{"id":79374},"hello-briebug-software","Hello, Briebug Software",[1004,79377,79378],{},[651,79379,79380],{},"“Life is a circle. The end of one journey is the beginning of the next.”\n― Joseph M. Marshall III",[651,79382,79383,79384,79389,79390,664],{},"I am excited to announce that I have joined ",[812,79385,79388],{"href":79386,"rel":79387},"https://briebug.com/",[816],"Briebug software"," as an Enterprise Java Architect. I didn't know much about Briebug going into this process but I was referred to by my friend ",[812,79391,79394],{"href":79392,"rel":79393},"https://twitter.com/JordanPowell88",[816],"Jordan Powell",[651,79396,79397],{},"Jordan had nothing but great things to say about the company culture and how he thought I would be a great fit. After talking to a few employees at Briebug I was beyond excited about this opportunity.",[651,79399,79400],{},"Java was the first programming language I learned and I have spent most of my career on the JVM. This opportunity gives me the chance to work on some exciting projects solving some of the most challenging problems our clients have.",[651,79402,79403],{},"As much as I love solving problems through code it isn't what drives me. I really love to learn new things and teach what I have learned to others. This position gives me the chance to work with some very smart people and I feed off of that. It also gives me a chance to teach and mentor other developers as a team lead and I think I'm looking forward to that the most.",[651,79405,79406,79407,664],{},"If that wasn't exciting enough, we are hiring! We are looking to fill many roles so if you're interested in working with me please check out our ",[812,79408,79411],{"href":79409,"rel":79410},"https://briebug.com/careers",[816],"careers page",[651,79413,79414],{},[660,79415],{"alt":79416,"src":79417},"Code Editor","/images/blog/2021/01/07/christopher-gower-m_HRfLhgABo-unsplash.jpg",[651,79419,79420,79421,79425,79426,664],{},"What this means for you is that you can expect a lot more content on this blog and on my ",[812,79422,15432],{"href":79423,"rel":79424},"https://www.danvega.dev/youtube",[816]," channel around the projects I will be working on day to day. If you have specific items you would like to see me cover please leave a comment below 👇🏻 or reach out to me on ",[812,79427,51474],{"href":79428,"rel":79429},"https://www.danvega.dev/twitter",[816],[4542,79431,79433],{"id":79432},"learning","Learning",[651,79435,79436],{},"I don't know about you but I keep a long list of subjects that I am interested in learning. This could be something brand new or brushing up on an existing skill. This list is always changing and given my new role I am going to focus a lot on Enterprise Architecture, Cloud and leadership skills.",[651,79438,79439],{},[660,79440],{"alt":79441,"src":79442},"Bookshelf","/images/blog/2021/01/07/pickawood-YbLitAY8bPA-unsplash.jpg",[5909,79444,79446],{"id":79445},"software-architecture","Software Architecture",[651,79448,79449,79450,79455],{},"As an architect, I am looking forward to obsessing over finding the best solutions to our customer's problems. I am going to start by re-reading ",[812,79451,79454],{"href":79452,"rel":79453},"https://amzn.to/3niCGfq",[816],"\"Clean Architecture\" by Robert C Martin",". This book does a great job by covering a lot of the fundamentals of architecture and design principles.",[651,79457,79458,79459,79464],{},"I also picked up a new book ",[812,79460,79463],{"href":79461,"rel":79462},"https://amzn.to/3ousa6x",[816],"Fundamentals of Software Architecture"," that I am looking forward to reading. Finally, I plan on spending some time take a deeper dive into software architecture patterns like microservices and event-driven architecture.",[651,79466,79467],{},"If there is anything you would like to see me cover please let me know below 👇🏻.",[5909,79469,79471],{"id":79470},"aws","AWS",[651,79473,79474,79475,79480],{},"I am in awe of all the capabilities that Amazon Web Services (AWS) provides. In my ",[812,79476,79479],{"href":79477,"rel":79478},"https://www.udemy.com/course/spring-boot-2/?referralCode=ECB6B9F8EF104672AF4A",[816],"Getting Started with Spring Boot 2"," course I used AWS to launch the application that we built into production.",[651,79482,79483],{},"I spent some time over the break to better understand what services are available and when to reach for them. I have just begun to scratch the surface of what AWS can do and here are a few services that I am excited to learn more about.",[5316,79485,79486,79493,79500],{},[5332,79487,79488],{},[812,79489,79492],{"href":79490,"rel":79491},"https://docs.amplify.aws/",[816],"AWS Amplify",[5332,79494,79495],{},[812,79496,79499],{"href":79497,"rel":79498},"https://aws.amazon.com/appsync/",[816],"AWS AppSync",[5332,79501,79502],{},[812,79503,79506],{"href":79504,"rel":79505},"https://aws.amazon.com/lambda/",[816],"AWS Lambda",[5909,79508,79510],{"id":79509},"azure","Azure",[651,79512,79513,79514,79519],{},"I haven't had the opportunity to use Microsoft's cloud services yet and I am going to take advantage of them this year so I am looking forward to diving right in. I am especially excited to check out ",[812,79515,79518],{"href":79516,"rel":79517},"https://azure.microsoft.com/en-us/services/spring-cloud/",[816],"Azure Spring Cloud"," which is a fully managed Spring Cloud service, jointly built and operated with VMware.",[5909,79521,79523],{"id":79522},"leadership-skills","Leadership Skills",[651,79525,79526],{},"On the non-technical front, being a good leader is important to me. My goals here are to build trust and respect with my team and I found the perfect book. The Art of leadership by Michael Lopp looks like a fantastic read. Michael spent time at Netscape, Apple, and Slack and he shares a series of small but compelling practices to help you build leadership skills.",[5909,79528,79530],{"id":79529},"just-for-fun","Just for fun",[651,79532,79533],{},"There are a few things on my list that If I ever found myself sitting around with nothing to do (unlikely) that I would love to learn.",[5316,79535,79536,79542],{},[5332,79537,79538,79541],{},[2939,79539,79540],{},"Go",": I have already started learning Go but I need to find more time for it and a few real-world applications to give me a kick start.",[5332,79543,79544,79546],{},[2939,79545,41079],{}," (Swift / Flutter): I don't know why but I have always wanted to build a mobile application. I don't have any ideas for an app at the moment but when I do I think it would be so cool to publish an application to an app store 🤷♂️",[4542,79548,79549],{"id":39139},"Goals",[651,79551,79552,79553,79558],{},"When the new year starts I usually like to set some goals. I have talked about this before but one of my favorite books on this subject is ",[812,79554,79557],{"href":79555,"rel":79556},"https://amzn.to/3ni9Goj",[816],"Your Best Year Ever by Michael Hyatt",". I learned how to make specific goals and how to achieve them. I'm throwing a lot of that knowledge out the door this year.",[651,79560,79561],{},"The pandemic has been a real challenge with two young daughters at home. I'm also starting a new job that is going to require my full focus. This doesn't leave a lot of time to work towards some of these goals. With that said there are things I want to do or at the very least work towards this year. That is why I am not going to put any specific time measurement on my goals and they very well could go into next year.",[651,79563,79564],{},[660,79565],{"alt":79549,"src":79566},"/images/blog/2021/01/07/danielle-macinnes-IuLgi9PWETU-unsplash.jpg",[5909,79568,79570],{"id":79569},"java-champion","Java Champion",[651,79572,79573,79578],{},[812,79574,79577],{"href":79575,"rel":79576},"https://developer.oracle.com/javachampions/",[816],"Java Champions"," are leaders in the Java community who are experts at what they do. I happen to know a few Java Champions and I look up to them and aspire to be like them. I feel like I do a lot for the Java community but I could be doing so much more. I am going to figure out more ways to contribute to the community who has given me so much so that one day I might join this list.",[5909,79580,79582],{"id":79581},"cloud-certifications","Cloud Certifications",[651,79584,79585],{},"I would really like to get a few cloud certifications from AWS and build from there. I am going to start with the Cloud Practitioner, Solutions Architect Associate, and Solutions Architect Professional.",[5909,79587,43654],{"id":43653},[651,79589,79590],{},"My new job is going to afford me the opportunity to give back to the community and create content. This will be through my blog, YouTube channel and live streaming. This is one of those things that I can't put a number on yet but when I do I just want to be consistent. Consistently provided value for you is my goal, not burnout.",[5909,79592,55316],{"id":6526},[651,79594,79595],{},"Career aspiration for me has always been to write a book. I'm not naive and realize that this isn't going to happen this year but It's always in the back of my head. As a way to kickstart this, I have a few ideas of e-books that I could create. This would be a shorter more manageable goal and something I could finish this year.",[5909,79597,21973],{"id":21972},[651,79599,79600],{},"I miss creating courses. The whole process from idea to curriculum design to production. Some of my most popular courses are 12-15 hours long and I just don't have the time for that these days. That doesn't mean that I couldn't create a smaller course in the range of 2-4 hours. I think If I aligned this with my ideas for an e-book I could use the content across both mediums and package them up.",[5909,79602,67442],{"id":67441},[651,79604,79605],{},"I stopped releasing my weekly newsletter this year because, you know 2020. I need to come up with a plan where this is a manageable task that I can stick to each week. The format and cadence is also something I need to examine.",[5909,79607,79609],{"id":79608},"conferences","Conferences",[651,79611,79612],{},"I miss conferences, a lot. I miss learning and talking to all of you. A goal of mine this is year is to speak at or at the very least attend a conference later this year if everything is safe.",[4542,79614,9042],{"id":9041},[651,79616,79617],{},"Leaving your comfort zone is never easy but the biggest gains come from the things that scare you the most. I'm looking forward to some sense of normalcy returning in 2021 and taking this new challenge on. What are you looking to learn in 2021? Do you have any resources or books you want to share? Please share below.",{"title":674,"searchDepth":790,"depth":790,"links":79619},[79620,79621,79622,79629,79638],{"id":79338,"depth":790,"text":79339},{"id":79374,"depth":790,"text":79375},{"id":79432,"depth":790,"text":79433,"children":79623},[79624,79625,79626,79627,79628],{"id":79445,"depth":892,"text":79446},{"id":79470,"depth":892,"text":79471},{"id":79509,"depth":892,"text":79510},{"id":79522,"depth":892,"text":79523},{"id":79529,"depth":892,"text":79530},{"id":39139,"depth":790,"text":79549,"children":79630},[79631,79632,79633,79634,79635,79636,79637],{"id":79569,"depth":892,"text":79570},{"id":79581,"depth":892,"text":79582},{"id":43653,"depth":892,"text":43654},{"id":6526,"depth":892,"text":55316},{"id":21972,"depth":892,"text":21973},{"id":67441,"depth":892,"text":67442},{"id":79608,"depth":892,"text":79609},{"id":9041,"depth":790,"text":9042},"In this article, I have some exciting personal news to share and I want to take a look ahead at the new year.",{"slug":79641,"date":79642,"published":797,"author":798,"tags":79643,"cover":79644},"happy-new-year-2021","2021-01-07T11:30:00.000Z",[11968],"isaac-smith-YwrdbQw0oco-unsplash.jpg",{"title":417,"description":79639},"blog/2021/01/07/happy-new-year-2021","St2KNnEChooEW_Zll2ivJzSkd_3d1nWGbbD4iQPU1lM",{"id":79649,"title":414,"body":79650,"description":80625,"extension":793,"meta":80626,"navigation":797,"path":415,"seo":80631,"stem":80632,"__hash__":80633},"content/blog/2021/01/08/network-throttling.md",{"type":648,"value":79651,"toc":80619},[79652,79655,79661,79664,79675,79678,79682,79685,79690,79693,79699,79702,79706,79722,80097,80100,80167,80174,80206,80209,80268,80277,80353,80356,80359,80370,80460,80465,80480,80483,80611,80613,80616],[651,79653,79654],{},"So you have this amazing loading animation, and you want to test it out but you have a problem. When you're working on your local machine there isn't much latency so you never see your animated art.",[651,79656,79657],{},[660,79658],{"alt":79659,"src":79660},"Fancy Animation","/images/blog/2021/01/08/bean-eater-200px_small.jpeg",[651,79662,79663],{},"While it's good that you want to test this out this is also an opportunity to test our application for visitors that might be on a slower connection. We have to remember that not everyone is lucky enough to have access to hi-speed internet like you and I do. There are many ways that you can test this out but we are going to cover the following in this tutorial.",[5316,79665,79666,79669,79672],{},[5332,79667,79668],{},"Chrome DevTools (Bandwidth Throttling)",[5332,79670,79671],{},"Client Side (Force Delay)",[5332,79673,79674],{},"Server Side (Force Delay)",[651,79676,79677],{},"The code samples in this article will use Vue on the frontend and Java on the backend but the concepts are the same regardless of languages or frameworks.",[4542,79679,79681],{"id":79680},"chrome-devtools","Chrome DevTools",[651,79683,79684],{},"As I mentioned in the intro it is important to test your applications on a slower connection. One way to accomplish this is by using the Chrome DevTools to throttle the network speed.",[1004,79686,79687],{},[651,79688,79689],{},"Network throttling is an intentional slowing down of internet speed. In web performance, network throttling, or network condition emulation, it is used to emulate low bandwidth conditions experienced by likely a large segment of a site's target user base",[651,79691,79692],{},"To do this open up DevTools and go to the Network tab. There is a dropdown on the first line that defaults to \"Online\". Change to what speed you want to imitate and reload the page to simulate that connection.",[651,79694,79695],{},[660,79696],{"alt":79697,"src":79698},"Chrome DevTools Network Throttling","/images/blog/2021/01/08/chrome-devtools-network-throttling.png",[651,79700,79701],{},"This is a great first step but this will throttle the entire connection and I often don't have the patience for this during development.",[4542,79703,79705],{"id":79704},"client-side-vue","Client-Side (Vue)",[651,79707,79708,79709,79714,79715,79718,79719,79721],{},"The next solution I want to cover is building in a delay to the client-side code. In the following example, I am using a ",[812,79710,79713],{"href":79711,"rel":79712},"https://icanhazdadjoke.com/",[816],"public API"," to return a random dad joke. When the component is mounted the ",[676,79716,79717],{},"loadJoke()"," method is called and a ",[676,79720,76139],{}," request is made to the API. We use state within the component to display the loading animation until the fetch request has been completed and we have a joke to display.",[669,79723,79725],{"className":50297,"code":79724,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003Cdiv class=\"dadjoke\">\n \u003Ch1>Random Dad Jokes\u003C/h1>\n \u003Cp v-if=\"loading\">\u003Cimg src=\"./assets/bean-eater-200px.gif\" />\u003C/p>\n \u003Cp v-else>{{ joke }}\u003C/p>\n \u003C/div>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: \"App\",\n data() {\n return {\n joke: \"\",\n loading: true,\n };\n },\n methods: {\n loadJoke() {\n fetch(\"https://icanhazdadjoke.com/\", {\n headers: {\n Accept: \"application/json\",\n },\n })\n .then((response) => {\n return response.json();\n })\n .then((data) => {\n if (data.status === 200) {\n this.joke = data.joke;\n this.loading = false;\n }\n })\n .catch((err) => console.log(err));\n },\n },\n mounted() {\n this.loadJoke();\n },\n};\n\u003C/script>\n",[676,79726,79727,79735,79750,79763,79794,79810,79818,79826,79830,79838,79846,79854,79860,79866,79875,79884,79888,79892,79896,79903,79915,79920,79930,79935,79940,79956,79967,79972,79988,80002,80015,80028,80032,80036,80056,80060,80064,80070,80081,80085,80089],{"__ignoreMap":674},[679,79728,79729,79731,79733],{"class":681,"line":682},[679,79730,4505],{"class":693},[679,79732,45982],{"class":4508},[679,79734,4519],{"class":693},[679,79736,79737,79739,79741,79743,79745,79748],{"class":681,"line":790},[679,79738,11738],{"class":693},[679,79740,4509],{"class":4508},[679,79742,4512],{"class":880},[679,79744,686],{"class":693},[679,79746,79747],{"class":689},"\"dadjoke\"",[679,79749,4519],{"class":693},[679,79751,79752,79754,79756,79759,79761],{"class":681,"line":892},[679,79753,4524],{"class":693},[679,79755,11859],{"class":4508},[679,79757,79758],{"class":693},">Random Dad Jokes\u003C/",[679,79760,11859],{"class":4508},[679,79762,4519],{"class":693},[679,79764,79765,79767,79769,79771,79773,79776,79778,79780,79782,79784,79787,79790,79792],{"class":681,"line":901},[679,79766,4524],{"class":693},[679,79768,651],{"class":4508},[679,79770,52441],{"class":880},[679,79772,686],{"class":693},[679,79774,79775],{"class":689},"\"loading\"",[679,79777,4545],{"class":693},[679,79779,660],{"class":4508},[679,79781,5361],{"class":880},[679,79783,686],{"class":693},[679,79785,79786],{"class":689},"\"./assets/bean-eater-200px.gif\"",[679,79788,79789],{"class":693}," />\u003C/",[679,79791,651],{"class":4508},[679,79793,4519],{"class":693},[679,79795,79796,79798,79800,79803,79806,79808],{"class":681,"line":909},[679,79797,4524],{"class":693},[679,79799,651],{"class":4508},[679,79801,79802],{"class":880}," v-else",[679,79804,79805],{"class":693},">{{ joke }}\u003C/",[679,79807,651],{"class":4508},[679,79809,4519],{"class":693},[679,79811,79812,79814,79816],{"class":681,"line":918},[679,79813,11840],{"class":693},[679,79815,4509],{"class":4508},[679,79817,4519],{"class":693},[679,79819,79820,79822,79824],{"class":681,"line":935},[679,79821,4586],{"class":693},[679,79823,45982],{"class":4508},[679,79825,4519],{"class":693},[679,79827,79828],{"class":681,"line":944},[679,79829,889],{"emptyLinePlaceholder":797},[679,79831,79832,79834,79836],{"class":681,"line":959},[679,79833,4505],{"class":693},[679,79835,47668],{"class":4508},[679,79837,4519],{"class":693},[679,79839,79840,79842,79844],{"class":681,"line":964},[679,79841,29245],{"class":685},[679,79843,50460],{"class":685},[679,79845,884],{"class":693},[679,79847,79848,79850,79852],{"class":681,"line":977},[679,79849,49970],{"class":693},[679,79851,64012],{"class":689},[679,79853,12083],{"class":693},[679,79855,79856,79858],{"class":681,"line":982},[679,79857,49979],{"class":880},[679,79859,2667],{"class":693},[679,79861,79862,79864],{"class":681,"line":988},[679,79863,21478],{"class":685},[679,79865,884],{"class":693},[679,79867,79868,79871,79873],{"class":681,"line":993},[679,79869,79870],{"class":693}," joke: ",[679,79872,3579],{"class":689},[679,79874,12083],{"class":693},[679,79876,79877,79880,79882],{"class":681,"line":2129},[679,79878,79879],{"class":693}," loading: ",[679,79881,3441],{"class":931},[679,79883,12083],{"class":693},[679,79885,79886],{"class":681,"line":2140},[679,79887,50000],{"class":693},[679,79889,79890],{"class":681,"line":2145},[679,79891,28483],{"class":693},[679,79893,79894],{"class":681,"line":2154},[679,79895,50502],{"class":693},[679,79897,79898,79901],{"class":681,"line":2159},[679,79899,79900],{"class":880}," loadJoke",[679,79902,2667],{"class":693},[679,79904,79905,79908,79910,79913],{"class":681,"line":2164},[679,79906,79907],{"class":880}," fetch",[679,79909,745],{"class":693},[679,79911,79912],{"class":689},"\"https://icanhazdadjoke.com/\"",[679,79914,56322],{"class":693},[679,79916,79917],{"class":681,"line":3134},[679,79918,79919],{"class":693}," headers: {\n",[679,79921,79922,79925,79928],{"class":681,"line":3139},[679,79923,79924],{"class":693}," Accept: ",[679,79926,79927],{"class":689},"\"application/json\"",[679,79929,12083],{"class":693},[679,79931,79932],{"class":681,"line":3144},[679,79933,79934],{"class":693}," },\n",[679,79936,79937],{"class":681,"line":3149},[679,79938,79939],{"class":693}," })\n",[679,79941,79942,79944,79946,79948,79950,79952,79954],{"class":681,"line":3169},[679,79943,21331],{"class":693},[679,79945,46728],{"class":880},[679,79947,51931],{"class":693},[679,79949,10447],{"class":2099},[679,79951,2378],{"class":693},[679,79953,21350],{"class":685},[679,79955,884],{"class":693},[679,79957,79958,79961,79963,79965],{"class":681,"line":3185},[679,79959,79960],{"class":685}," return",[679,79962,46743],{"class":693},[679,79964,28441],{"class":880},[679,79966,9317],{"class":693},[679,79968,79969],{"class":681,"line":3194},[679,79970,79971],{"class":693}," })\n",[679,79973,79974,79976,79978,79980,79982,79984,79986],{"class":681,"line":3199},[679,79975,21331],{"class":693},[679,79977,46728],{"class":880},[679,79979,51931],{"class":693},[679,79981,54091],{"class":2099},[679,79983,2378],{"class":693},[679,79985,21350],{"class":685},[679,79987,884],{"class":693},[679,79989,79990,79993,79996,79998,80000],{"class":681,"line":3212},[679,79991,79992],{"class":685}," if",[679,79994,79995],{"class":693}," (data.status ",[679,79997,51108],{"class":685},[679,79999,67965],{"class":931},[679,80001,4390],{"class":693},[679,80003,80004,80007,80010,80012],{"class":681,"line":3217},[679,80005,80006],{"class":931}," this",[679,80008,80009],{"class":693},".joke ",[679,80011,686],{"class":685},[679,80013,80014],{"class":693}," data.joke;\n",[679,80016,80017,80019,80022,80024,80026],{"class":681,"line":3222},[679,80018,80006],{"class":931},[679,80020,80021],{"class":693},".loading ",[679,80023,686],{"class":685},[679,80025,14559],{"class":931},[679,80027,1186],{"class":693},[679,80029,80030],{"class":681,"line":3227},[679,80031,27880],{"class":693},[679,80033,80034],{"class":681,"line":3232},[679,80035,79971],{"class":693},[679,80037,80038,80040,80042,80044,80046,80048,80050,80052,80054],{"class":681,"line":3499},[679,80039,21331],{"class":693},[679,80041,9394],{"class":880},[679,80043,51931],{"class":693},[679,80045,46786],{"class":2099},[679,80047,2378],{"class":693},[679,80049,21350],{"class":685},[679,80051,21371],{"class":693},[679,80053,21374],{"class":880},[679,80055,46795],{"class":693},[679,80057,80058],{"class":681,"line":3509},[679,80059,28763],{"class":693},[679,80061,80062],{"class":681,"line":3516},[679,80063,28483],{"class":693},[679,80065,80066,80068],{"class":681,"line":3531},[679,80067,54604],{"class":880},[679,80069,2667],{"class":693},[679,80071,80072,80074,80076,80079],{"class":681,"line":3536},[679,80073,27825],{"class":931},[679,80075,664],{"class":693},[679,80077,80078],{"class":880},"loadJoke",[679,80080,9317],{"class":693},[679,80082,80083],{"class":681,"line":3541},[679,80084,28483],{"class":693},[679,80086,80087],{"class":681,"line":3546},[679,80088,44055],{"class":693},[679,80090,80091,80093,80095],{"class":681,"line":3551},[679,80092,4586],{"class":693},[679,80094,47668],{"class":4508},[679,80096,4519],{"class":693},[651,80098,80099],{},"Even in a CodeSandbox environment, this is pretty fast and you don't see the loading animation.",[669,80101,80103],{"className":4496,"code":80102,"language":4498,"meta":674,"style":674},"\u003Ciframe src=\"https://codesandbox.io/embed/angry-fast-3te0x?fontsize=14&module=%2Fsrc%2FApp.vue&theme=dark\"\n style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n title=\"angry-fast-3te0x\"\n allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n >\u003C/iframe>\n",[676,80104,80105,80118,80128,80138,80148,80158],{"__ignoreMap":674},[679,80106,80107,80109,80111,80113,80115],{"class":681,"line":682},[679,80108,4505],{"class":693},[679,80110,59634],{"class":4508},[679,80112,5361],{"class":880},[679,80114,686],{"class":693},[679,80116,80117],{"class":689},"\"https://codesandbox.io/embed/angry-fast-3te0x?fontsize=14&module=%2Fsrc%2FApp.vue&theme=dark\"\n",[679,80119,80120,80123,80125],{"class":681,"line":790},[679,80121,80122],{"class":880}," style",[679,80124,686],{"class":693},[679,80126,80127],{"class":689},"\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n",[679,80129,80130,80133,80135],{"class":681,"line":892},[679,80131,80132],{"class":880}," title",[679,80134,686],{"class":693},[679,80136,80137],{"class":689},"\"angry-fast-3te0x\"\n",[679,80139,80140,80143,80145],{"class":681,"line":901},[679,80141,80142],{"class":880}," allow",[679,80144,686],{"class":693},[679,80146,80147],{"class":689},"\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n",[679,80149,80150,80153,80155],{"class":681,"line":909},[679,80151,80152],{"class":880}," sandbox",[679,80154,686],{"class":693},[679,80156,80157],{"class":689},"\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n",[679,80159,80160,80163,80165],{"class":681,"line":918},[679,80161,80162],{"class":693}," >\u003C/",[679,80164,59634],{"class":4508},[679,80166,4519],{"class":693},[651,80168,80169,80170,80173],{},"To force a delay you can use the ",[676,80171,80172],{},"setTimeout()"," function in JavaScript to set a timer which executes a function or specified piece of code once the timer expires. The first argument is the function or code you want to execute and the second argument is the delay in milliseconds. In this example, I am forcing a three-second delay.",[669,80175,80177],{"className":64365,"code":80176,"language":64367,"meta":674,"style":674},"mounted() {\n setTimeout(this.loadJoke, 3000);\n}\n",[676,80178,80179,80185,80202],{"__ignoreMap":674},[679,80180,80181,80183],{"class":681,"line":682},[679,80182,57647],{"class":880},[679,80184,2667],{"class":693},[679,80186,80187,80190,80192,80194,80197,80200],{"class":681,"line":790},[679,80188,80189],{"class":880}," setTimeout",[679,80191,745],{"class":693},[679,80193,4732],{"class":931},[679,80195,80196],{"class":693},".loadJoke, ",[679,80198,80199],{"class":931},"3000",[679,80201,1208],{"class":693},[679,80203,80204],{"class":681,"line":892},[679,80205,996],{"class":693},[651,80207,80208],{},"Now when we run our example we can have a three second delay and we can clearly see our loading animation.",[669,80210,80212],{"className":4496,"code":80211,"language":4498,"meta":674,"style":674},"\u003Ciframe src=\"https://codesandbox.io/embed/cocky-benz-o2x09?fontsize=14&module=%2Fsrc%2FApp.vue&theme=dark\"\n style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n title=\"cocky-benz-o2x09\"\n allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n >\u003C/iframe>\n",[676,80213,80214,80227,80235,80244,80252,80260],{"__ignoreMap":674},[679,80215,80216,80218,80220,80222,80224],{"class":681,"line":682},[679,80217,4505],{"class":693},[679,80219,59634],{"class":4508},[679,80221,5361],{"class":880},[679,80223,686],{"class":693},[679,80225,80226],{"class":689},"\"https://codesandbox.io/embed/cocky-benz-o2x09?fontsize=14&module=%2Fsrc%2FApp.vue&theme=dark\"\n",[679,80228,80229,80231,80233],{"class":681,"line":790},[679,80230,80122],{"class":880},[679,80232,686],{"class":693},[679,80234,80127],{"class":689},[679,80236,80237,80239,80241],{"class":681,"line":892},[679,80238,80132],{"class":880},[679,80240,686],{"class":693},[679,80242,80243],{"class":689},"\"cocky-benz-o2x09\"\n",[679,80245,80246,80248,80250],{"class":681,"line":901},[679,80247,80142],{"class":880},[679,80249,686],{"class":693},[679,80251,80147],{"class":689},[679,80253,80254,80256,80258],{"class":681,"line":909},[679,80255,80152],{"class":880},[679,80257,686],{"class":693},[679,80259,80157],{"class":689},[679,80261,80262,80264,80266],{"class":681,"line":918},[679,80263,80162],{"class":693},[679,80265,59634],{"class":4508},[679,80267,4519],{"class":693},[651,80269,80270,80271,80276],{},"The one downside to this approach is that you might forget to remove code like this and you don't want this getting checked in and pushed into production. One solution to this problem is to use environment variables to make sure you are in development mode when forcing a delay. In the following example, I am using ",[812,80272,80275],{"href":80273,"rel":80274},"https://github.com/vitejs/vite",[816],"Vite"," to get the environment and to set a custom delay.",[669,80278,80280],{"className":64365,"code":80279,"language":64367,"meta":674,"style":674},"mounted() {\n if (import.meta.env.DEV) {\n setTimeout(this.loadJoke, import.meta.env.VITE_FETCH_DELAY);\n } else {\n this.loadJoke;\n }\n}\n",[676,80281,80282,80288,80307,80330,80338,80345,80349],{"__ignoreMap":674},[679,80283,80284,80286],{"class":681,"line":682},[679,80285,57647],{"class":880},[679,80287,2667],{"class":693},[679,80289,80290,80292,80294,80296,80298,80300,80303,80305],{"class":681,"line":790},[679,80291,47550],{"class":685},[679,80293,4193],{"class":693},[679,80295,1999],{"class":685},[679,80297,664],{"class":693},[679,80299,11968],{"class":931},[679,80301,80302],{"class":693},".env.",[679,80304,56888],{"class":931},[679,80306,4390],{"class":693},[679,80308,80309,80311,80313,80315,80317,80319,80321,80323,80325,80328],{"class":681,"line":892},[679,80310,71329],{"class":880},[679,80312,745],{"class":693},[679,80314,4732],{"class":931},[679,80316,80196],{"class":693},[679,80318,1999],{"class":685},[679,80320,664],{"class":693},[679,80322,11968],{"class":931},[679,80324,80302],{"class":693},[679,80326,80327],{"class":931},"VITE_FETCH_DELAY",[679,80329,1208],{"class":693},[679,80331,80332,80334,80336],{"class":681,"line":901},[679,80333,59590],{"class":693},[679,80335,2256],{"class":685},[679,80337,884],{"class":693},[679,80339,80340,80342],{"class":681,"line":909},[679,80341,27825],{"class":931},[679,80343,80344],{"class":693},".loadJoke;\n",[679,80346,80347],{"class":681,"line":918},[679,80348,21405],{"class":693},[679,80350,80351],{"class":681,"line":935},[679,80352,996],{"class":693},[4542,80354,80355],{"id":17902},"Server-Side",[651,80357,80358],{},"The next solution I want to look at is building in a delay to the server-side code. This wouldn't work for the previous example because we didn't have control over that API. If you are the one working on the server-side code though you can force a delay there.",[651,80360,80361,80362,80365,80366,80369],{},"In the following example, I am using Java but you can accomplish this in whatever language you're using. I have a Spring Boot Controller that returns a random joke but before that happens we force a delay of three seconds using the static ",[676,80363,80364],{},"sleep()"," method in the ",[676,80367,80368],{},"Thread"," class.",[669,80371,80373],{"className":4107,"code":80372,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/jokes\")\npublic class JokeController {\n\n @GetMapping\n public String random() throws InterruptedException {\n Thread.sleep(3000);\n return \"What do you call a pig that knows karate? A pork chop!\";\n }\n}\n",[676,80374,80375,80381,80394,80405,80409,80416,80431,80443,80452,80456],{"__ignoreMap":674},[679,80376,80377,80379],{"class":681,"line":682},[679,80378,4116],{"class":693},[679,80380,9212],{"class":685},[679,80382,80383,80385,80387,80389,80392],{"class":681,"line":790},[679,80384,4116],{"class":693},[679,80386,9275],{"class":685},[679,80388,745],{"class":693},[679,80390,80391],{"class":689},"\"/api/jokes\"",[679,80393,1339],{"class":693},[679,80395,80396,80398,80400,80403],{"class":681,"line":892},[679,80397,6073],{"class":685},[679,80399,4512],{"class":685},[679,80401,80402],{"class":880}," JokeController",[679,80404,884],{"class":693},[679,80406,80407],{"class":681,"line":901},[679,80408,889],{"emptyLinePlaceholder":797},[679,80410,80411,80413],{"class":681,"line":909},[679,80412,6872],{"class":693},[679,80414,80415],{"class":685},"GetMapping\n",[679,80417,80418,80420,80422,80424,80426,80428],{"class":681,"line":918},[679,80419,6089],{"class":685},[679,80421,9289],{"class":693},[679,80423,5899],{"class":880},[679,80425,6700],{"class":693},[679,80427,9580],{"class":685},[679,80429,80430],{"class":693}," InterruptedException {\n",[679,80432,80433,80435,80437,80439,80441],{"class":681,"line":935},[679,80434,9606],{"class":693},[679,80436,9609],{"class":880},[679,80438,745],{"class":693},[679,80440,80199],{"class":931},[679,80442,1208],{"class":693},[679,80444,80445,80447,80450],{"class":681,"line":944},[679,80446,9444],{"class":685},[679,80448,80449],{"class":689}," \"What do you call a pig that knows karate? A pork chop!\"",[679,80451,1186],{"class":693},[679,80453,80454],{"class":681,"line":959},[679,80455,985],{"class":693},[679,80457,80458],{"class":681,"line":964},[679,80459,996],{"class":693},[651,80461,80462,80463,2391],{},"As with the client-side solution you should be careful about checking this in and letting this go into production. You could set an active profile in ",[676,80464,16242],{},[669,80466,80468],{"className":4107,"code":80467,"language":4109,"meta":674,"style":674},"spring.profiles.active=dev\n",[676,80469,80470],{"__ignoreMap":674},[679,80471,80472,80475,80477],{"class":681,"line":682},[679,80473,80474],{"class":693},"spring.profiles.active",[679,80476,686],{"class":685},[679,80478,80479],{"class":693},"dev\n",[651,80481,80482],{},"Back in your controller, you can get that value and as along as it's not a production environment you can force your delay.",[669,80484,80486],{"className":4107,"code":80485,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/jokes\")\npublic class JokeController {\n\n @Value(\"${spring.profiles.active}\")\n private String ENV_MODE;\n\n @GetMapping\n public String random() throws InterruptedException {\n if( ENV_MODE != \"prod\" ) {\n Thread.sleep(3000);\n }\n return \"What do you call a pig that knows karate? A pork chop!\";\n }\n}\n",[676,80487,80488,80494,80506,80516,80520,80533,80540,80544,80550,80564,80578,80591,80595,80603,80607],{"__ignoreMap":674},[679,80489,80490,80492],{"class":681,"line":682},[679,80491,4116],{"class":693},[679,80493,9212],{"class":685},[679,80495,80496,80498,80500,80502,80504],{"class":681,"line":790},[679,80497,4116],{"class":693},[679,80499,9275],{"class":685},[679,80501,745],{"class":693},[679,80503,80391],{"class":689},[679,80505,1339],{"class":693},[679,80507,80508,80510,80512,80514],{"class":681,"line":892},[679,80509,6073],{"class":685},[679,80511,4512],{"class":685},[679,80513,80402],{"class":880},[679,80515,884],{"class":693},[679,80517,80518],{"class":681,"line":901},[679,80519,889],{"emptyLinePlaceholder":797},[679,80521,80522,80524,80526,80528,80531],{"class":681,"line":909},[679,80523,6872],{"class":693},[679,80525,31784],{"class":685},[679,80527,745],{"class":693},[679,80529,80530],{"class":689},"\"${spring.profiles.active}\"",[679,80532,1339],{"class":693},[679,80534,80535,80537],{"class":681,"line":918},[679,80536,9232],{"class":685},[679,80538,80539],{"class":693}," String ENV_MODE;\n",[679,80541,80542],{"class":681,"line":935},[679,80543,889],{"emptyLinePlaceholder":797},[679,80545,80546,80548],{"class":681,"line":944},[679,80547,6872],{"class":693},[679,80549,80415],{"class":685},[679,80551,80552,80554,80556,80558,80560,80562],{"class":681,"line":959},[679,80553,6089],{"class":685},[679,80555,9289],{"class":693},[679,80557,5899],{"class":880},[679,80559,6700],{"class":693},[679,80561,9580],{"class":685},[679,80563,80430],{"class":693},[679,80565,80566,80568,80571,80573,80576],{"class":681,"line":964},[679,80567,1249],{"class":685},[679,80569,80570],{"class":693},"( ENV_MODE ",[679,80572,1587],{"class":685},[679,80574,80575],{"class":689}," \"prod\"",[679,80577,5581],{"class":693},[679,80579,80580,80583,80585,80587,80589],{"class":681,"line":977},[679,80581,80582],{"class":693}," Thread.",[679,80584,9609],{"class":880},[679,80586,745],{"class":693},[679,80588,80199],{"class":931},[679,80590,1208],{"class":693},[679,80592,80593],{"class":681,"line":982},[679,80594,1290],{"class":693},[679,80596,80597,80599,80601],{"class":681,"line":988},[679,80598,9444],{"class":685},[679,80600,80449],{"class":689},[679,80602,1186],{"class":693},[679,80604,80605],{"class":681,"line":993},[679,80606,985],{"class":693},[679,80608,80609],{"class":681,"line":2129},[679,80610,996],{"class":693},[4542,80612,9042],{"id":9041},[651,80614,80615],{},"This question came to me from one of our students and I thought it was a good one. Just remember while you might want to test one component or feature for latency it is important to test the entire application with a slower connection to ensure each visitor has a good experience.",[786,80617,80618],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":80620},[80621,80622,80623,80624],{"id":79680,"depth":790,"text":79681},{"id":79704,"depth":790,"text":79705},{"id":17902,"depth":790,"text":80355},{"id":9041,"depth":790,"text":9042},"In this tutorial, I will show you can force a delay in a web application for testing purposes.",{"slug":80627,"date":80628,"published":797,"author":798,"tags":80629,"cover":80630},"network-throttling","2021-01-08T09:30:00.000Z",[25134,44169,4109],"./network-throttling-cover.png",{"title":414,"description":80625},"blog/2021/01/08/network-throttling","8KlfJwh8jyf1uhgYhcG-g6rGsSxRWX0MuAOfZm9Gqt8",{"id":80635,"title":411,"body":80636,"description":82092,"extension":793,"meta":82093,"navigation":797,"path":412,"seo":82099,"stem":82100,"__hash__":82101},"content/blog/2021/01/22/full-stack-java.md",{"type":648,"value":80637,"toc":82078},[80638,80645,80649,80674,80677,80680,80685,80688,80691,80693,80699,80702,80705,80708,80711,80715,80718,80723,80726,80729,80741,80743,80746,80752,80758,80764,80767,80828,80831,80896,80900,80903,80985,80992,80998,81000,81019,81024,81030,81042,81045,81049,81058,81152,81163,81165,81170,81294,81298,81309,81485,81491,81497,81500,81503,81510,81519,81549,81572,81788,81804,81910,81913,81928,81934,82029,82035,82041,82044,82066,82068,82071,82075],[651,80639,80640,80641,664],{},"In this tutorial, you will learn how to build a full-stack application that uses Vue for the frontend and Spring Boot for the backend. As always you can find the code associated with this over on ",[812,80642,17458],{"href":80643,"rel":80644},"https://github.com/danvega/full-stack-java-vue",[816],[4542,80646,80648],{"id":80647},"table-of-contents","Table of Contents",[5316,80650,80651,80654,80657,80672],{},[5332,80652,80653],{},"Design decisions",[5332,80655,80656],{},"Monolithic architecture",[5332,80658,80659,80660],{},"Creating the project\n",[5316,80661,80662,80665,80667,80669],{},[5332,80663,80664],{},"Requirements",[5332,80666,7077],{},[5332,80668,43866],{},[5332,80670,80671],{},"Build plugins",[5332,80673,9042],{},[4542,80675,80653],{"id":80676},"design-decisions",[651,80678,80679],{},"Creating a brand new application from scratch is exciting, isn't it? The possibilities are endless but the freedom to create whatever you want comes with some future implications. I know your first instinct is to jump right into the deep end and start writing code but before you do, you should have a clear idea of what you're trying to build.",[1004,80681,80682],{},[651,80683,80684],{},"\"IF YOU FAIL TO PLAN, YOU ARE PLANNING TO FAIL\"",[651,80686,80687],{},"Without fully understanding the problems you are trying to solve for I can't recommend the absolute best solution but I will give you a general approach. One of the first questions I try to answer is \"What is the structure of the development team on this project?\". This shouldn't be the driving force for all of your decisions but it is an important one.",[651,80689,80690],{},"If you have full-stack developers that will be working on both sides of the application a monolithic approach could make sense. If you have separate teams working on the frontend and backend splitting these projects into two independent projects might make sense. There are pros and cons to each and these should really be evaluated on a per-project basis.",[4542,80692,80656],{"id":37458},[651,80694,80695],{},[660,80696],{"alt":80697,"src":80698},"Monolith","/images/blog/2021/01/22/trent-erwin-zvKMpmvLlSo-unsplash.jpg",[651,80700,80701],{},"In this article we are going to focus on building a Monolithic application that uses Vue on the fronted and Spring Boot on the backend. A lot of developers I come across are familiar with both of these technologies but aren't quite sure how to configure them in a single project.",[651,80703,80704],{},"Having a single deployable asset simplifies the deployment process which I know a lot of developers don't like to deal with. If you're a single developer or a team of full-stack developers this is a great option. There are some tricks to getting this all into a single project but that is exactly what you will learn in this tutorial.",[651,80706,80707],{},"The biggest advantage to this approach is that you end up with a single artifact to deploy. If you don't have a team dedicated to the DevOps side of development this will help cut down some of the complexities of moving to production.",[651,80709,80710],{},"This also means that if you want to change something small on the frontend you will to redeploy the entire application. If you have a large team there could be a lot of paths crossing which could end up causing lots of merge conflicts. I don't know about you but the only thing worse than real life conflicts is dealing with merge conflicts 😳",[4542,80712,80714],{"id":80713},"creating-the-project","Creating the project",[651,80716,80717],{},"You will start by creating a Spring Boot project that will act as the foundation for your project. You can then generate a new Vue project in the Spring Boot project and with some configuration you will be able to put all of the puzzle pieces together.",[1004,80719,80720],{},[651,80721,80722],{},"This could be done using any front end technology (Angular,React,Vue,Svelte,etc..)",[5909,80724,80664],{"id":80725},"requirements",[651,80727,80728],{},"I am going to make some assumptions about you before we get started. You should be familiar and have some experience with the following:",[5316,80730,80731,80733,80735,80737,80739],{},[5332,80732,36422],{},[5332,80734,38919],{},[5332,80736,7077],{},[5332,80738,46520],{},[5332,80740,43568],{},[5909,80742,7077],{"id":20742},[651,80744,80745],{},"My IDE of choice is IntelliJ Ultimate Edition. If you're using the same you can create the project right in the IDE:",[651,80747,80748],{},[660,80749],{"alt":80750,"src":80751},"Spring Initializr IntelliJ","/images/blog/2021/01/22/intelli_new_spring_project.png",[651,80753,80754,80755],{},"If you're not you can do the same from ",[812,80756,51358],{"href":51358,"rel":80757},[816],[651,80759,80760],{},[660,80761],{"alt":80762,"src":80763},"Spring Initializr Web","/images/blog/2021/01/22/start_spring_io.png",[651,80765,80766],{},"Start by creating a new Spring Boot project with the following properties:",[5316,80768,80769,80775,80781,80787,80793,80799,80805,80810,80815],{},[5332,80770,80771,80774],{},[2939,80772,80773],{},"Group:"," dev.danvega",[5332,80776,80777,80780],{},[2939,80778,80779],{},"Artifact:"," fsjava",[5332,80782,80783,80786],{},[2939,80784,80785],{},"Type:"," Maven",[5332,80788,80789,80792],{},[2939,80790,80791],{},"Spring Boot:"," 2.4.2 (or latest)",[5332,80794,80795,80798],{},[2939,80796,80797],{},"Language:"," Java",[5332,80800,80801,80804],{},[2939,80802,80803],{},"Packaging:"," Jar",[5332,80806,80807,35112],{},[2939,80808,80809],{},"Java Version:",[5332,80811,80812,80804],{},[2939,80813,80814],{},"Package:",[5332,80816,80817,80820],{},[2939,80818,80819],{},"Dependencies:",[5316,80821,80822,80825],{},[5332,80823,80824],{},"Spring Boot DevTools",[5332,80826,80827],{},"Spring Web",[651,80829,80830],{},"The first thing I like to do with any new project is to make sure it runs as expected. Open up the main application class (I renamed mine to Application.java) and run it to make sure",[669,80832,80834],{"className":4107,"code":80833,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n}\n",[676,80835,80836,80842,80852,80856,80876,80884,80888,80892],{"__ignoreMap":674},[679,80837,80838,80840],{"class":681,"line":682},[679,80839,4116],{"class":693},[679,80841,6068],{"class":685},[679,80843,80844,80846,80848,80850],{"class":681,"line":790},[679,80845,6073],{"class":685},[679,80847,4512],{"class":685},[679,80849,16878],{"class":880},[679,80851,884],{"class":693},[679,80853,80854],{"class":681,"line":892},[679,80855,889],{"emptyLinePlaceholder":797},[679,80857,80858,80860,80862,80864,80866,80868,80870,80872,80874],{"class":681,"line":901},[679,80859,6089],{"class":685},[679,80861,6092],{"class":685},[679,80863,6095],{"class":685},[679,80865,6098],{"class":880},[679,80867,745],{"class":693},[679,80869,4758],{"class":685},[679,80871,16901],{"class":693},[679,80873,6108],{"class":2099},[679,80875,4390],{"class":693},[679,80877,80878,80880,80882],{"class":681,"line":909},[679,80879,6115],{"class":693},[679,80881,6118],{"class":880},[679,80883,16914],{"class":693},[679,80885,80886],{"class":681,"line":918},[679,80887,985],{"class":693},[679,80889,80890],{"class":681,"line":935},[679,80891,889],{"emptyLinePlaceholder":797},[679,80893,80894],{"class":681,"line":944},[679,80895,996],{"class":693},[5259,80897,80899],{"id":80898},"message-controller","Message Controller",[651,80901,80902],{},"The first class you are going to create is a simple controller that contains a single mapping. This will be a public REST endpoint that you can call from your client application.",[669,80904,80906],{"className":4107,"code":80905,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/messages\")\npublic class MessageController {\n\n @GetMapping(\"/hello\")\n public String hello() {\n return \"Full Stack Java with Spring Boot & VueJS!\";\n }\n\n}\n",[676,80907,80908,80914,80927,80938,80942,80954,80964,80973,80977,80981],{"__ignoreMap":674},[679,80909,80910,80912],{"class":681,"line":682},[679,80911,4116],{"class":693},[679,80913,9212],{"class":685},[679,80915,80916,80918,80920,80922,80925],{"class":681,"line":790},[679,80917,4116],{"class":693},[679,80919,9275],{"class":685},[679,80921,745],{"class":693},[679,80923,80924],{"class":689},"\"/api/messages\"",[679,80926,1339],{"class":693},[679,80928,80929,80931,80933,80936],{"class":681,"line":892},[679,80930,6073],{"class":685},[679,80932,4512],{"class":685},[679,80934,80935],{"class":880}," MessageController",[679,80937,884],{"class":693},[679,80939,80940],{"class":681,"line":901},[679,80941,889],{"emptyLinePlaceholder":797},[679,80943,80944,80946,80948,80950,80952],{"class":681,"line":909},[679,80945,6872],{"class":693},[679,80947,20852],{"class":685},[679,80949,745],{"class":693},[679,80951,73233],{"class":689},[679,80953,1339],{"class":693},[679,80955,80956,80958,80960,80962],{"class":681,"line":918},[679,80957,6089],{"class":685},[679,80959,9289],{"class":693},[679,80961,72963],{"class":880},[679,80963,2667],{"class":693},[679,80965,80966,80968,80971],{"class":681,"line":935},[679,80967,9444],{"class":685},[679,80969,80970],{"class":689}," \"Full Stack Java with Spring Boot & VueJS!\"",[679,80972,1186],{"class":693},[679,80974,80975],{"class":681,"line":944},[679,80976,985],{"class":693},[679,80978,80979],{"class":681,"line":959},[679,80980,889],{"emptyLinePlaceholder":797},[679,80982,80983],{"class":681,"line":964},[679,80984,996],{"class":693},[651,80986,80987,80988],{},"With DevTools your application should have restarted after this change, if it hasn't please re-run the application. Open up a browser and navigate to ",[812,80989,80990],{"href":80990,"rel":80991},"http://localhost:8080/api/messages/hello",[816],[651,80993,80994],{},[660,80995],{"alt":80996,"src":80997},"Hello Endpoint","/images/blog/2021/01/22/hello_endpoint.png",[5909,80999,43866],{"id":44169},[651,81001,81002,81003,81006,81007,81010,81011,81014,81015,81018],{},"Next, you will create the frontend application using the ",[812,81004,57067],{"href":57065,"rel":81005},[816],". If you're in IntelliJ you can open up the terminal or open up what ever shell you use. If you have the Vue CLI installed you can navigate to the ",[676,81008,81009],{},"/src"," folder and run the command ",[676,81012,81013],{},"vue create frontend"," which will generate a project into ",[676,81016,81017],{},"/src/frontend",". For this project I am generating a Vue 3 application but this will also work with v2.",[651,81020,81021],{},[660,81022],{"alt":57067,"src":81023},"/images/blog/2021/01/22/vue_cli.png",[651,81025,81026,81027,81029],{},"Once the application has been created navigate to ",[676,81028,81017],{}," and run the application.",[669,81031,81032],{"className":5851,"code":58127,"language":5853,"meta":674,"style":674},[676,81033,81034],{"__ignoreMap":674},[679,81035,81036,81038,81040],{"class":681,"line":682},[679,81037,24568],{"class":880},[679,81039,16486],{"class":689},[679,81041,27433],{"class":689},[651,81043,81044],{},"If your Spring Boot application isn't running the client app will start on port 8080, if it is it will find the next available port. At this point you should be able to run both of the applications independently.",[5909,81046,81048],{"id":81047},"vue-configuration","Vue Configuration",[651,81050,81051,81052,81055,81056,62609],{},"Next let's add some configuration so to our Vue application that will solve a couple of problems. Create a new file in the root of the ",[676,81053,81054],{},"/frontend"," folder named ",[676,81057,62608],{},[669,81059,81061],{"className":64365,"code":81060,"language":64367,"meta":674,"style":674},"// vue.config.js\nmodule.exports = {\n // https://cli.vuejs.org/config/#devserver-proxy\n devServer: {\n port: 3000,\n proxy: {\n '/api': {\n target: 'http://localhost:8080',\n ws: true,\n changeOrigin: true\n }\n }\n }\n}\n",[676,81062,81063,81068,81080,81085,81089,81098,81103,81110,81120,81129,81136,81140,81144,81148],{"__ignoreMap":674},[679,81064,81065],{"class":681,"line":682},[679,81066,81067],{"class":1400},"// vue.config.js\n",[679,81069,81070,81072,81074,81076,81078],{"class":681,"line":790},[679,81071,27515],{"class":931},[679,81073,664],{"class":693},[679,81075,43903],{"class":931},[679,81077,6883],{"class":685},[679,81079,884],{"class":693},[679,81081,81082],{"class":681,"line":892},[679,81083,81084],{"class":1400}," // https://cli.vuejs.org/config/#devserver-proxy\n",[679,81086,81087],{"class":681,"line":901},[679,81088,62631],{"class":693},[679,81090,81091,81094,81096],{"class":681,"line":909},[679,81092,81093],{"class":693}," port: ",[679,81095,80199],{"class":931},[679,81097,12083],{"class":693},[679,81099,81100],{"class":681,"line":918},[679,81101,81102],{"class":693}," proxy: {\n",[679,81104,81105,81108],{"class":681,"line":935},[679,81106,81107],{"class":689}," '/api'",[679,81109,28468],{"class":693},[679,81111,81112,81115,81118],{"class":681,"line":944},[679,81113,81114],{"class":693}," target: ",[679,81116,81117],{"class":689},"'http://localhost:8080'",[679,81119,12083],{"class":693},[679,81121,81122,81125,81127],{"class":681,"line":959},[679,81123,81124],{"class":693}," ws: ",[679,81126,3441],{"class":931},[679,81128,12083],{"class":693},[679,81130,81131,81134],{"class":681,"line":964},[679,81132,81133],{"class":693}," changeOrigin: ",[679,81135,5134],{"class":931},[679,81137,81138],{"class":681,"line":977},[679,81139,25517],{"class":693},[679,81141,81142],{"class":681,"line":982},[679,81143,1290],{"class":693},[679,81145,81146],{"class":681,"line":988},[679,81147,985],{"class":693},[679,81149,81150],{"class":681,"line":993},[679,81151,996],{"class":693},[651,81153,81154,81155,81158,81159,81162],{},"This will set the port to 3000 so we don't collide with the Spring Boot application. Next you're setting a proxy so that any request that starts with ",[676,81156,81157],{},"/api"," will be forwarded to ",[812,81160,11697],{"href":11697,"rel":81161},[816],". This means that you don't have to worry about setting up different environment variables for development and production.",[5259,81164,49477],{"id":70186},[651,81166,81167,81169],{},[676,81168,49477],{}," is the main component that is loaded in your Vue application. You can start by cleaning this up and removing some of the default styles.",[669,81171,81173],{"className":50297,"code":81172,"language":44169,"meta":674,"style":674},"\u003Ctemplate>\n \u003CHelloWorld />\n\u003C/template>\n\n\u003Cscript>\nimport HelloWorld from './components/HelloWorld.vue'\n\nexport default {\n name: 'App',\n components: {\n HelloWorld\n }\n}\n\u003C/script>\n\n\u003Cstyle>\n\n\u003C/style>\n",[676,81174,81175,81183,81192,81200,81204,81212,81224,81228,81236,81245,81249,81254,81258,81262,81270,81274,81282,81286],{"__ignoreMap":674},[679,81176,81177,81179,81181],{"class":681,"line":682},[679,81178,4505],{"class":693},[679,81180,45982],{"class":4508},[679,81182,4519],{"class":693},[679,81184,81185,81187,81190],{"class":681,"line":790},[679,81186,11738],{"class":693},[679,81188,81189],{"class":4508},"HelloWorld",[679,81191,5387],{"class":693},[679,81193,81194,81196,81198],{"class":681,"line":892},[679,81195,4586],{"class":693},[679,81197,45982],{"class":4508},[679,81199,4519],{"class":693},[679,81201,81202],{"class":681,"line":901},[679,81203,889],{"emptyLinePlaceholder":797},[679,81205,81206,81208,81210],{"class":681,"line":909},[679,81207,4505],{"class":693},[679,81209,47668],{"class":4508},[679,81211,4519],{"class":693},[679,81213,81214,81216,81219,81221],{"class":681,"line":918},[679,81215,1999],{"class":685},[679,81217,81218],{"class":693}," HelloWorld ",[679,81220,28887],{"class":685},[679,81222,81223],{"class":689}," './components/HelloWorld.vue'\n",[679,81225,81226],{"class":681,"line":935},[679,81227,889],{"emptyLinePlaceholder":797},[679,81229,81230,81232,81234],{"class":681,"line":944},[679,81231,29245],{"class":685},[679,81233,50460],{"class":685},[679,81235,884],{"class":693},[679,81237,81238,81240,81243],{"class":681,"line":959},[679,81239,49970],{"class":693},[679,81241,81242],{"class":689},"'App'",[679,81244,12083],{"class":693},[679,81246,81247],{"class":681,"line":964},[679,81248,54590],{"class":693},[679,81250,81251],{"class":681,"line":977},[679,81252,81253],{"class":693}," HelloWorld\n",[679,81255,81256],{"class":681,"line":982},[679,81257,21405],{"class":693},[679,81259,81260],{"class":681,"line":988},[679,81261,996],{"class":693},[679,81263,81264,81266,81268],{"class":681,"line":993},[679,81265,4586],{"class":693},[679,81267,47668],{"class":4508},[679,81269,4519],{"class":693},[679,81271,81272],{"class":681,"line":2129},[679,81273,889],{"emptyLinePlaceholder":797},[679,81275,81276,81278,81280],{"class":681,"line":2140},[679,81277,4505],{"class":693},[679,81279,786],{"class":4508},[679,81281,4519],{"class":693},[679,81283,81284],{"class":681,"line":2145},[679,81285,889],{"emptyLinePlaceholder":797},[679,81287,81288,81290,81292],{"class":681,"line":2154},[679,81289,4586],{"class":693},[679,81291,786],{"class":4508},[679,81293,4519],{"class":693},[5909,81295,81297],{"id":81296},"helloworld-component","HelloWorld Component",[651,81299,81300,81301,81304,81305,81308],{},"Last step on the frontend is to remove the boiler-plate code from the ",[676,81302,81303],{},"HelloWorld.vue"," component. In this component you can make a fetch request to your backend API to retrieve the message that is being exposed on your hello endpoint. I am doing this in the ",[676,81306,81307],{},"mounted()"," lifecycle hook and I am using the Fetch API.",[669,81310,81312],{"className":4496,"code":81311,"language":4498,"meta":674,"style":674},"\u003Ctemplate>\n \u003Ch1>{{ msg }}\u003C/h1>\n\u003C/template>\n\n\u003Cscript>\nexport default {\n name: 'HelloWorld',\n data() {\n return {\n msg: ''\n }\n },\n mounted() {\n fetch(\"/api/messages/hello\")\n .then((response) => response.text())\n .then((data) => {\n this.msg = data;\n });\n }\n}\n\u003C/script>\n",[676,81313,81314,81322,81334,81342,81346,81354,81362,81371,81377,81383,81390,81394,81398,81404,81416,81436,81452,81465,81469,81473,81477],{"__ignoreMap":674},[679,81315,81316,81318,81320],{"class":681,"line":682},[679,81317,4505],{"class":693},[679,81319,45982],{"class":4508},[679,81321,4519],{"class":693},[679,81323,81324,81326,81328,81330,81332],{"class":681,"line":790},[679,81325,11738],{"class":693},[679,81327,11859],{"class":4508},[679,81329,57931],{"class":693},[679,81331,11859],{"class":4508},[679,81333,4519],{"class":693},[679,81335,81336,81338,81340],{"class":681,"line":892},[679,81337,4586],{"class":693},[679,81339,45982],{"class":4508},[679,81341,4519],{"class":693},[679,81343,81344],{"class":681,"line":901},[679,81345,889],{"emptyLinePlaceholder":797},[679,81347,81348,81350,81352],{"class":681,"line":909},[679,81349,4505],{"class":693},[679,81351,47668],{"class":4508},[679,81353,4519],{"class":693},[679,81355,81356,81358,81360],{"class":681,"line":918},[679,81357,29245],{"class":685},[679,81359,50460],{"class":685},[679,81361,884],{"class":693},[679,81363,81364,81366,81369],{"class":681,"line":935},[679,81365,49970],{"class":693},[679,81367,81368],{"class":689},"'HelloWorld'",[679,81370,12083],{"class":693},[679,81372,81373,81375],{"class":681,"line":944},[679,81374,49979],{"class":880},[679,81376,2667],{"class":693},[679,81378,81379,81381],{"class":681,"line":959},[679,81380,21478],{"class":685},[679,81382,884],{"class":693},[679,81384,81385,81388],{"class":681,"line":964},[679,81386,81387],{"class":693}," msg: ",[679,81389,65309],{"class":689},[679,81391,81392],{"class":681,"line":977},[679,81393,985],{"class":693},[679,81395,81396],{"class":681,"line":982},[679,81397,28483],{"class":693},[679,81399,81400,81402],{"class":681,"line":988},[679,81401,54604],{"class":880},[679,81403,2667],{"class":693},[679,81405,81406,81409,81411,81414],{"class":681,"line":993},[679,81407,81408],{"class":880}," fetch",[679,81410,745],{"class":693},[679,81412,81413],{"class":689},"\"/api/messages/hello\"",[679,81415,1339],{"class":693},[679,81417,81418,81420,81422,81424,81426,81428,81430,81432,81434],{"class":681,"line":2129},[679,81419,27839],{"class":693},[679,81421,46728],{"class":880},[679,81423,51931],{"class":693},[679,81425,10447],{"class":2099},[679,81427,2378],{"class":693},[679,81429,21350],{"class":685},[679,81431,46743],{"class":693},[679,81433,11464],{"class":880},[679,81435,40172],{"class":693},[679,81437,81438,81440,81442,81444,81446,81448,81450],{"class":681,"line":2140},[679,81439,27839],{"class":693},[679,81441,46728],{"class":880},[679,81443,51931],{"class":693},[679,81445,54091],{"class":2099},[679,81447,2378],{"class":693},[679,81449,21350],{"class":685},[679,81451,884],{"class":693},[679,81453,81454,81457,81460,81462],{"class":681,"line":2145},[679,81455,81456],{"class":931}," this",[679,81458,81459],{"class":693},".msg ",[679,81461,686],{"class":685},[679,81463,81464],{"class":693}," data;\n",[679,81466,81467],{"class":681,"line":2154},[679,81468,47293],{"class":693},[679,81470,81471],{"class":681,"line":2159},[679,81472,21405],{"class":693},[679,81474,81475],{"class":681,"line":2164},[679,81476,996],{"class":693},[679,81478,81479,81481,81483],{"class":681,"line":3134},[679,81480,4586],{"class":693},[679,81482,47668],{"class":4508},[679,81484,4519],{"class":693},[651,81486,81487,81488,81490],{},"If you run the Vue application using ",[676,81489,61701],{}," you should see the message from your backend API being displayed.",[651,81492,81493],{},[660,81494],{"alt":81495,"src":81496},"Vue Hello World","/images/blog/2021/01/22/vue_hello_world.png",[651,81498,81499],{},"My development workflow consists of running these as separate applications. I usually start up a new shell in the background for running the Vue application. This gives me one less distraction in IntelliJ and I appreciate the larger iTerm window to view anything going wrong with the client.",[5909,81501,80671],{"id":81502},"build-plugins",[651,81504,81505,81506,81509],{},"When it comes to production the goal of our Monolithic application is to have a single deployable artifact. To accomplish this you will need to build a production version of our VueJS application and copy that to your ",[676,81507,81508],{},"/target/classes/static"," directory.",[651,81511,76105,81512,81514,81515,81518],{},[676,81513,57223],{}," in the static folder this is the file that will be served when you hit the root. Finally as part of that build process Spring Boot will build a runnable JAR. Open up your ",[676,81516,81517],{},"pom.xml"," and make sure you have the following properties set:",[669,81520,81522],{"className":9101,"code":81521,"language":9103,"meta":674,"style":674},"\u003Cproperties>\n \u003Cjava.version>11\u003C/java.version>\n \u003Cnode.version>v10.15.0\u003C/node.version>\n \u003Cnpm.version>6.14.3\u003C/npm.version>\n\u003C/properties>\n",[676,81523,81524,81529,81534,81539,81544],{"__ignoreMap":674},[679,81525,81526],{"class":681,"line":682},[679,81527,81528],{},"\u003Cproperties>\n",[679,81530,81531],{"class":681,"line":790},[679,81532,81533],{}," \u003Cjava.version>11\u003C/java.version>\n",[679,81535,81536],{"class":681,"line":892},[679,81537,81538],{}," \u003Cnode.version>v10.15.0\u003C/node.version>\n",[679,81540,81541],{"class":681,"line":901},[679,81542,81543],{}," \u003Cnpm.version>6.14.3\u003C/npm.version>\n",[679,81545,81546],{"class":681,"line":909},[679,81547,81548],{},"\u003C/properties>\n",[651,81550,81551,81552,81555,81556,81561,81562,23212,81565,81568,81569,81509],{},"Next, locate the ",[676,81553,81554],{},"\u003Cbuild>"," section and add the ",[812,81557,81560],{"href":81558,"rel":81559},"https://github.com/eirslett/frontend-maven-plugin",[816],"frontend-maven-plugin",". This plugin downloads/installs Node and NPM locally for your project, and runs ",[676,81563,81564],{},"npm install",[676,81566,81567],{},"npm build"," to build your Vue application. The output for this build is stored in the ",[676,81570,81571],{},"/src/frontend/dist",[669,81573,81575],{"className":9101,"code":81574,"language":9103,"meta":674,"style":674},"\u003Cplugin>\n \u003CgroupId>com.github.eirslett\u003C/groupId>\n \u003CartifactId>frontend-maven-plugin\u003C/artifactId>\n \u003Cversion>1.7.6\u003C/version>\n\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cid>Install node and npm\u003C/id>\n \u003Cgoals>\n \u003Cgoal>install-node-and-npm\u003C/goal>\n \u003C/goals>\n \u003Cphase>generate-resources\u003C/phase>\n \u003Cconfiguration>\n \u003CnodeVersion>${node.version}\u003C/nodeVersion>\n \u003CnpmVersion>${npm.version}\u003C/npmVersion>\n \u003C/configuration>\n \u003C/execution>\n\n \u003Cexecution>\n \u003Cid>npm install\u003C/id>\n \u003Cgoals>\n \u003Cgoal>npm\u003C/goal>\n \u003C/goals>\n \u003Cphase>generate-resources\u003C/phase>\n \u003Cconfiguration>\n \u003Carguments>install\u003C/arguments>\n \u003C/configuration>\n \u003C/execution>\n\n \u003Cexecution>\n \u003Cid>npm build\u003C/id>\n \u003Cgoals>\n \u003Cgoal>npm\u003C/goal>\n \u003C/goals>\n \u003Cphase>generate-resources\u003C/phase>\n \u003Cconfiguration>\n \u003Carguments>run build\u003C/arguments>\n \u003C/configuration>\n \u003C/execution>\n \u003C/executions>\n \u003Cconfiguration>\n \u003CnodeVersion>${node.version}\u003C/nodeVersion>\n \u003CworkingDirectory>src/frontend\u003C/workingDirectory>\n \u003C/configuration>\n\n\u003C/plugin>\n",[676,81576,81577,81582,81587,81592,81597,81601,81606,81611,81616,81621,81626,81631,81636,81641,81646,81651,81656,81661,81665,81669,81674,81678,81683,81687,81691,81695,81700,81704,81708,81712,81716,81721,81725,81729,81733,81737,81741,81746,81750,81754,81759,81764,81769,81774,81779,81783],{"__ignoreMap":674},[679,81578,81579],{"class":681,"line":682},[679,81580,81581],{},"\u003Cplugin>\n",[679,81583,81584],{"class":681,"line":790},[679,81585,81586],{}," \u003CgroupId>com.github.eirslett\u003C/groupId>\n",[679,81588,81589],{"class":681,"line":892},[679,81590,81591],{}," \u003CartifactId>frontend-maven-plugin\u003C/artifactId>\n",[679,81593,81594],{"class":681,"line":901},[679,81595,81596],{}," \u003Cversion>1.7.6\u003C/version>\n",[679,81598,81599],{"class":681,"line":909},[679,81600,889],{"emptyLinePlaceholder":797},[679,81602,81603],{"class":681,"line":918},[679,81604,81605],{}," \u003Cexecutions>\n",[679,81607,81608],{"class":681,"line":935},[679,81609,81610],{}," \u003Cexecution>\n",[679,81612,81613],{"class":681,"line":944},[679,81614,81615],{}," \u003Cid>Install node and npm\u003C/id>\n",[679,81617,81618],{"class":681,"line":959},[679,81619,81620],{}," \u003Cgoals>\n",[679,81622,81623],{"class":681,"line":964},[679,81624,81625],{}," \u003Cgoal>install-node-and-npm\u003C/goal>\n",[679,81627,81628],{"class":681,"line":977},[679,81629,81630],{}," \u003C/goals>\n",[679,81632,81633],{"class":681,"line":982},[679,81634,81635],{}," \u003Cphase>generate-resources\u003C/phase>\n",[679,81637,81638],{"class":681,"line":988},[679,81639,81640],{}," \u003Cconfiguration>\n",[679,81642,81643],{"class":681,"line":993},[679,81644,81645],{}," \u003CnodeVersion>${node.version}\u003C/nodeVersion>\n",[679,81647,81648],{"class":681,"line":2129},[679,81649,81650],{}," \u003CnpmVersion>${npm.version}\u003C/npmVersion>\n",[679,81652,81653],{"class":681,"line":2140},[679,81654,81655],{}," \u003C/configuration>\n",[679,81657,81658],{"class":681,"line":2145},[679,81659,81660],{}," \u003C/execution>\n",[679,81662,81663],{"class":681,"line":2154},[679,81664,889],{"emptyLinePlaceholder":797},[679,81666,81667],{"class":681,"line":2159},[679,81668,81610],{},[679,81670,81671],{"class":681,"line":2164},[679,81672,81673],{}," \u003Cid>npm install\u003C/id>\n",[679,81675,81676],{"class":681,"line":3134},[679,81677,81620],{},[679,81679,81680],{"class":681,"line":3139},[679,81681,81682],{}," \u003Cgoal>npm\u003C/goal>\n",[679,81684,81685],{"class":681,"line":3144},[679,81686,81630],{},[679,81688,81689],{"class":681,"line":3149},[679,81690,81635],{},[679,81692,81693],{"class":681,"line":3169},[679,81694,81640],{},[679,81696,81697],{"class":681,"line":3185},[679,81698,81699],{}," \u003Carguments>install\u003C/arguments>\n",[679,81701,81702],{"class":681,"line":3194},[679,81703,81655],{},[679,81705,81706],{"class":681,"line":3199},[679,81707,81660],{},[679,81709,81710],{"class":681,"line":3212},[679,81711,889],{"emptyLinePlaceholder":797},[679,81713,81714],{"class":681,"line":3217},[679,81715,81610],{},[679,81717,81718],{"class":681,"line":3222},[679,81719,81720],{}," \u003Cid>npm build\u003C/id>\n",[679,81722,81723],{"class":681,"line":3227},[679,81724,81620],{},[679,81726,81727],{"class":681,"line":3232},[679,81728,81682],{},[679,81730,81731],{"class":681,"line":3499},[679,81732,81630],{},[679,81734,81735],{"class":681,"line":3509},[679,81736,81635],{},[679,81738,81739],{"class":681,"line":3516},[679,81740,81640],{},[679,81742,81743],{"class":681,"line":3531},[679,81744,81745],{}," \u003Carguments>run build\u003C/arguments>\n",[679,81747,81748],{"class":681,"line":3536},[679,81749,81655],{},[679,81751,81752],{"class":681,"line":3541},[679,81753,81660],{},[679,81755,81756],{"class":681,"line":3546},[679,81757,81758],{}," \u003C/executions>\n",[679,81760,81761],{"class":681,"line":3551},[679,81762,81763],{}," \u003Cconfiguration>\n",[679,81765,81766],{"class":681,"line":3557},[679,81767,81768],{}," \u003CnodeVersion>${node.version}\u003C/nodeVersion>\n",[679,81770,81771],{"class":681,"line":3567},[679,81772,81773],{}," \u003CworkingDirectory>src/frontend\u003C/workingDirectory>\n",[679,81775,81776],{"class":681,"line":3574},[679,81777,81778],{}," \u003C/configuration>\n",[679,81780,81781],{"class":681,"line":3589},[679,81782,889],{"emptyLinePlaceholder":797},[679,81784,81785],{"class":681,"line":3594},[679,81786,81787],{},"\u003C/plugin>\n",[651,81789,81790,81791,81796,81797,81800,81801,81509],{},"Next, add the ",[812,81792,81795],{"href":81793,"rel":81794},"https://maven.apache.org/plugins/maven-resources-plugin/",[816],"maven-resources-plugin"," which handles the copying of project resources to the output directory. You will need to copy everything from the ",[676,81798,81799],{},"src/frontend/dist"," directory to the ",[676,81802,81803],{},"target/classes/static",[669,81805,81807],{"className":9101,"code":81806,"language":9103,"meta":674,"style":674}," \u003Cplugin>\n \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n \u003CartifactId>maven-resources-plugin\u003C/artifactId>\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cid>Copy Vue frontend into Spring Boot target static folder\u003C/id>\n \u003Cphase>process-resources\u003C/phase>\n \u003Cgoals>\n \u003Cgoal>copy-resources\u003C/goal>\n \u003C/goals>\n \u003Cconfiguration>\n \u003CoutputDirectory>target/classes/static\u003C/outputDirectory>\n \u003Cresources>\n \u003Cresource>\n \u003Cdirectory>src/frontend/dist\u003C/directory>\n \u003Cfiltering>true\u003C/filtering>\n \u003C/resource>\n \u003C/resources>\n \u003C/configuration>\n \u003C/execution>\n \u003C/executions>\n\u003C/plugin>\n",[676,81808,81809,81814,81819,81824,81828,81832,81837,81842,81846,81851,81855,81859,81864,81869,81874,81879,81884,81889,81894,81898,81902,81906],{"__ignoreMap":674},[679,81810,81811],{"class":681,"line":682},[679,81812,81813],{}," \u003Cplugin>\n",[679,81815,81816],{"class":681,"line":790},[679,81817,81818],{}," \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n",[679,81820,81821],{"class":681,"line":892},[679,81822,81823],{}," \u003CartifactId>maven-resources-plugin\u003C/artifactId>\n",[679,81825,81826],{"class":681,"line":901},[679,81827,81605],{},[679,81829,81830],{"class":681,"line":909},[679,81831,81610],{},[679,81833,81834],{"class":681,"line":918},[679,81835,81836],{}," \u003Cid>Copy Vue frontend into Spring Boot target static folder\u003C/id>\n",[679,81838,81839],{"class":681,"line":935},[679,81840,81841],{}," \u003Cphase>process-resources\u003C/phase>\n",[679,81843,81844],{"class":681,"line":944},[679,81845,81620],{},[679,81847,81848],{"class":681,"line":959},[679,81849,81850],{}," \u003Cgoal>copy-resources\u003C/goal>\n",[679,81852,81853],{"class":681,"line":964},[679,81854,81630],{},[679,81856,81857],{"class":681,"line":977},[679,81858,81640],{},[679,81860,81861],{"class":681,"line":982},[679,81862,81863],{}," \u003CoutputDirectory>target/classes/static\u003C/outputDirectory>\n",[679,81865,81866],{"class":681,"line":988},[679,81867,81868],{}," \u003Cresources>\n",[679,81870,81871],{"class":681,"line":993},[679,81872,81873],{}," \u003Cresource>\n",[679,81875,81876],{"class":681,"line":2129},[679,81877,81878],{}," \u003Cdirectory>src/frontend/dist\u003C/directory>\n",[679,81880,81881],{"class":681,"line":2140},[679,81882,81883],{}," \u003Cfiltering>true\u003C/filtering>\n",[679,81885,81886],{"class":681,"line":2145},[679,81887,81888],{}," \u003C/resource>\n",[679,81890,81891],{"class":681,"line":2154},[679,81892,81893],{}," \u003C/resources>\n",[679,81895,81896],{"class":681,"line":2159},[679,81897,81655],{},[679,81899,81900],{"class":681,"line":2164},[679,81901,81660],{},[679,81903,81904],{"class":681,"line":3134},[679,81905,81758],{},[679,81907,81908],{"class":681,"line":3139},[679,81909,81787],{},[651,81911,81912],{},"And that is all of the configuration you need. From the command-line you can now package the application using Maven:",[669,81914,81916],{"className":5851,"code":81915,"language":5853,"meta":674,"style":674},"mvn clean package\n",[676,81917,81918],{"__ignoreMap":674},[679,81919,81920,81923,81926],{"class":681,"line":682},[679,81921,81922],{"class":880},"mvn",[679,81924,81925],{"class":689}," clean",[679,81927,32496],{"class":689},[651,81929,81930,81931,2525],{},"When that is complete you should see something that looks like this and it will end up producing a new JAR ",[676,81932,81933],{},"fsjava-0.0.1-SNAPSHOT.jar",[669,81935,81937],{"className":5851,"code":81936,"language":5853,"meta":674,"style":674},"[INFO]\n[INFO] Results:\n[INFO]\n[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0\n[INFO]\n[INFO]\n[INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ fsjava ---\n[INFO] Building jar: /Users/vega/dev/boot/full-stack-java-vue/target/fsjava-0.0.1-SNAPSHOT.jar\n[INFO]\n[INFO] --- spring-boot-maven-plugin:2.4.2:repackage (repackage) @ fsjava ---\n[INFO] Replacing main artifact with repackaged archive\n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time: 20.083 s\n[INFO] Finished at: 2021-01-22T13:24:38-05:00\n[INFO] ------------------------------------------------------------------------\n",[676,81938,81939,81944,81949,81953,81958,81962,81966,81977,81982,81986,81996,82001,82006,82011,82015,82020,82025],{"__ignoreMap":674},[679,81940,81941],{"class":681,"line":682},[679,81942,81943],{"class":693},"[INFO]\n",[679,81945,81946],{"class":681,"line":790},[679,81947,81948],{"class":693},"[INFO] Results:\n",[679,81950,81951],{"class":681,"line":892},[679,81952,81943],{"class":693},[679,81954,81955],{"class":681,"line":901},[679,81956,81957],{"class":693},"[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0\n",[679,81959,81960],{"class":681,"line":909},[679,81961,81943],{"class":693},[679,81963,81964],{"class":681,"line":918},[679,81965,81943],{"class":693},[679,81967,81968,81971,81974],{"class":681,"line":935},[679,81969,81970],{"class":693},"[INFO] --- maven-jar-plugin:3.2.0:jar (",[679,81972,81973],{"class":880},"default-jar",[679,81975,81976],{"class":693},") @ fsjava ---\n",[679,81978,81979],{"class":681,"line":944},[679,81980,81981],{"class":693},"[INFO] Building jar: /Users/vega/dev/boot/full-stack-java-vue/target/fsjava-0.0.1-SNAPSHOT.jar\n",[679,81983,81984],{"class":681,"line":959},[679,81985,81943],{"class":693},[679,81987,81988,81991,81994],{"class":681,"line":964},[679,81989,81990],{"class":693},"[INFO] --- spring-boot-maven-plugin:2.4.2:repackage (",[679,81992,81993],{"class":880},"repackage",[679,81995,81976],{"class":693},[679,81997,81998],{"class":681,"line":977},[679,81999,82000],{"class":693},"[INFO] Replacing main artifact with repackaged archive\n",[679,82002,82003],{"class":681,"line":982},[679,82004,82005],{"class":693},"[INFO] ------------------------------------------------------------------------\n",[679,82007,82008],{"class":681,"line":988},[679,82009,82010],{"class":693},"[INFO] BUILD SUCCESS\n",[679,82012,82013],{"class":681,"line":993},[679,82014,82005],{"class":693},[679,82016,82017],{"class":681,"line":2129},[679,82018,82019],{"class":693},"[INFO] Total time: 20.083 s\n",[679,82021,82022],{"class":681,"line":2140},[679,82023,82024],{"class":693},"[INFO] Finished at: 2021-01-22T13:24:38-05:00\n",[679,82026,82027],{"class":681,"line":2145},[679,82028,82005],{"class":693},[651,82030,82031,82032],{},"You can run this jar from the command line ",[676,82033,82034],{},"java -jar target/fsjava-0.0.1-SNAPSHOT.jar",[651,82036,82037],{},[660,82038],{"alt":82039,"src":82040},"Java JAR","/images/blog/2021/01/22/java_jar.png",[651,82042,82043],{},"With this single artifact you can push this into a production environment and you are ready to go. A few of my favorite options for doing this are:",[5316,82045,82046,82053,82060],{},[5332,82047,82048],{},[812,82049,82052],{"href":82050,"rel":82051},"https://www.heroku.com/",[816],"Heroku",[5332,82054,82055],{},[812,82056,82059],{"href":82057,"rel":82058},"https://aws.amazon.com/elasticbeanstalk/",[816],"AWS Beanstalk",[5332,82061,82062],{},[812,82063,82065],{"href":79516,"rel":82064},[816],"Azure Spring",[4542,82067,9042],{"id":9041},[651,82069,82070],{},"I wanted to keep the application simple in this article so we could focus on the plumbing of the project. That said if you're interested in seeing more please let me know. Spring Boot and Vue are two technologies I really enjoy using and I hope this helps others put the two together. As always friends...",[651,82072,41105,82073,41109],{},[41107,82074],{},[786,82076,82077],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":82079},[82080,82081,82082,82083,82091],{"id":80647,"depth":790,"text":80648},{"id":80676,"depth":790,"text":80653},{"id":37458,"depth":790,"text":80656},{"id":80713,"depth":790,"text":80714,"children":82084},[82085,82086,82087,82088,82089,82090],{"id":80725,"depth":892,"text":80664},{"id":20742,"depth":892,"text":7077},{"id":44169,"depth":892,"text":43866},{"id":81047,"depth":892,"text":81048},{"id":81296,"depth":892,"text":81297},{"id":81502,"depth":892,"text":80671},{"id":9041,"depth":790,"text":9042},"In this tutorial, you are going to learn how to build a full-stack application that uses Vue for the frontend and Spring Boot for the backend.",{"slug":82094,"date":82095,"published":797,"author":798,"tags":82096,"cover":82097,"video":82098},"full-stack-java","2021-01-22T08:00:00.000Z",[4109,44169],"./full-stack-java-spring-vue.png","https://www.youtube.com/embed/2G6r2f40Lps",{"title":411,"description":82092},"blog/2021/01/22/full-stack-java","Ky7h8v0VWO8FBgGVFZw6f_M-LmpdWKouWXgKrSSDL-c",{"id":82103,"title":408,"body":82104,"description":82189,"extension":793,"meta":82190,"navigation":797,"path":409,"seo":82195,"stem":82196,"__hash__":82197},"content/blog/2021/08/30/spring-one-2021.md",{"type":648,"value":82105,"toc":82182},[82106,82115,82119,82122,82127,82132,82137,82141,82144,82155,82158,82164,82166,82169,82175,82179],[651,82107,82108,82109,82114],{},"This year ",[812,82110,82113],{"href":82111,"rel":82112},"https://springone.io/",[816],"SpringOne"," takes place September 1-2, 2021, and is free to virtually attend. As much as I want to get back to speaking in person I am just so thankful to have been selected to speak at this conference. I am looking forward to sharing the virtual stage with so many of the best speakers in our community.",[4542,82116,82118],{"id":82117},"full-stack-java-development-with-spring-boot-and-vue","Full Stack Java Development with Spring Boot and Vue",[651,82120,82121],{},"I am going to be presenting on two of my favorite frameworks, Spring Boot and VueJS. I have a bit of a confession to make, I am a lazy programmer and both of these frameworks are enablers. They both make all of the hard stuff easy and let me focus on the business requirements of the project. Here is the presentation abstract to give you a bit more context on the presentation.",[1004,82123,82124],{},[651,82125,82126],{},"Are you interested in building full-stack web applications with Spring Boot and Vue 3? If so, this session is going to give you everything you need to get started. You will learn some of the different approaches to building full-stack applications and the pros and cons to each.",[1004,82128,82129],{},[651,82130,82131],{},"First, we will look at how Vue is a progressive framework and will allow you to incrementally adopt it in your Java applications as your needs grow. Next, we will look at how to build a monolith application where you end up with a single deployable JAR. Finally, if your frontend and backend teams are separate, we will look at a solution for you.",[1004,82133,82134],{},[651,82135,82136],{},"When you leave this session, you’ll have everything you need to start building full-stack web applications with Spring Boot and VueJS.",[5909,82138,82140],{"id":82139},"what-will-you-learn","What will you learn?",[651,82142,82143],{},"As I said in the abstract I want to focus on three different approaches to using Spring Boot and VueJS together. What approach you choose will depend on the project's requirements and the makeup of your team. You will learn what to use each of these as well as some pros/cons to each.",[27665,82145,82146,82149,82152],{},[5332,82147,82148],{},"Progressive Vue - Incrementally adopting Vue to enhance an existing page",[5332,82150,82151],{},"Monolith - The Spring Boot project contains the Vue frontend and you will package both of them into a single deployable JAR.",[5332,82153,82154],{},"Single Page Application (SPA) - The frontend and backend applications are built and deployed independently.",[651,82156,82157],{},"To illustrate these approaches we will be building out functionality for an Ice Cream store. If you don't learn anything you either weren't paying attention or completely consumed with the idea of getting ice cream.",[651,82159,82160],{},[660,82161],{"alt":82162,"src":82163},"Ice Cream Store","/images/blog/2021/08/30/ice-cream-store.png",[5909,82165,21931],{"id":21930},[651,82167,82168],{},"If you would like to get access to the source code for any of the demos I walked through or the slide deck for the presentation please visit the following Github Repository.",[651,82170,82171],{},[812,82172,82173],{"href":82173,"rel":82174},"https://github.com/danvega/spring-one-2021",[816],[5909,82176,82178],{"id":82177},"feedback","Feedback",[651,82180,82181],{},"I love putting presentations together but without your feedback, they can sometimes leave me feeling incomplete. If you have any feedback (positive or negative) please reach out to me and let me know. I hope to give this talk again so I would love to know what I can do to improve it for next time.",{"title":674,"searchDepth":790,"depth":790,"links":82183},[82184],{"id":82117,"depth":790,"text":82118,"children":82185},[82186,82187,82188],{"id":82139,"depth":892,"text":82140},{"id":21930,"depth":892,"text":21931},{"id":82177,"depth":892,"text":82178},"I'm so honored to be speaking at this year's SpringOne Conference. This article will give you a look ahead to the presentation by explaining what you can expect and the resources from my talk.",{"slug":82191,"date":82192,"published":797,"author":798,"tags":82193,"cover":82194},"spring-one-2021","2021-08-30T19:45:43.204Z",[41511,7055,44169],"./spring-one-2021-cover.png",{"title":408,"description":82189},"blog/2021/08/30/spring-one-2021","6F3L8QaUs2BWZPbwPRCwL4mG54cvuzZemdpqfjPpq-U",{"id":82199,"title":405,"body":82200,"description":82246,"extension":793,"meta":82247,"navigation":797,"path":406,"seo":82254,"stem":82255,"__hash__":82256},"content/blog/2021/11/06/github-copilot-java-developers.md",{"type":648,"value":82201,"toc":82242},[82202,82205,82208,82221,82224,82230,82232,82235,82239],[651,82203,82204],{},"Earlier this year GitHub launched Copilot, an AI pair-programmer. With GitHub Copilot, get suggestions for whole lines or entire functions right inside your editor. I'm not sure if it supported Java when it was first launched but at the time of writing this, the technical preview supports Python, JavaScript, TypeScript, Ruby, Java & Go.",[651,82206,82207],{},"When it was released back in June of 2021 they only had support for Visual Studio Code. At Github Universe 2021 they announced support for NeoVim and Jetbrains. This means that if you're running any flavor of the JetBrains IDE like IntelliJ, GoLand, or Webstorm you can use Github Copilot.",[651,82209,82210,82211,82216,82217,82220],{},"In this tutorial, I am focused on Github Copilot for Java Developers. I am using IntelliJ Ultimate Edition but this should work fine in the community edition. The first thing you need to do is to visit ",[812,82212,82215],{"href":82213,"rel":82214},"https://copilot.github.com/",[816],"https://copilot.github.com"," and sign up for the technical preview. Next, open up IntelliJ and go to ",[676,82218,82219],{},"Preferences > Plugins",", search for Github Copilot and install the plugin.",[651,82222,82223],{},"At this point, you're ready to use GitHub Copilot. In the remainder of the video tutorial, I walk through different ways GitHub Copilot was able to make suggestions for lines of code and even entire methods. If you want to grab the final code you can head over to the repository below.",[651,82225,82226],{},[812,82227,82228],{"href":82228,"rel":82229},"https://github.com/danvega/youtube/tree/main/java/github-copilot",[816],[4542,82231,9042],{"id":9041},[651,82233,82234],{},"As I said throughout the video I am not sure how this is going to work into my day-to-day workflow. Since the video was made I have been using it a little bit more and I can say that it's not replacing me, it's actually assisting me in writing code faster.",[4542,82236,82238],{"id":82237},"update","Update",[651,82240,82241],{},"I have been using Github Copilot with IntelliJ Ultimate Edition for a while now and I'm very happy with the results. It really does a great job of predicting what you need to write next and I use it for Java, Spring, JavaScript and even Markdown. I find myself turning it off for presentations now because it's the only thing viewers want to talk about and it takes away from the demo.",{"title":674,"searchDepth":790,"depth":790,"links":82243},[82244,82245],{"id":9041,"depth":790,"text":9042},{"id":82237,"depth":790,"text":82238},"Earlier this year GitHub launched Copilot, an AI pair-programmer. With GitHub Copilot, get suggestions for whole lines or entire functions right inside your editor.",{"slug":82248,"date":82249,"updatedOn":82250,"published":797,"author":798,"tags":82251,"cover":82252,"video":82253},"github-copilot-java-developers","2021-11-08T10:00:00.000Z","2022-05-11T10:30:00.000Z",[4109,7055],"./github_copilot_thumbnail.png","https://www.youtube.com/embed/97C3fQqzj-I",{"title":405,"description":82246},"blog/2021/11/06/github-copilot-java-developers","a_9Gv3WoGYDjgHBXbke-OimRq7j42bQA1xKNXqC8IJQ",{"id":82258,"title":402,"body":82259,"description":82543,"extension":793,"meta":82544,"navigation":797,"path":403,"seo":82551,"stem":82552,"__hash__":82553},"content/blog/2021/11/15/macbook-pro-m1-max-review.md",{"type":648,"value":82260,"toc":82531},[82261,82264,82273,82278,82281,82285,82288,82314,82317,82402,82405,82408,82415,82420,82423,82428,82432,82435,82438,82442,82445,82451,82454,82457,82463,82473,82477,82480,82485,82488,82491,82494,82498,82501,82508,82512,82517,82520,82523,82526,82528],[651,82262,82263],{},"I have been using the new MacBook Pro for about a week and wanted to give you my initial thoughts from a developer & content creator perspective. If you're looking for an in-depth review full of Geekbench benchmarks you aren't going to find them here. Instead, I will share with you what I like or don't like about it some day-to-day workflows I am using it for.",[66068,82265,82268,82269,82272],{"className":82266},[82267],"tldr","\n📖 ",[2939,82270,82271],{},"TLDR;"," If you are not the patient type and want me to get right to the point, here it is. This is by far the best laptop I have ever used and it has me excited for the future of Apple Silicon.\n",[651,82274,82275],{},[660,82276],{"alt":82277,"src":82277},"https://images.unsplash.com/photo-1635310568932-47fd9c961c26?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb",[651,82279,82280],{},"If you are new around here I have been a software developer for over 20 years now. I have used everything from custom-built desktops to the laptop I am talking about today. I have used Linux, Windows, and macOS and for whatever reason, I have just really enjoyed my time on the mac. I also create content in the form of articles like the one you're reading now, videos for my YouTube channel, and courses that are consumed by students worldwide.",[4542,82282,82284],{"id":82283},"_14-macbook-pro-m1-max","14\" MacBook Pro M1 Max",[651,82286,82287],{},"This is the custom configuration I ended up ordering:",[5316,82289,82290,82293,82296,82299,82302,82305,82308,82311],{},[5332,82291,82292],{},"Apple M1 Max with 10-core CPU, 24-core GPU, 16-core Neural Engine",[5332,82294,82295],{},"32GB unified memory",[5332,82297,82298],{},"1TB SSD storage",[5332,82300,82301],{},"96w USB-C Power Adapter",[5332,82303,82304],{},"14-inch Liquid Retina XDR display",[5332,82306,82307],{},"Three Thunderbolt 4 ports, HDMI port, SDXC card slot, MagSafe 3 port",[5332,82309,82310],{},"Backlit Magic Keyboard with Touch ID - US English",[5332,82312,82313],{},"Accessory Kit",[651,82315,82316],{},"When Apple announced that both the 14\" & 16\" would be able to be configured with pretty much the same specs It was an easy decision for me to choose the 14\" model. My laptop is usually on my desk where it is hooked up to 2 external monitors so the screen size wasn't important. If you're curious about the monitors or other peripherals found on my desk here are a few:",[66068,82318,82321,82325,82397],{"className":82319},[82320],"promo",[5909,82322,82324],{"id":82323},"whats-on-my-desk","What's on my desk:",[5316,82326,82327,82334,82341,82348,82355,82362,82369,82376,82383,82390],{},[5332,82328,82329],{},[812,82330,82333],{"href":82331,"rel":82332},"https://amzn.to/3C8IqQy",[816],"LG 4k Display",[5332,82335,82336],{},[812,82337,82340],{"href":82338,"rel":82339},"https://amzn.to/3HcTobs",[816],"Dell UltraSharp U3415W 34-Inch Curved LED-Lit Monitor",[5332,82342,82343],{},[812,82344,82347],{"href":82345,"rel":82346},"https://amzn.to/3quWxh0",[816],"Apple Magic Keyboard",[5332,82349,82350],{},[812,82351,82354],{"href":82352,"rel":82353},"https://amzn.to/3F2gLCy",[816],"Logitech MX Master 3 Mouse",[5332,82356,82357],{},[812,82358,82361],{"href":82359,"rel":82360},"https://amzn.to/3HdoZto",[816],"Audio-Technica AT2020 USB Microphone",[5332,82363,82364],{},[812,82365,82368],{"href":82366,"rel":82367},"https://amzn.to/3Dk78P2",[816],"Sony A6100 DSLR Camera",[5332,82370,82371],{},[812,82372,82375],{"href":82373,"rel":82374},"https://amzn.to/3c4fpea",[816],"Elgato Cam Link 4k",[5332,82377,82378],{},[812,82379,82382],{"href":82380,"rel":82381},"https://amzn.to/3HdpnIm",[816],"Elgato Key Light * 2",[5332,82384,82385],{},[812,82386,82389],{"href":82387,"rel":82388},"https://amzn.to/3qvw3vO",[816],"Apple AirPods Pro",[5332,82391,82392],{},[812,82393,82396],{"href":82394,"rel":82395},"https://amzn.to/3F5Utjp",[816],"Wyze Wireless Noise Cancelling Headphones",[651,82398,82399],{},[52417,82400,82401],{},"* Each of these are affiliate links at no extra cost to you but they do help support this website. Thank You 🙏",[651,82403,82404],{},"When I am not sitting at my desk I am using my standing desk or I am on the move and I appreciate the portability. When I am on the go whether it's doing some work at a coffee shop or traveling I like the smaller form factor.",[651,82406,82407],{},"Those were my initial thoughts when ordering the laptop and I'm super happy I went with the 14\". It's a really great size and it's got a little weight to it which, in a pro machine I appreciate. The XDR display is nothing short of amazing. Speaking of the display I heard a lot of people complaining about the notch and I can honestly say that after a day or two I haven't noticed it at all. When you move an app into full-screen it's totally gone and when you're not in full-screen it's not a distraction.",[651,82409,82410,82411,82414],{},"The keyboard is new and so far it's been a great experience. Usually, I prefer using my external keyboard (Apple Magic Keyboard) but I actually prefer typing on this one. The keys are smooth and have made for a really good typing experience. I was never a big fan of the touch bar so I was excited to see the physical function keys return. There is ",[676,82412,82413],{},"Fn"," key on the lower left-hand side of the keyboard that also serves as an emoji picker which I have found to be really handy.",[651,82416,82417],{},[660,82418],{"alt":82419,"src":82419},"https://images.unsplash.com/photo-1635310478752-cad8a1f77d9c?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb",[651,82421,82422],{},"I was also really happy to see the return of the ports. This laptop comes with 3 Thunderbolt 4 ports, an HDMI port, an SDXC card slot, and a MagSafe 3 port. When I'm presenting it's nice to have quick access to an HDMI port without having to use one of the many dongles I have to carry in my bag. When I am shooting a video it's nice to have quick access to an SD card reader. I think one of the knocks I heard most on the M1 laptops from a year ago was that they only had 2 Thunderbolt ports, having that extra one makes a big difference. The theme here is doing as much as we can without having to carry all of those connectors.",[651,82424,82425],{},[660,82426],{"alt":82427,"src":82427},"https://images.unsplash.com/photo-1635310567899-949f415d0e90?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb",[4542,82429,82431],{"id":82430},"macbook-pro-m1-max-performance","MacBook Pro M1 Max Performance",[651,82433,82434],{},"Everything I have mentioned before is great but the real star of this laptop is Apple Silicon and the performance. I heard this was similar on the previous M1 laptops but the first thing you notice is how fast the laptop starts up when you open it up, almost instantly.",[651,82436,82437],{},"From day-to-day use, everything just feels smooth, fast, and snappy. The other thing I have noticed is that the fans either are not kicking on or I am just not hearing them. This isn't the case with my 2015 & 2019 MacBooks which during the wintertime double as a space heater.",[5909,82439,82441],{"id":82440},"developer-productivity","Developer Productivity",[651,82443,82444],{},"I think the biggest thing I have noticed from a software developer standpoint is the speed at which everything moves. I find myself waiting around less for things to happen. It's particularly more responsive when I fire up my IDE IntelliJ and the project has to get indexed or when I have to build something. I tweeted this out recently but I pulled down the Spring Pet Clinic project and I was able to run it in 1.95 seconds.",[651,82446,82447],{},[812,82448,82449],{"href":82449,"rel":82450},"https://twitter.com/therealdanvega/status/1458979393557123089",[816],[651,82452,82453],{},"The other thing I have found is that I am less worried about running resource-intensive applications like Chrome while I am coding. In the past, I would try and limit the number of tabs or extensions running but it's not something I am paying attention to now.",[651,82455,82456],{},"I think the build times are going to be the biggest time savers when it comes to developer productivity. I came across this tweet and thought it was right on the money. Laptops are cheaper than developers!",[651,82458,82459],{},[812,82460,82461],{"href":82461,"rel":82462},"https://twitter.com/softwarejameson/status/1455971162060697613",[816],[66068,82464,82467,82468],{"className":82465},[82320,82466],"tip-jar","\n Support this website ",[812,82469,82472],{"href":82470,"target":82471},"https://danvega.ck.page/products/content-creator-tip-jar","_blank","Buy Me Coffee",[5909,82474,82476],{"id":82475},"content-creator-productivity","Content Creator Productivity",[651,82478,82479],{},"When it comes to creating content this machine is a dream come true. The biggest improvement for me is when I am both recording and editing videos. In my previous setups whenever I would record my screen or try and live stream the fans would go into overdrive and the machine would get sluggish making it hard to multitask.",[651,82481,82482],{},[660,82483],{"alt":82484,"src":82484},"https://images.unsplash.com/photo-1607968565043-36af90dde238?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb",[651,82486,82487],{},"With this machine, I am able to run my recording software (Ecamm Live) and the fans are not in my way and everything still feels snappy.",[651,82489,82490],{},"Editing videos is where I feel like I am seeing the biggest performance gains and getting back time in my day. In previous setups when I would drag footage onto my timeline It would take up to 10 seconds to render and scrubbing through footage would be choppy. Now, everything is just instant and snappy.",[651,82492,82493],{},"Finally, when it comes to exporting I have found videos that were taking me 15 min to export now take me 4-5 minutes. During the course of a day or a week, these things really start to add up!",[4542,82495,82497],{"id":82496},"developer-setup","Developer Setup",[651,82499,82500],{},"I originally wanted to create videos around each step of my setup process and quickly realized that would take way too long. I wanted to get it set up and start playing with it as soon as I could. What I have been doing over the past few years is documenting my step-by-step setup. I like doing this because it might help others and you might come across a new tool or application that can help you out. You can find the link below and as always, feedback is welcome!",[651,82502,82503],{},[812,82504,82507],{"href":82505,"rel":82506},"https://github.com/danvega/new-macbook-setup/tree/master/2021",[816],"new-macbook-setup/2021 at master · danvega/new-macbook-setup",[4542,82509,82511],{"id":82510},"macbook-2021-price","MacBook 2021 Price",[651,82513,82514],{},[660,82515],{"alt":82516,"src":82516},"https://images.unsplash.com/photo-1459257831348-f0cdd359235f?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb",[651,82518,82519],{},"If there is a con it's the price of these new machines, they aren't cheap. I feel pretty fortunate to even be in a position to buy one because for most of my tech life I have never been able to buy the latest tech. The only reason I am in this position is that I have a business where and it's almost the end of the year which means I am looking for tax write-offs.",[651,82521,82522],{},"I haven't actually used the MacBook Air or the MacBook M1 from 2020 but from everything I have seen and heard they are amazing machines. In fact, if you aren't creating content I might recommend going that route if the price is an issue.",[651,82524,82525],{},"That said if you are live streaming, editing video, and producing content I think it's worth every penny. I also think there is a way that you can talk your boss into buying you one if you're building larger software projects.",[4542,82527,9042],{"id":9041},[651,82529,82530],{},"I'm usually a pretty patient person but I don't like waiting for my computer to perform builds or exports videos. While we haven't reached the ultimate goal of instant gratification It is incredible what this machine can do. I think the biggest thing for me is that this is only the 2nd iteration of this chip. I am excited about the future of Apple Silicon and can't wait to see what they do next!",{"title":674,"searchDepth":790,"depth":790,"links":82532},[82533,82536,82540,82541,82542],{"id":82283,"depth":790,"text":82284,"children":82534},[82535],{"id":82323,"depth":892,"text":82324},{"id":82430,"depth":790,"text":82431,"children":82537},[82538,82539],{"id":82440,"depth":892,"text":82441},{"id":82475,"depth":892,"text":82476},{"id":82496,"depth":790,"text":82497},{"id":82510,"depth":790,"text":82511},{"id":9041,"depth":790,"text":9042},"I have been using the new MacBook Pro for about a week and wanted to give you my initial thoughts from a developer & content creator perspective.",{"slug":82545,"date":82546,"published":797,"author":798,"tags":82547,"cover":82549,"video":82550},"macbook-pro-m1-max-review","2021-11-15T21:00:00.000Z",[82548],"Apple","./macbook-pro-review-thumbnail.png","https://www.youtube.com/embed/t-hEOyUnaXQ",{"title":402,"description":82543},"blog/2021/11/15/macbook-pro-m1-max-review","QpZ_m7yZVXGTAbV5FFngSLhrggHgJrkjLpPXtJb0o1Y",{"id":82555,"title":399,"body":82556,"description":82560,"extension":793,"meta":82829,"navigation":797,"path":400,"seo":82834,"stem":82835,"__hash__":82836},"content/blog/2022/01/01/happy-new-year-2022.md",{"type":648,"value":82557,"toc":82812},[82558,82561,82565,82569,82577,82580,82593,82599,82603,82606,82610,82626,82630,82667,82671,82674,82678,82683,82696,82699,82715,82718,82721,82727,82730,82744,82751,82754,82757,82760,82762,82771,82773,82781,82784,82790,82794,82799,82807,82809],[651,82559,82560],{},"Happy New Year! I want to take a few minutes and reflect on the year that was 2021 and look ahead to 2022.",[4542,82562,82564],{"id":82563},"_2021","2021",[5909,82566,82568],{"id":82567},"briebug","Briebug",[651,82570,82571,82572,82576],{},"I started a new job this year at ",[812,82573,82575],{"href":79386,"rel":82574},[816],"Briebug Software"," as an Enterprise Architect. We are a consultancy and I spent this year working for a single client who happens to be a Fortune 50 company. The size of the company and the volume at which the applications run presented all sorts of new challenges for me. I got to work on a bunch of really cool projects running Spring Boot & Spring Cloud. Towards the end of the year, I was promoted to Principal Architect and can’t say enough good things about Briebug!",[5909,82578,82113],{"id":82579},"springone",[651,82581,82582,82583,82587,82588,664],{},"This year I was lucky to be selected as one of the speakers at SpringOne. I have attended a few Spring Conferences before and it is easily one of my favorites so this was a big deal and an honor for me. ",[812,82584,41617],{"href":82585,"rel":82586},"https://www.danvega.dev/blog/2021/08/30/spring-one-2021/",[816]," was on Full Stack Java Development with Spring Boot and Vue. I thought I did a really good job of preparing for this talk but nothing really prepares you for Zoom dropping off in the middle of your presentation. Thankfully I was alerted that it happened and I was able to log back in and finish the talk. I got some really good feedback on my talk and appreciate anyone who joined us live or watched ",[812,82589,82592],{"href":82590,"rel":82591},"https://springone.io/2021/sessions/full-stack-development-with-spring-boot-vuejs",[816],"the replay",[651,82594,82595],{},[660,82596],{"alt":82597,"src":82598},"Spring One","/images/blog/2022/01/01/spring-one.png",[5909,82600,82602],{"id":82601},"creating-content","Creating Content",[651,82604,82605],{},"I love creating content and thankfully I was able to get back to it this year. Here are a few of my favorite pieces of content that I worked on this year.",[651,82607,82608],{},[2939,82609,37521],{},[5316,82611,82612,82619],{},[5332,82613,82614],{},[812,82615,82618],{"href":82616,"rel":82617},"https://www.danvega.dev/blog/2021/01/22/full-stack-java-vue/",[816],"Full Stack Java Development with Spring Boot and VueJS",[5332,82620,82621],{},[812,82622,82625],{"href":82623,"rel":82624},"https://www.danvega.dev/blog/2021/11/15/macbook-pro-m1-max-review/",[816],"14” MacBook Pro M1 Max Review",[651,82627,82628],{},[2939,82629,15432],{},[5316,82631,82632,82639,82646,82653,82660],{},[5332,82633,82634],{},[812,82635,82638],{"href":82636,"rel":82637},"https://youtu.be/e8aSyQo0nHo",[816],"Create your first Spring application (Without Spring Boot)",[5332,82640,82641],{},[812,82642,82645],{"href":82643,"rel":82644},"https://youtu.be/97C3fQqzj-I",[816],"Github Copilot for Java Developers",[5332,82647,82648],{},[812,82649,82652],{"href":82650,"rel":82651},"https://youtu.be/nGqVYiwu8uo",[816],"Log4J Vulnerability for Spring Boot Applications",[5332,82654,82655],{},[812,82656,82659],{"href":82657,"rel":82658},"https://youtu.be/tdOoKKXlDCQ",[816],"Getting Started with Nuxt 3",[5332,82661,82662],{},[812,82663,82666],{"href":82664,"rel":82665},"https://youtu.be/ZddzOO_ovz8",[816],"Java is now FREE for everyone (YouTube Short)",[4542,82668,82670],{"id":82669},"_2022","2022",[651,82672,82673],{},"Now that I have 2 small kids at home I’m hesitant to put specific goals out in the world because I honestly don’t know If I can complete them. With that said here are some aspirations.",[5909,82675,82677],{"id":82676},"move-my-website-over-to-nuxt-3","Move my website over to Nuxt 3",[651,82679,82680],{},[660,82681],{"alt":53863,"src":82682},"/images/blog/2022/01/01/danvega-dev-homepage.png",[651,82684,82685,82686,82691,82692,82695],{},"I’m really happy with my ",[812,82687,82690],{"href":82688,"rel":82689},"https://www.danvega.dev/",[816],"current website",". Every time I post a new article I head over to the site to make sure it looks ok and I remember just how proud I am of it. The current version of my website is written in a framework called ",[812,82693,43848],{"href":43854,"rel":82694},[816],", which is a static site generator for VueJS. I have been really happy with Gridsome but there are a few limitations that are stopping me from doing some of the things I want to do. That and the project hasn’t been moving forward that much over the last couple of years.",[651,82697,82698],{},"Enter Nuxt 3 which just went into beta and brings a whole bunch of exciting features. There are too many to mention here but these are my favorites so far",[5316,82700,82701,82704,82706,82709,82712],{},[5332,82702,82703],{},"Vue 3 Support",[5332,82705,74145],{},[5332,82707,82708],{},"Vite - My favorite build tool",[5332,82710,82711],{},"Nitro - Nuxt 3 Server",[5332,82713,82714],{},"FAST!",[651,82716,82717],{},"Nuxt 3 is still in beta and while I love what they are doing there are still some things missing that are going to prevent me from moving over right now. The biggest one of all is static site generation which is coming soon 🤞🏻 Lucky for me I am in no rush to move over which gives me a great opportunity to spend some time learning Nuxt 3.",[5909,82719,82720],{"id":47833},"Content",[651,82722,82723],{},[660,82724],{"alt":82725,"src":82726},"content creator","/images/blog/2022/01/01/onur-binay-O2-EZNGZIyk-unsplash.jpg",[651,82728,82729],{},"I was happy to be back at turning out content last year but after looking at my total numbers I know I can do better. I’m setting some lofty goals in this department but I think they are doable.",[5316,82731,82732,82735,82738,82741],{},[5332,82733,82734],{},"Blog: 1 post per month for a total of 12 posts",[5332,82736,82737],{},"YouTube: 1 video per week for a total of 52 videos",[5332,82739,82740],{},"Live Streaming: 2x a month for a total of 24 live streams",[5332,82742,82743],{},"Courses: 2 New Courses",[651,82745,82746,82747,82750],{},"As you can see YouTube is where I want to focus this year. I’m really proud that I have been able to grow my channel to almost 14k subscribers and I have done so without being consistent. I looked back at my stats and I only published ",[2939,82748,82749],{},"11 videos"," in 2021. That is less than 1 per month and I know I can do better than that.",[651,82752,82753],{},"You might be thinking that moving from 11 to 52 videos in a year is a lofty goal and I would completely agree. I think the one thing I have on my side is that I can create YouTube shorts much quicker than normal tutorials. Are you a fan of consuming shorts on YouTube? If so can you let me know what types of videos you want to see or some of your favorite creators that are creating shorts?",[5909,82755,39352],{"id":82756},"read-more-books",[651,82758,82759],{},"Whenever a read a new book I always end up asking myself, why don’t I do this more often. I read a few books towards the end of the year and it reminded me what I was missing. I am going to set a goal of reading 1 book per month for a total of 12 books in 2022.",[5909,82761,79609],{"id":79608},[651,82763,82764,82765,82770],{},"I’m really hoping that we return to somewhat of a normal conference season where we are all hanging out in person. I would love to speak at 4+ conferences this year and to do that I need to submit to a bunch of conferences. I need to get better and finding the conferences I want to speak at writing good abstracts. I keep all of my call for papers in a ",[812,82766,82769],{"href":82767,"rel":82768},"https://github.com/danvega/call-for-papers",[816],"Github repo"," if you’re interested in reading them. If you know of any good places to find lists of upcoming conferences I would love to hear about them.",[5909,82772,67442],{"id":67441},[651,82774,82775,82776,664],{},"I’m really happy that I got back to producing my newsletter on a weekly basis. I would like to continue that in 2022. I’m constantly looking at making small tweaks to my format and that is something I will continue to do. If you’re a subscriber and have a suggestions please send them my way. If you’re not a subscriber, ",[812,82777,82780],{"href":82778,"rel":82779},"https://www.danvega.dev/newsletter/",[816],"what are you waiting for",[5909,82782,79577],{"id":82783},"java-champions",[651,82785,82786,82789],{},[812,82787,79577],{"href":79575,"rel":82788},[816]," are leaders in the Java community who are experts at what they do. I happen to know a few Java Champions and I look up to them and aspire to be like them. I feel like I do a lot for the Java community but I could be doing so much more. I am going to figure out more ways to contribute to the community who has given me so much so that one day I might join this list.",[5909,82791,82793],{"id":82792},"charity","Charity",[651,82795,82796],{},[660,82797],{"alt":39497,"src":82798},"/images/blog/2022/01/01/toysforshots.png",[651,82800,82801,82802,82806],{},"Due to the pandemic the non profit organization I run has been unable to throw our big event, ",[812,82803,39497],{"href":82804,"rel":82805},"https://toysforshots.com/",[816],". This is something I hope we can back to this year. With that I would like to move our website over to Nuxt 3. I also want to build in some stripe integration so that we can take donations online which is one feature we are lacking. CLE Cares, is the name of our non-profit and we need to get a site setup for that. A lot of work to do here but it’s something I enjoy working on so I look forward to it.",[4542,82808,9042],{"id":9041},[651,82810,82811],{},"I know it sounds silly but I look forward to the new year after the long holiday season. I’m excited for what this year will bring. I’m looking forward to doing more of what I love to do, learning and creating content. Thank you to all of you who continue to support me and I hope you have a happy and healthy 2022.",{"title":674,"searchDepth":790,"depth":790,"links":82813},[82814,82819,82828],{"id":82563,"depth":790,"text":82564,"children":82815},[82816,82817,82818],{"id":82567,"depth":892,"text":82568},{"id":82579,"depth":892,"text":82113},{"id":82601,"depth":892,"text":82602},{"id":82669,"depth":790,"text":82670,"children":82820},[82821,82822,82823,82824,82825,82826,82827],{"id":82676,"depth":892,"text":82677},{"id":47833,"depth":892,"text":82720},{"id":82756,"depth":892,"text":39352},{"id":79608,"depth":892,"text":79609},{"id":67441,"depth":892,"text":67442},{"id":82783,"depth":892,"text":79577},{"id":82792,"depth":892,"text":82793},{"id":9041,"depth":790,"text":9042},{"slug":82830,"date":82831,"published":797,"author":798,"tags":82832,"cover":82833},"happy-new-year-2022","2022-01-01T11:00:00.000Z",[39139],"./kelly-sikkema-PXl_S152jNM-unsplash.jpg",{"title":399,"description":82560},"blog/2022/01/01/happy-new-year-2022","CAlfARZy-_VkW1LqUBG1zR0VkKun36y4EG4XQEMKyw8",{"id":82838,"title":396,"body":82839,"description":83233,"extension":793,"meta":83234,"navigation":797,"path":397,"seo":83239,"stem":83240,"__hash__":83241},"content/blog/2022/01/24/im-joining-vmware.md",{"type":648,"value":82840,"toc":83216},[82841,82852,82855,82861,82865,82868,82871,82874,82878,82881,82885,82900,82903,82909,82912,82917,82920,82924,82927,82930,82933,82953,82967,82973,82982,82985,82991,82994,82998,83001,83009,83012,83015,83018,83022,83028,83032,83035,83037,83040,83045,83048,83051,83054,83056,83063,83069,83072,83075,83078,83082,83088,83097,83100,83103,83106,83110,83113,83116,83119,83126,83132,83144,83148,83151,83208,83210],[651,82842,82843,82844,82847,82848,82851],{},"I am thrilled to announce that I have joined ",[2939,82845,82846],{},"VMWare"," as a ",[7300,82849,82850],{},"Spring Developer Advocate",". I have so many emotions going through my head right now as I sit down to write this article. The ones that stand out the most to me are excited and proud.",[651,82853,82854],{},"I’m excited for the opportunity that lies ahead and I know that this is just the start, there is a lot of work ahead. I’m proud of always believing in myself even when I had doubts if this was the career for me. I’m a self-taught developer and over my 22+ years, the only person I have been trying to prove anything to was myself. A job should never define you or your worth but this is a dream job and you better believe I’m going to celebrate this one 🥳",[651,82856,82857],{},[660,82858],{"alt":82859,"src":82860},"1642029002280.jpeg","/images/blog/2022/01/24/1642029002280.jpeg",[4542,82862,82864],{"id":82863},"a-trip-down-spring-memory-lane","A Trip down Spring Memory Lane",[651,82866,82867],{},"If you have been following me and my content you know that I am a huge fan of Spring, the ecosystem, and the community. I thought I would take this opportunity to tell you how and when I got started with Spring as well as what I have been up to lately.",[651,82869,82870],{},"My first introduction to the Spring Framework was around 2012 when I did some freelancing on a few side projects. I remember being impressed with the versatility of the framework but I also remember it being incredibly complex to stand up a new project. Trying to figure out what dependencies you needed, which ones played nicely together, and how to configure a basic application was not easy.",[651,82872,82873],{},"In 2013 I was working for a company that was moving a lot of their ColdFusion-based web applications over to Groovy & Grails. I was a huge fan of Groovy because it made things that were complex in Java really easy. Grails was a convention over configuration framework that allowed us to focus on the business logic and less on the infrastructure. For those that weren’t around for this Groovy & Grails at this time did for us what modern Java & Spring Boot do today.",[5909,82875,82877],{"id":82876},"springone-2013","SpringOne 2013",[651,82879,82880],{},"My company sent a few of us to SpringOne that year and my main goals for that conference were to see the rock stars of the Groovy & Grails world like Graeme Rocher, Burt Beckwith, Jeff Brown, Guillarme Laforge, Venkat Subramanian, Ken Kousen, Paul King, Dan Woods, and many more. I learned so much at that conference from them and a lot of others. I remember the sheer number of people at this conference and just being in awe. I was used to going to conferences where there were a few hundred and there were probably 2,000+ there.",[82882,82883,82884],"aside",{},"\n💡 Below is a picture from the room I stayed in and you can see the construction of what is now Levi Stadium in Santa Clara, where the San Francisco 49ers play. I also still have that USB key/bottle opener that I received at that conference!\n",[4509,82886,82888,82894],{"style":82887},"display:flex;gap:10px;",[651,82889,82890],{},[660,82891],{"alt":82892,"src":82893},"Levi Stadium","/images/blog/2022/01/24/spring_2013_room.jpg",[651,82895,82896],{},[660,82897],{"alt":82898,"src":82899},"Spring 2013 USB Key","/images/blog/2022/01/24/spring_2013_usb.jpg",[651,82901,82902],{},"It was in the opening night Keynote that I first heard about Spring Boot. Adrian Colyer and Dave Syer gave a good overview of what Spring Boot would do for Spring Developers. If you have never watched the keynote (or it's been a while) I would encourage you to check out just how far Spring has come since its debut!",[59634,82904],{"width":69527,"height":82905,"src":82906,"title":82907,"frameBorder":1060,"allow":82908,"allowFullScreen":797},500,"https://www.youtube.com/embed/jplkJIHPGos","YouTube video player","accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",[651,82910,82911],{},"A quote that stuck out to me then and stays true today:",[1004,82913,82914],{},[651,82915,82916],{},"“Spring Boot really puts the developer enjoyment back.”",[651,82918,82919],{},"As I said before as a developer my experience with Spring wasn’t a great first impression. Spring could have a solution for every problem under the sun but if developers didn’t enjoy using it, it might not be as popular as it is today. While I was excited to hear about the work they were doing with Spring Boot it was still new and I was focused on working with Groovy and Grails.",[5909,82921,82923],{"id":82922},"springone-2015","SpringOne 2015",[651,82925,82926],{},"I started playing with Spring Boot about 6 months later in April 2014 when version 1.0 was released. At this point, I wasn’t using it for anything serious but I do remember prototyping some applications at work to see if could solve some of the problems we were facing.",[651,82928,82929],{},"In 2015 I made my triumphant return to SpringOne which took place on September 14-17 at the Marriott in Washington DC. I remember this fondly because my coworker and friend Sam Farmer lived in DC and I got to spend some time with him and his family. The hotel and convention center was honestly one of the best settings for a conference I have ever been to. I also really enjoyed the chance to be a tourist in DC for a few days with some friends.",[82882,82931,82932],{},"\n💡 Here are a few images that I took from SpringOne 2015\n",[4509,82934,82935,82941,82947],{"style":82887},[651,82936,82937],{},[660,82938],{"alt":82939,"src":82940},"Spring 2015 Main Room","/images/blog/2022/01/24/spring_2015_main_room.jpeg",[651,82942,82943],{},[660,82944],{"alt":82945,"src":82946},"Spring 2015 Booths","/images/blog/2022/01/24/spring_2015_booths.jpeg",[651,82948,82949],{},[660,82950],{"alt":82951,"src":82952},"Spring 2015 Hotel","/images/blog/2022/01/24/spring_2015_hotel.jpeg",[4509,82954,82955,82961],{"style":82887},[651,82956,82957],{},[660,82958],{"alt":82959,"src":82960},"Spring 2015 Swag","/images/blog/2022/01/24/spring_2015_swag.jpeg",[651,82962,82963],{},[660,82964],{"alt":82965,"src":82966},"Spring 2015 Dave and Josh","/images/blog/2022/01/24/spring_2015_dave_josh.jpeg",[651,82968,82969,82970,82972],{},"This time around I was there to learn as much as I could about ",[2939,82971,7077],{}," because at this point I realized it was going to take over the Java world. I mainly focused on beginner sessions around Spring Boot, Spring Data, Spring Cloud, and I focused on what questions developers were asking. I consumed as much knowledge as I could and I came home with a purpose.",[651,82974,82975,82976,82981],{},"I began working on my first course, ",[812,82977,82980],{"href":82978,"rel":82979},"https://www.udemy.com/course/spring-boot-intro/?referralCode=2B0F1F9DE0DC40C97DC5",[816],"Learn Spring Boot"," as soon as I got home. I realized that while there were some courses out there on Spring Boot, I could offer a different perspective. A lot of people believe that you need to be an expert to teach and that is a huge misconception. You only need to be one step ahead of your intended audience, which I now was.",[651,82983,82984],{},"I would launch this course on November 17th just in time for the big Udemy Black Friday Sale. This course would end up being almost 14 hours in length and as a sidebar, I would not recommend making that long of a course as your first one. As you can see from the screenshot this course has almost 24,000 students worldwide.",[651,82986,82987],{},[660,82988],{"alt":82989,"src":82990},"Learn Spring Boot Course","/images/blog/2022/01/24/learn_spring_boot_course.png",[651,82992,82993],{},"I cringe looking back at some of the videos I created for this course but you have to start somewhere. Don’t let production quality stop you if you know your content is good. I now have 6 courses and close to 140,000 students in 184 countries. That still blows my mind and I’m very grateful for that opportunity 🙏 I’m hoping I have some time this year to update my existing courses as well as the opportunity to create some new ones.",[5909,82995,82997],{"id":82996},"springone-2021","SpringOne 2021",[651,82999,83000],{},"After attending a couple of conferences in person I was excited to be selected to speak at my first SpringOne. While this conference was virtual that didn’t change my excitement level. I was going to share the “virtual” stage with so many amazing speakers, some of which I have looked up to since that first conference in 2013.",[651,83002,83003,83004,83008],{},"My presentation was on ",[812,83005,83007],{"href":82585,"rel":83006},[816],"Full-Stack Development with Spring Boot and Vue",". If you follow me you know I am a big fan of using Vue for frontend development. Much like Spring Boot does for backend development Vue simplifies so many things and lets me focus on building out my features.",[651,83010,83011],{},"A big advantage of giving a remote conference talk for me was that I was in a familiar setting. I create videos for YouTube so being able to sit down at my desk, have my external monitors, keyboard, and mouse made everything easy.",[651,83013,83014],{},"Well, easy until I got a text from my now boss Tasha telling me that Zoom had dropped on their end for everyone and that I was probably talking to air. She was right, I didn’t even notice and was just rambling to nobody. I logged right back in and without hesitation, I picked up right where I left off. I like to think that I impressed a few people, including Tasha with how calm and collected I was. What everyone fails to realize is that I have raised 2 babies during Covid, very few things rattle me these days 🤣",[59634,83016],{"width":69527,"height":82905,"src":83017,"title":82907,"frameBorder":1060,"allow":82908,"allowFullScreen":797},"https://www.youtube.com/embed/VkrGHqwSPVA",[5909,83019,83021],{"id":83020},"springone-2022-beyond","SpringOne 2022 & Beyond!",[651,83023,83024,83025,664],{},"My hope is that we return to an in-person conference this year and I get to spend some time talking to all of you. I can’t tell you how excited I am to hear what problems you’re facing and how I can help. In the meantime, if you’re interested in me speaking at your conference or user group please feel free to reach out to me on ",[812,83026,51474],{"href":44086,"rel":83027},[816],[4542,83029,83031],{"id":83030},"what-have-i-been-up-to","What have I been up to?",[651,83033,83034],{},"After a quick history lesson, I thought I would take some time to tell you what I have been up to lately and what led to this opportunity.",[5909,83036,41137],{"id":41664},[651,83038,83039],{},"I joined Tech Elevator back in 2018 and I can honestly say that no other company has had an impact on me the way they did. Tech Elevator is a coding BootCamp where you can learn how to code and the soft skills needed to start a career in software development. It was great to see people change careers and their lives after going through our program.",[651,83041,83042],{},[660,83043],{"alt":41137,"src":83044},"/images/blog/2022/01/24/tech_elevator.png",[651,83046,83047],{},"I was hired on as a Curriculum Developer. My job was to help improve the curriculum the students went through as well as the materials the instructors would use to teach with. We had 2 tracks, Java and .NET. In these tracks, the students would learn the skills needed to be well-rounded full-stack developers.",[651,83049,83050],{},"I was particularly excited to work on both the Java and Vue content. On the Java side, I helped us move from Java 8 to Java 11 and Spring + Eclipse over to Spring Boot & IntelliJ. I had a lot of fun updating content and in some cases rewriting entire days of the curriculum. I had a lot of fun improving our tests including our tests of tests which involved a lot of reflection 🤯",[651,83052,83053],{},"Even though I created my own curriculum in the past, Tech Elevator gave me a great guide to what it takes to create a really good experience for the instructor and the student. I miss it there, the energy the students came in with every day was infectious. I hope to make it back there and share some more wisdom with them this year.",[5909,83055,82568],{"id":82567},[651,83057,83058,83059,83062],{},"I spent a year with ",[812,83060,82568],{"href":79386,"rel":83061},[816]," and I was recently promoted to Principal Enterprise Architect. Briebug is a consulting company and I worked on one client the entire time I was there. This client was the largest logistics company in the world and I got an opportunity to work on some really cool projects that were at a scale I wasn’t used to working on.",[651,83064,83065],{},[660,83066],{"alt":83067,"src":83068},"kari-shea-1SAnrIxw5OY-unsplash.jpg","/images/blog/2022/01/24/kari-shea-1SAnrIxw5OY-unsplash.jpg",[651,83070,83071],{},"All of the projects I worked on were projects using Spring Boot, Spring Cloud, and Pivotal Cloud Foundry (PCF). I also had the opportunity to design a new system from the ground up that ended up being a set of 10 or so Microservices.",[651,83073,83074],{},"What I really enjoyed about this process is that a lot of the team members were new to Microservices and Spring Cloud in general. I ended up setting up office hours meetings every single day that gave the developers the opportunity to ask questions, and if there were no questions that day I would go through code reviews and explain what changes we needed to make and why.",[651,83076,83077],{},"I really enjoy mentoring and teaching so these are some of my fondest memories over the past year. At the end of the day, I learned that I didn’t like consulting and working for 2 companies, it’s not for everyone. I enjoyed my time with Briebug, my coworkers, and the core values the company stands for.",[4542,83079,83081],{"id":83080},"spring-developer-advocate","Spring Developer Advocate 🥑",[651,83083,83084],{},[660,83085],{"alt":83086,"src":83087},"mia-baker-ctRgcY-lY8I-unsplash.jpg","/images/blog/2022/01/24/mia-baker-ctRgcY-lY8I-unsplash.jpg",[651,83089,83090,83091,83096],{},"Now that you know a little bit of my history I want to take some time to tell you why this is a ",[2939,83092,83093],{},[7300,83094,83095],{},"dream job"," for me. I have always had a passion for the art of Software Development. For as long as I can remember though what has brought me the most joy throughout my career is helping others.",[651,83098,83099],{},"I come from a time when there was no StackOverflow or YouTube. I remember struggling with something and thinking to myself, someone else is having a hard time with this too so I am going to blog about it and hopefully save someone the stress of figuring this out.",[651,83101,83102],{},"During this time I found my superpower. Has anyone ever asked you “What is your superpower”? I used to get asked this and I would have no answer but I do now. My superpower is the ability to break down complex problems into easy-to-understand byte-sized chunks, also referred to as problem-solving. I’m good at that and then explaining the problem and solution to others.",[651,83104,83105],{},"I think if you take the experience I have of being in this industry for 22 years, Course Development, Curriculum Development, Writing, YouTube, and my ability to connect with others it’s a surprise I didn’t end up in Advocacy sooner.",[5909,83107,83109],{"id":83108},"vmware","VMware",[651,83111,83112],{},"I’m ecstatic to join such a world-class organization and I’m not taking this opportunity lightly. In this role, I will get to help customers and developers by informing them of solutions to problems. I really hope to be an inspiration to you the way so many have been for me over the years.",[651,83114,83115],{},"I’m so thankful to my boss Tasha Isenberg for giving me this opportunity. Everyone I talk to her raves about her as a person and a Boss and I can already see why. I’m so excited to join such an amazing team of advocates who are all some of the best developers and teachers in the industry.",[651,83117,83118],{},"I’m excited to call Josh Long a coworker, he is someone I have looked up to for a long time and I’m eager to learn from him. I’m excited to work with Nate Schutta who is one of the easiest people In the world to have a conversation with. He also did something very important for me, he introduced me to Ted Lasso.",[59634,83120],{"src":83121,"width":83122,"height":83123,"frameBorder":1060,"className":83124,"allowFullScreen":797},"https://giphy.com/embed/ZYKLgA1pB1WSPQASCs",480,270,[83125],"giphy-embed",[651,83127,83128],{},[812,83129,83131],{"href":83130},"https://giphy.com/gifs/AppleTV-ted-lasso-tedlasso-ZYKLgA1pB1WSPQASCs","via GIPHY",[651,83133,83134,83135,83137,83138,83143],{},"The one thing I am really looking forward to as the world continues to open back up is to get back to in-person conferences. I hope to be speaking at a few more this year and I already have my eyes on ",[2939,83136,82113],{},". If you see me at a conference or a ",[812,83139,83142],{"href":83140,"rel":83141},"https://tanzu.vmware.com/developer/tv/springone-tour/",[816],"SpringOne Tour stop"," please say hello 👋🏻 and let’s talk about any of the problems you’re facing.",[4542,83145,83147],{"id":83146},"thank-you","Thank you 🙏",[651,83149,83150],{},"There are so many people that I would like to thank for being there for me throughout my career. I have certainly worked my butt off over the years but I have also been incredibly lucky to be surrounded by some really great people. In no particular order I want to thank the following people:",[5316,83152,83153,83156,83159,83162,83165,83168,83171,83174,83177,83180,83183,83185,83188,83191,83194,83197,83199,83202,83205],{},[5332,83154,83155],{},"My Wife & Kids ❤️",[5332,83157,83158],{},"Tasha Isenberg",[5332,83160,83161],{},"Dana Hawthorne",[5332,83163,83164],{},"Ken Kousen",[5332,83166,83167],{},"Nate Schutta",[5332,83169,83170],{},"Josh Long",[5332,83172,83173],{},"Jesse Sanders",[5332,83175,83176],{},"John Kim",[5332,83178,83179],{},"Andrew Veliath",[5332,83181,83182],{},"David Winrich",[5332,83184,41159],{},[5332,83186,83187],{},"Sam Farmer",[5332,83189,83190],{},"Lance Staples",[5332,83192,83193],{},"Phil Rodopolous",[5332,83195,83196],{},"Todd Sharp",[5332,83198,43752],{},[5332,83200,83201],{},"Tracy Ash",[5332,83203,83204],{},"Sharat Chander",[5332,83206,83207],{},"Julien Dubois",[4542,83209,9042],{"id":9041},[651,83211,83212,83213,664],{},"If you can’t tell I am beyond excited about the opportunity that lies ahead of me. I’m going to find a good way for you to reach out and let me know what content you want me to create around Spring. Until I have something in place please feel free to reach out to me on ",[812,83214,51474],{"href":44086,"rel":83215},[816],{"title":674,"searchDepth":790,"depth":790,"links":83217},[83218,83224,83228,83231,83232],{"id":82863,"depth":790,"text":82864,"children":83219},[83220,83221,83222,83223],{"id":82876,"depth":892,"text":82877},{"id":82922,"depth":892,"text":82923},{"id":82996,"depth":892,"text":82997},{"id":83020,"depth":892,"text":83021},{"id":83030,"depth":790,"text":83031,"children":83225},[83226,83227],{"id":41664,"depth":892,"text":41137},{"id":82567,"depth":892,"text":82568},{"id":83080,"depth":790,"text":83081,"children":83229},[83230],{"id":83108,"depth":892,"text":83109},{"id":83146,"depth":790,"text":83147},{"id":9041,"depth":790,"text":9042},"I'm so excited to announce that I am joining VMware as a Spring Developer Advocate!",{"slug":83235,"date":83236,"published":797,"author":798,"tags":83237,"cover":83238},"im-joining-vmware","2022-01-24T08:00:00.000Z",[11968],"./im-joining-vmware-cover.png",{"title":396,"description":83233},"blog/2022/01/24/im-joining-vmware","fvoLT9dUEIat-kHQJfoaukLhidOLyFKPvELL83vwXdk",{"id":83243,"title":393,"body":83244,"description":83817,"extension":793,"meta":83818,"navigation":797,"path":394,"seo":83824,"stem":83825,"__hash__":83826},"content/blog/2022/05/11/spring-boot-value-annotation.md",{"type":648,"value":83245,"toc":83811},[83246,83249,83255,83258,83265,83270,83290,83300,83362,83373,83386,83397,83469,83486,83576,83583,83592,83680,83683,83720,83724,83730,83739,83746,83759,83765,83771,83773,83778,83787,83790,83792,83804,83808],[651,83247,83248],{},"Spring Boot allows you to configure your application through a number of property sources. A property source, simply put, is a source for your configuration such as Java properties files, YAML files, environment variables, command line arguments and more.",[651,83250,83251,83252,83254],{},"In this article you will learn about how to set configuration properties and values and then inject them into your application using the ",[676,83253,40349],{}," annotation. You will also learn about Spring Boot’s property source order and how to override configuration values.",[4542,83256,393],{"id":83257},"spring-boot-value-annotation",[651,83259,83260,83261,83264],{},"For this example I am using a simple application that I bootstrapped using ",[812,83262,77478],{"href":30440,"rel":83263},[816]," and the only dependency you need is Spring Web.",[651,83266,83267],{},[660,83268],{"alt":7117,"src":83269},"/images/blog/2022/05/11/spring-init.png",[651,83271,83272,83273,83275,83276,83279,83280,83282,83283,83286,83287],{},"The first thing you will need to do is to create a new class named ",[676,83274,77720],{}," and to mark it as a ",[676,83277,83278],{},"@RestController."," Next, create a method named ",[676,83281,12642],{}," that returns the hard coded String “",[2939,83284,83285],{},"Hello, World","”. To make this accessible on the root context annotate it with ",[676,83288,83289],{},"@GetMapping.",[651,83291,83292,83293,83296,83297,83299],{},"If you were to run this application and open ",[812,83294,11697],{"href":11697,"rel":83295},[816]," you should see the text ",[2939,83298,38829],{}," While this works and is a bit of a contrived example this greeting message might not be something you want to hard code. Any value that you want to be configurable should move to configuration.",[669,83301,83303],{"className":4107,"code":83302,"language":4109,"meta":674,"style":674},"@RestController\npublic class GreetingController {\n\n @GetMapping\n public String home() {\n return \"Hello, World!!\";\n }\n\n}\n",[676,83304,83305,83311,83321,83325,83331,83341,83350,83354,83358],{"__ignoreMap":674},[679,83306,83307,83309],{"class":681,"line":682},[679,83308,4116],{"class":693},[679,83310,9212],{"class":685},[679,83312,83313,83315,83317,83319],{"class":681,"line":790},[679,83314,6073],{"class":685},[679,83316,4512],{"class":685},[679,83318,77796],{"class":880},[679,83320,884],{"class":693},[679,83322,83323],{"class":681,"line":892},[679,83324,889],{"emptyLinePlaceholder":797},[679,83326,83327,83329],{"class":681,"line":901},[679,83328,6872],{"class":693},[679,83330,80415],{"class":685},[679,83332,83333,83335,83337,83339],{"class":681,"line":909},[679,83334,6089],{"class":685},[679,83336,9289],{"class":693},[679,83338,12642],{"class":880},[679,83340,2667],{"class":693},[679,83342,83343,83345,83348],{"class":681,"line":918},[679,83344,9444],{"class":685},[679,83346,83347],{"class":689}," \"Hello, World!!\"",[679,83349,1186],{"class":693},[679,83351,83352],{"class":681,"line":935},[679,83353,985],{"class":693},[679,83355,83356],{"class":681,"line":944},[679,83357,889],{"emptyLinePlaceholder":797},[679,83359,83360],{"class":681,"line":959},[679,83361,996],{"class":693},[651,83363,83364,83365,76149,83367,83369,83370,83372],{},"One of the property sources at your disposal are config data such as ",[676,83366,16242],{},[676,83368,31490],{}," variants. Open up ",[676,83371,16242],{}," and add the following key/value:",[669,83374,83376],{"className":5851,"code":83375,"language":5853,"meta":674,"style":674},"welcome.salutation=Hello\n",[676,83377,83378],{"__ignoreMap":674},[679,83379,83380,83383],{"class":681,"line":682},[679,83381,83382],{"class":880},"welcome.salutation",[679,83384,83385],{"class":689},"=Hello\n",[651,83387,83388,83389,83393,83394,664],{},"With the property set you need a way to inject that value of it into your controller. This is where the ",[812,83390,40349],{"href":83391,"rel":83392},"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Value.html",[816]," annotation will help by allowing us to use property-driven dependency injection. Create a new field of type String called ",[676,83395,83396],{},"welcomeSalutation",[669,83398,83400],{"className":4107,"code":83399,"language":4109,"meta":674,"style":674},"@RestController\npublic class GreetingController {\n\n private String welcomeSalutation;\n\n @GetMapping\n public String home() {\n return \"Hello, World!!\";\n }\n\n}\n",[676,83401,83402,83408,83418,83422,83429,83433,83439,83449,83457,83461,83465],{"__ignoreMap":674},[679,83403,83404,83406],{"class":681,"line":682},[679,83405,4116],{"class":693},[679,83407,9212],{"class":685},[679,83409,83410,83412,83414,83416],{"class":681,"line":790},[679,83411,6073],{"class":685},[679,83413,4512],{"class":685},[679,83415,77796],{"class":880},[679,83417,884],{"class":693},[679,83419,83420],{"class":681,"line":892},[679,83421,889],{"emptyLinePlaceholder":797},[679,83423,83424,83426],{"class":681,"line":901},[679,83425,9232],{"class":685},[679,83427,83428],{"class":693}," String welcomeSalutation;\n",[679,83430,83431],{"class":681,"line":909},[679,83432,889],{"emptyLinePlaceholder":797},[679,83434,83435,83437],{"class":681,"line":918},[679,83436,6872],{"class":693},[679,83438,80415],{"class":685},[679,83440,83441,83443,83445,83447],{"class":681,"line":935},[679,83442,6089],{"class":685},[679,83444,9289],{"class":693},[679,83446,12642],{"class":880},[679,83448,2667],{"class":693},[679,83450,83451,83453,83455],{"class":681,"line":944},[679,83452,9444],{"class":685},[679,83454,83347],{"class":689},[679,83456,1186],{"class":693},[679,83458,83459],{"class":681,"line":959},[679,83460,985],{"class":693},[679,83462,83463],{"class":681,"line":964},[679,83464,889],{"emptyLinePlaceholder":797},[679,83466,83467],{"class":681,"line":977},[679,83468,996],{"class":693},[651,83470,83471,83472,83474,83475,83478,83479,83482,83483,78287],{},"You can now add the ",[676,83473,40349],{}," annotation which expects The actual value expression such as ",[676,83476,83477],{},"#{systemProperties.myProp}","or property placeholder such as ",[676,83480,83481],{},"${my.app.myProp}."," Instead of returning the hard coded salutation you can now use the field in the return statement of your ",[676,83484,83485],{},"home()",[669,83487,83489],{"className":4107,"code":83488,"language":4109,"meta":674,"style":674},"@RestController\npublic class GreetingController {\n\n @Value(\"${welcome.salutation}\")\n private String welcomeSalutation;\n\n @GetMapping\n public String home() {\n return welcomeSalutation + \", World!\";\n }\n\n}\n",[676,83490,83491,83497,83507,83511,83524,83530,83534,83540,83550,83564,83568,83572],{"__ignoreMap":674},[679,83492,83493,83495],{"class":681,"line":682},[679,83494,4116],{"class":693},[679,83496,9212],{"class":685},[679,83498,83499,83501,83503,83505],{"class":681,"line":790},[679,83500,6073],{"class":685},[679,83502,4512],{"class":685},[679,83504,77796],{"class":880},[679,83506,884],{"class":693},[679,83508,83509],{"class":681,"line":892},[679,83510,889],{"emptyLinePlaceholder":797},[679,83512,83513,83515,83517,83519,83522],{"class":681,"line":901},[679,83514,6872],{"class":693},[679,83516,31784],{"class":685},[679,83518,745],{"class":693},[679,83520,83521],{"class":689},"\"${welcome.salutation}\"",[679,83523,1339],{"class":693},[679,83525,83526,83528],{"class":681,"line":909},[679,83527,9232],{"class":685},[679,83529,83428],{"class":693},[679,83531,83532],{"class":681,"line":918},[679,83533,889],{"emptyLinePlaceholder":797},[679,83535,83536,83538],{"class":681,"line":935},[679,83537,6872],{"class":693},[679,83539,80415],{"class":685},[679,83541,83542,83544,83546,83548],{"class":681,"line":944},[679,83543,6089],{"class":685},[679,83545,9289],{"class":693},[679,83547,12642],{"class":880},[679,83549,2667],{"class":693},[679,83551,83552,83554,83557,83559,83562],{"class":681,"line":959},[679,83553,9444],{"class":685},[679,83555,83556],{"class":693}," welcomeSalutation ",[679,83558,3065],{"class":685},[679,83560,83561],{"class":689}," \", World!\"",[679,83563,1186],{"class":693},[679,83565,83566],{"class":681,"line":964},[679,83567,985],{"class":693},[679,83569,83570],{"class":681,"line":977},[679,83571,889],{"emptyLinePlaceholder":797},[679,83573,83574],{"class":681,"line":982},[679,83575,996],{"class":693},[651,83577,83578,83579,83582],{},"If you run the application you should see the same Hello, World! text displayed on the page. If you change the value in ",[676,83580,83581],{},"applications.properties"," and restart the app you should see that change reflected. Congrats, you have now externalized the value of welcomeSalutation to a property source.",[651,83584,83585,83586,83588,83589,83591],{},"You might not realize it yet but there is a problem you have solved for. If you remove the property from ",[676,83587,16242],{}," and try to run the application you will get an error. Spring is trying inject a value for a property it can’t find in any of its property sources. For this situation you should provide a default value in the case that ",[676,83590,83396],{}," isn’t set. You can do so by adding a colon after the property name and supplying the default value.",[669,83593,83595],{"className":4107,"code":83594,"language":4109,"meta":674,"style":674},"@RestController\npublic class GreetingController {\n\n @Value(\"${welcome.salutation:👋🏻 Hello}\")\n private String welcomeSalutation;\n\n @GetMapping\n public String home() {\n return welcomeSalutation + \", World!\";\n }\n\n}\n",[676,83596,83597,83603,83613,83617,83630,83636,83640,83646,83656,83668,83672,83676],{"__ignoreMap":674},[679,83598,83599,83601],{"class":681,"line":682},[679,83600,4116],{"class":693},[679,83602,9212],{"class":685},[679,83604,83605,83607,83609,83611],{"class":681,"line":790},[679,83606,6073],{"class":685},[679,83608,4512],{"class":685},[679,83610,77796],{"class":880},[679,83612,884],{"class":693},[679,83614,83615],{"class":681,"line":892},[679,83616,889],{"emptyLinePlaceholder":797},[679,83618,83619,83621,83623,83625,83628],{"class":681,"line":901},[679,83620,6872],{"class":693},[679,83622,31784],{"class":685},[679,83624,745],{"class":693},[679,83626,83627],{"class":689},"\"${welcome.salutation:👋🏻 Hello}\"",[679,83629,1339],{"class":693},[679,83631,83632,83634],{"class":681,"line":909},[679,83633,9232],{"class":685},[679,83635,83428],{"class":693},[679,83637,83638],{"class":681,"line":918},[679,83639,889],{"emptyLinePlaceholder":797},[679,83641,83642,83644],{"class":681,"line":935},[679,83643,6872],{"class":693},[679,83645,80415],{"class":685},[679,83647,83648,83650,83652,83654],{"class":681,"line":944},[679,83649,6089],{"class":685},[679,83651,9289],{"class":693},[679,83653,12642],{"class":880},[679,83655,2667],{"class":693},[679,83657,83658,83660,83662,83664,83666],{"class":681,"line":959},[679,83659,9444],{"class":685},[679,83661,83556],{"class":693},[679,83663,3065],{"class":685},[679,83665,83561],{"class":689},[679,83667,1186],{"class":693},[679,83669,83670],{"class":681,"line":964},[679,83671,985],{"class":693},[679,83673,83674],{"class":681,"line":977},[679,83675,889],{"emptyLinePlaceholder":797},[679,83677,83678],{"class":681,"line":982},[679,83679,996],{"class":693},[651,83681,83682],{},"Another great feature is that property values can be contrived from other properties:",[669,83684,83686],{"className":4107,"code":83685,"language":4109,"meta":674,"style":674},"welcome.salutation=Hello\nwelcome.name=Dan\nwelcome.greeting=${welcome.salutation}, ${welcome.name}!\n",[676,83687,83688,83697,83707],{"__ignoreMap":674},[679,83689,83690,83692,83694],{"class":681,"line":682},[679,83691,83382],{"class":693},[679,83693,686],{"class":685},[679,83695,83696],{"class":693},"Hello\n",[679,83698,83699,83702,83704],{"class":681,"line":790},[679,83700,83701],{"class":693},"welcome.name",[679,83703,686],{"class":685},[679,83705,83706],{"class":693},"Dan\n",[679,83708,83709,83712,83714,83717],{"class":681,"line":892},[679,83710,83711],{"class":693},"welcome.greeting",[679,83713,686],{"class":685},[679,83715,83716],{"class":693},"${welcome.salutation}, ${welcome.name}",[679,83718,83719],{"class":685},"!\n",[4542,83721,83723],{"id":83722},"spring-boot-property-source-order","Spring Boot Property Source Order",[651,83725,83726,83727,83729],{},"Now that you understand how to set a property and inject it into your application using the ",[676,83728,40349],{}," annotation you need to understand the property source order. Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments.",[651,83731,83732,83733,83738],{},"This means that while the welcome salutation is one value for local development it might be something different in another environment. I’m not going to cover every single property source here because the ",[812,83734,83737],{"href":83735,"rel":83736},"https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config",[816],"Spring Boot Reference documentation"," does a great job of that. All you need to know is that as you go down the list of property sources they can override previous ones. This means that command-line arguments override anything in config data (application.properties).",[651,83740,83741,83742,83745],{},"To test this out you can set a command line argument in IntelliJ by going to ",[676,83743,83744],{},"Run > Edit Configurations > Environment > Program Arguments"," and add the following arg:",[669,83747,83749],{"className":5851,"code":83748,"language":5853,"meta":674,"style":674},"--welcome.salutation=🤩Hello\n",[676,83750,83751],{"__ignoreMap":674},[679,83752,83753,83756],{"class":681,"line":682},[679,83754,83755],{"class":880},"--welcome.salutation",[679,83757,83758],{"class":689},"=🤩Hello\n",[651,83760,83761],{},[660,83762],{"alt":83763,"src":83764},"command-line arguments","/images/blog/2022/05/11/command-line-arguments.png",[651,83766,83767,83768,83770],{},"Even with a value set in ",[676,83769,16242],{}," this command line argument will now be used because of the property source order. Restart the application and see this change take affect.",[4542,83772,40340],{"id":40339},[651,83774,40060,83775,83777],{},[676,83776,40349],{}," annotation is the easiest way to get a single value from a property source into your Spring application. One of the downsides is that there is no metadata generated for these properties and because of that you won’t get any Intellisense in your property files.",[651,83779,83780,83781,83783,83784,83786],{},"If you have one or more related properties you should really move them into a ",[676,83782,22732],{}," class and with that you will get metadata generated for them. I have a tutorial on creating a class or record using ",[676,83785,22732],{}," and setting default values that you can check out below.",[5988,83788],{"id":83789},"Gqn_q2sAebg",[4542,83791,9042],{"id":9041},[651,83793,23533,83794,83796,83797,83799,83800,83803],{},[676,83795,40349],{}," annotation is the easiest way to inject configuration values into your application it might not always be the right choice. Knowing when to use it and when to reach for ",[676,83798,22732],{}," is half the battle (",[7300,83801,83802],{},"Go Joe!","). The other part of this is understanding that Spring Boot has a number of property sources and that there is a specific order in which they are loaded. I hope this tutorial helped you out and always friends...",[651,83805,41105,83806,41109],{},[41107,83807],{},[786,83809,83810],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":83812},[83813,83814,83815,83816],{"id":83257,"depth":790,"text":393},{"id":83722,"depth":790,"text":83723},{"id":40339,"depth":790,"text":40340},{"id":9041,"depth":790,"text":9042},"A brief introduction to the @Value annotation in Spring Boot.",{"slug":83257,"date":83819,"published":797,"author":798,"tags":83820,"cover":83821,"video":83822,"github":83823},"2022-05-11T09:30:00.000Z",[7055],"./spring-boot-value-annotation-thumbnail.png","https://www.youtube.com/embed/vLSyFktOm4g","https://github.com/danvega/spring-boot-value-annotation",{"title":393,"description":83817},"blog/2022/05/11/spring-boot-value-annotation","zeAbt20hflrAvcWnxEKuAA5kTKIq6PTRNU20nim4Y3I",{"id":83828,"title":390,"body":83829,"description":86327,"extension":793,"meta":86328,"navigation":797,"path":391,"seo":86333,"stem":86334,"__hash__":86335},"content/blog/2022/05/12/spring-data-jpa-pagination.md",{"type":648,"value":83830,"toc":86318},[83831,83834,83837,83840,83847,83852,83858,83900,83910,84160,84336,84340,84349,84356,84386,84397,84413,84479,84483,84490,84509,84522,84662,84668,84682,84789,84792,84796,84809,84819,85103,85111,85117,85120,85123,85143,85228,85241,85447,85460,85467,85624,85632,85639,86297,86299,86302,86311,86315],[651,83832,83833],{},"In this tutorial, you are going to learn how to work with pagination in Spring Data JPA. If you have a few records you can just return all of them from a single API endpoint. In the real world though you will probably have more than a few records. In this case you wouldn’t want to return all of the records to the client for performance reasons.",[651,83835,83836],{},"On the client you would display a smaller number of records at a time and allow the user to click on next, previous or a specific page number. To simulate that in this demo you will use a third party library to create a large dataset.",[4542,83838,41754],{"id":83839},"spring-data-jpa",[651,83841,83842,83843,83846],{},"To get started head over to ",[812,83844,77478],{"href":30440,"rel":83845},[816]," and create a new application with the following:",[651,83848,83849],{},[660,83850],{"alt":7117,"src":83851},"/images/blog/2022/05/12/spring-data-jpa-pagination.png",[651,83853,83854,83855,83857],{},"To get started open up ",[676,83856,16242],{}," and set the following properties for your database:",[669,83859,83861],{"className":5851,"code":83860,"language":5853,"meta":674,"style":674},"spring.h2.console.enabled=true\nspring.datasource.generate-unique-name=false\nspring.datasource.name=people\n\nspring.jpa.show-sql=true\n",[676,83862,83863,83871,83880,83887,83891],{"__ignoreMap":674},[679,83864,83865,83867,83869],{"class":681,"line":682},[679,83866,7323],{"class":880},[679,83868,686],{"class":689},[679,83870,5134],{"class":931},[679,83872,83873,83876,83878],{"class":681,"line":790},[679,83874,83875],{"class":880},"spring.datasource.generate-unique-name",[679,83877,686],{"class":689},[679,83879,2649],{"class":931},[679,83881,83882,83884],{"class":681,"line":892},[679,83883,27252],{"class":880},[679,83885,83886],{"class":689},"=people\n",[679,83888,83889],{"class":681,"line":901},[679,83890,889],{"emptyLinePlaceholder":797},[679,83892,83893,83896,83898],{"class":681,"line":909},[679,83894,83895],{"class":880},"spring.jpa.show-sql",[679,83897,686],{"class":689},[679,83899,5134],{"class":931},[651,83901,83902,83903,23212,83906,83909],{},"With the database connection in place you need to start by defining your entities. This application is going to model a CRM where you have a person and their address. Create a new model package and add the following ",[676,83904,83905],{},"Person",[676,83907,83908],{},"Address"," entities:",[669,83911,83913],{"className":4107,"code":83912,"language":4109,"meta":674,"style":674},"@Entity\npublic class Person {\n\n @Id @GeneratedValue\n private Integer id;\n private String firstName;\n private String lastName;\n private String phoneNumber;\n private String email;\n @OneToOne(cascade = CascadeType.ALL)\n @JoinColumn(name = \"address_id\", referencedColumnName = \"id\")\n private Address address;\n\n public Person() {\n }\n\n public Person(String firstName, String lastName, String phoneNumber, String email, Address address) {\n this.firstName = firstName;\n this.lastName = lastName;\n this.phoneNumber = phoneNumber;\n this.email = email;\n this.address = address;\n }\n\n // getters, setters & toString\n\n}\n",[676,83914,83915,83921,83932,83936,83946,83953,83959,83966,83973,83979,83996,84024,84030,84034,84042,84046,84050,84082,84093,84105,84117,84127,84139,84143,84147,84152,84156],{"__ignoreMap":674},[679,83916,83917,83919],{"class":681,"line":682},[679,83918,4116],{"class":693},[679,83920,11234],{"class":685},[679,83922,83923,83925,83927,83930],{"class":681,"line":790},[679,83924,6073],{"class":685},[679,83926,4512],{"class":685},[679,83928,83929],{"class":880}," Person",[679,83931,884],{"class":693},[679,83933,83934],{"class":681,"line":892},[679,83935,889],{"emptyLinePlaceholder":797},[679,83937,83938,83940,83942,83944],{"class":681,"line":901},[679,83939,6872],{"class":693},[679,83941,11256],{"class":685},[679,83943,6475],{"class":693},[679,83945,11261],{"class":685},[679,83947,83948,83950],{"class":681,"line":909},[679,83949,9232],{"class":685},[679,83951,83952],{"class":693}," Integer id;\n",[679,83954,83955,83957],{"class":681,"line":918},[679,83956,9232],{"class":685},[679,83958,35749],{"class":693},[679,83960,83961,83963],{"class":681,"line":935},[679,83962,9232],{"class":685},[679,83964,83965],{"class":693}," String lastName;\n",[679,83967,83968,83970],{"class":681,"line":944},[679,83969,9232],{"class":685},[679,83971,83972],{"class":693}," String phoneNumber;\n",[679,83974,83975,83977],{"class":681,"line":959},[679,83976,9232],{"class":685},[679,83978,13626],{"class":693},[679,83980,83981,83983,83986,83988,83991,83993],{"class":681,"line":964},[679,83982,6872],{"class":693},[679,83984,83985],{"class":685},"OneToOne",[679,83987,745],{"class":693},[679,83989,83990],{"class":931},"cascade",[679,83992,6883],{"class":685},[679,83994,83995],{"class":693}," CascadeType.ALL)\n",[679,83997,83998,84000,84003,84005,84007,84009,84012,84014,84017,84019,84022],{"class":681,"line":977},[679,83999,6872],{"class":693},[679,84001,84002],{"class":685},"JoinColumn",[679,84004,745],{"class":693},[679,84006,16334],{"class":931},[679,84008,6883],{"class":685},[679,84010,84011],{"class":689}," \"address_id\"",[679,84013,2797],{"class":693},[679,84015,84016],{"class":931},"referencedColumnName",[679,84018,6883],{"class":685},[679,84020,84021],{"class":689}," \"id\"",[679,84023,1339],{"class":693},[679,84025,84026,84028],{"class":681,"line":982},[679,84027,9232],{"class":685},[679,84029,33603],{"class":693},[679,84031,84032],{"class":681,"line":988},[679,84033,889],{"emptyLinePlaceholder":797},[679,84035,84036,84038,84040],{"class":681,"line":993},[679,84037,6089],{"class":685},[679,84039,83929],{"class":880},[679,84041,2667],{"class":693},[679,84043,84044],{"class":681,"line":2129},[679,84045,985],{"class":693},[679,84047,84048],{"class":681,"line":2140},[679,84049,889],{"emptyLinePlaceholder":797},[679,84051,84052,84054,84056,84058,84061,84063,84066,84068,84071,84073,84075,84078,84080],{"class":681,"line":2145},[679,84053,6089],{"class":685},[679,84055,83929],{"class":880},[679,84057,11400],{"class":693},[679,84059,84060],{"class":2099},"firstName",[679,84062,20006],{"class":693},[679,84064,84065],{"class":2099},"lastName",[679,84067,20006],{"class":693},[679,84069,84070],{"class":2099},"phoneNumber",[679,84072,20006],{"class":693},[679,84074,13988],{"class":2099},[679,84076,84077],{"class":693},", Address ",[679,84079,1355],{"class":2099},[679,84081,4390],{"class":693},[679,84083,84084,84086,84088,84090],{"class":681,"line":2154},[679,84085,7862],{"class":931},[679,84087,35788],{"class":693},[679,84089,686],{"class":685},[679,84091,84092],{"class":693}," firstName;\n",[679,84094,84095,84097,84100,84102],{"class":681,"line":2159},[679,84096,7862],{"class":931},[679,84098,84099],{"class":693},".lastName ",[679,84101,686],{"class":685},[679,84103,84104],{"class":693}," lastName;\n",[679,84106,84107,84109,84112,84114],{"class":681,"line":2164},[679,84108,7862],{"class":931},[679,84110,84111],{"class":693},".phoneNumber ",[679,84113,686],{"class":685},[679,84115,84116],{"class":693}," phoneNumber;\n",[679,84118,84119,84121,84123,84125],{"class":681,"line":3134},[679,84120,7862],{"class":931},[679,84122,13997],{"class":693},[679,84124,686],{"class":685},[679,84126,13966],{"class":693},[679,84128,84129,84131,84134,84136],{"class":681,"line":3139},[679,84130,7862],{"class":931},[679,84132,84133],{"class":693},".address ",[679,84135,686],{"class":685},[679,84137,84138],{"class":693}," address;\n",[679,84140,84141],{"class":681,"line":3144},[679,84142,985],{"class":693},[679,84144,84145],{"class":681,"line":3149},[679,84146,889],{"emptyLinePlaceholder":797},[679,84148,84149],{"class":681,"line":3169},[679,84150,84151],{"class":1400}," // getters, setters & toString\n",[679,84153,84154],{"class":681,"line":3185},[679,84155,889],{"emptyLinePlaceholder":797},[679,84157,84158],{"class":681,"line":3194},[679,84159,996],{"class":693},[669,84161,84163],{"className":4107,"code":84162,"language":4109,"meta":674,"style":674},"@Entity\npublic class Address {\n\n @Id @GeneratedValue\n private Integer id;\n private String address;\n private String city;\n private String state;\n private String zip;\n\n public Address() {\n }\n\n public Address(String address, String city, String state, String zip) {\n this.address = address;\n this.city = city;\n this.state = state;\n this.zip = zip;\n }\n\n // getters, setters & toString\n\n}\n",[676,84164,84165,84171,84181,84185,84195,84201,84208,84214,84220,84227,84231,84239,84243,84247,84273,84283,84294,84304,84316,84320,84324,84328,84332],{"__ignoreMap":674},[679,84166,84167,84169],{"class":681,"line":682},[679,84168,4116],{"class":693},[679,84170,11234],{"class":685},[679,84172,84173,84175,84177,84179],{"class":681,"line":790},[679,84174,6073],{"class":685},[679,84176,4512],{"class":685},[679,84178,33669],{"class":880},[679,84180,884],{"class":693},[679,84182,84183],{"class":681,"line":892},[679,84184,889],{"emptyLinePlaceholder":797},[679,84186,84187,84189,84191,84193],{"class":681,"line":901},[679,84188,6872],{"class":693},[679,84190,11256],{"class":685},[679,84192,6475],{"class":693},[679,84194,11261],{"class":685},[679,84196,84197,84199],{"class":681,"line":909},[679,84198,9232],{"class":685},[679,84200,83952],{"class":693},[679,84202,84203,84205],{"class":681,"line":918},[679,84204,9232],{"class":685},[679,84206,84207],{"class":693}," String address;\n",[679,84209,84210,84212],{"class":681,"line":935},[679,84211,9232],{"class":685},[679,84213,33696],{"class":693},[679,84215,84216,84218],{"class":681,"line":944},[679,84217,9232],{"class":685},[679,84219,19487],{"class":693},[679,84221,84222,84224],{"class":681,"line":959},[679,84223,9232],{"class":685},[679,84225,84226],{"class":693}," String zip;\n",[679,84228,84229],{"class":681,"line":964},[679,84230,889],{"emptyLinePlaceholder":797},[679,84232,84233,84235,84237],{"class":681,"line":977},[679,84234,6089],{"class":685},[679,84236,33669],{"class":880},[679,84238,2667],{"class":693},[679,84240,84241],{"class":681,"line":982},[679,84242,985],{"class":693},[679,84244,84245],{"class":681,"line":988},[679,84246,889],{"emptyLinePlaceholder":797},[679,84248,84249,84251,84253,84255,84257,84259,84262,84264,84266,84268,84271],{"class":681,"line":993},[679,84250,6089],{"class":685},[679,84252,33669],{"class":880},[679,84254,11400],{"class":693},[679,84256,1355],{"class":2099},[679,84258,20006],{"class":693},[679,84260,84261],{"class":2099},"city",[679,84263,20006],{"class":693},[679,84265,20009],{"class":2099},[679,84267,20006],{"class":693},[679,84269,84270],{"class":2099},"zip",[679,84272,4390],{"class":693},[679,84274,84275,84277,84279,84281],{"class":681,"line":2129},[679,84276,7862],{"class":931},[679,84278,84133],{"class":693},[679,84280,686],{"class":685},[679,84282,84138],{"class":693},[679,84284,84285,84287,84290,84292],{"class":681,"line":2140},[679,84286,7862],{"class":931},[679,84288,84289],{"class":693},".city ",[679,84291,686],{"class":685},[679,84293,1310],{"class":693},[679,84295,84296,84298,84300,84302],{"class":681,"line":2145},[679,84297,7862],{"class":931},[679,84299,20028],{"class":693},[679,84301,686],{"class":685},[679,84303,20033],{"class":693},[679,84305,84306,84308,84311,84313],{"class":681,"line":2154},[679,84307,7862],{"class":931},[679,84309,84310],{"class":693},".zip ",[679,84312,686],{"class":685},[679,84314,84315],{"class":693}," zip;\n",[679,84317,84318],{"class":681,"line":2159},[679,84319,985],{"class":693},[679,84321,84322],{"class":681,"line":2164},[679,84323,889],{"emptyLinePlaceholder":797},[679,84325,84326],{"class":681,"line":3134},[679,84327,84151],{"class":1400},[679,84329,84330],{"class":681,"line":3139},[679,84331,889],{"emptyLinePlaceholder":797},[679,84333,84334],{"class":681,"line":3144},[679,84335,996],{"class":693},[5909,84337,84339],{"id":84338},"spring-data-jpa-repositories","Spring Data JPA Repositories",[651,84341,84342,84343,84348],{},"With your entities in place you need a way to read and persist data from your database. This is where Spring Data really shines in giving you the functionality to perform most of your common operations right out of the box. What I really love is that this is a consistent programming model across all of the ",[812,84344,84347],{"href":84345,"rel":84346},"https://spring.io/projects/spring-data",[816],"Spring Data"," projects 👏🏻",[651,84350,84351,84352,84355],{},"Create a new interface (you heard me right) named ",[676,84353,84354],{},"PersonRepository",". Alone this wont do much but the real power is made available to you when you extend one of the Spring Data Repositories:",[5316,84357,84358,84365,84372,84379],{},[5332,84359,84360],{},[812,84361,84364],{"href":84362,"rel":84363},"https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/Repository.html",[816],"Repository\u003CT,ID>",[5332,84366,84367],{},[812,84368,84371],{"href":84369,"rel":84370},"https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html",[816],"CrudRepository\u003CT,ID>",[5332,84373,84374],{},[812,84375,84378],{"href":84376,"rel":84377},"https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html",[816],"PagingAndSortingRepository\u003CT,ID>",[5332,84380,84381],{},[812,84382,84385],{"href":84383,"rel":84384},"https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html",[816],"JpaRepository\u003CT,ID>",[651,84387,84388,84389,84392,84393,84396],{},"You are going to extend the ",[676,84390,84391],{},"PagingAndSortingRepository"," which gives you 2 methods you will need for pagination. This interface in turn extends the ",[676,84394,84395],{},"CrudRepostiory"," so you will also get all of the CRUD methods for free.",[651,84398,84399,84400,84402,84403,84406,84407,84409,84410,84412],{},"When you extend a repository you have to give a type which for this example will be ",[676,84401,83905],{}," and the 2nd argument is the type of the ",[676,84404,84405],{},"@id"," in ",[676,84408,83905],{}," which is an ",[676,84411,1083],{},". At runtime Spring will see that you have extended one of the repositories and turn this into an implementation that you can use.",[669,84414,84416],{"className":4107,"code":84415,"language":4109,"meta":674,"style":674},"package dev.danvega.graphqlpaging.repository;\n\nimport dev.danvega.graphqlpaging.model.Person;\nimport org.springframework.data.repository.PagingAndSortingRepository;\n\npublic interface PersonRepository extends PagingAndSortingRepository\u003CPerson,Integer> {\n\n}\n",[676,84417,84418,84425,84429,84436,84443,84447,84471,84475],{"__ignoreMap":674},[679,84419,84420,84422],{"class":681,"line":682},[679,84421,2543],{"class":685},[679,84423,84424],{"class":693}," dev.danvega.graphqlpaging.repository;\n",[679,84426,84427],{"class":681,"line":790},[679,84428,889],{"emptyLinePlaceholder":797},[679,84430,84431,84433],{"class":681,"line":892},[679,84432,1999],{"class":685},[679,84434,84435],{"class":693}," dev.danvega.graphqlpaging.model.Person;\n",[679,84437,84438,84440],{"class":681,"line":901},[679,84439,1999],{"class":685},[679,84441,84442],{"class":693}," org.springframework.data.repository.PagingAndSortingRepository;\n",[679,84444,84445],{"class":681,"line":909},[679,84446,889],{"emptyLinePlaceholder":797},[679,84448,84449,84451,84453,84456,84458,84461,84463,84465,84467,84469],{"class":681,"line":918},[679,84450,6073],{"class":685},[679,84452,6994],{"class":685},[679,84454,84455],{"class":880}," PersonRepository",[679,84457,2767],{"class":685},[679,84459,84460],{"class":880}," PagingAndSortingRepository",[679,84462,4505],{"class":693},[679,84464,83905],{"class":685},[679,84466,1202],{"class":693},[679,84468,1083],{"class":685},[679,84470,16397],{"class":693},[679,84472,84473],{"class":681,"line":935},[679,84474,889],{"emptyLinePlaceholder":797},[679,84476,84477],{"class":681,"line":944},[679,84478,996],{"class":693},[5909,84480,84482],{"id":84481},"sample-data-loader-with-a-command-line-runner","Sample Data Loader with a Command Line Runner",[651,84484,84485,84486,84489],{},"You need a way to load some data when the application starts. You could create a SQL file in the ",[676,84487,84488],{},"resources/"," directory but who wants to write SQL by hand? Instead you can write some Java code and persist some new records to the database.",[651,84491,84492,84493,84495,84496,84499,84500,84503,84504,84508],{},"The way you can achieve this is by utilizing an interface called the ",[676,84494,16415],{},". This will allow you to execute some code after the ",[676,84497,84498],{},"ApplicationContext"," is created and right before the ",[676,84501,84502],{},"SpringApplication.run()"," method executes. I have written a post on the ",[812,84505,84507],{"href":84506},"/command-line-runner","CommandLineRunner here"," if you would like to read more about it.",[651,84510,84511,84512,84515,84516,84518,84519,84521],{},"You will need to create a new class called ",[676,84513,84514],{},"SampleDataLoader"," that implements the ",[676,84517,16415],{}," interface. Implement the single method from the command line runner named ",[676,84520,16219],{}," and for now just log a message. If you run the application you should see that message in your console.",[669,84523,84525],{"className":4107,"code":84524,"language":4109,"meta":674,"style":674},"@Component\npublic class SampleDataLoader implements CommandLineRunner {\n\n private final Logger logger = LoggerFactory.getLogger(SampleDataLoader.class);\n private final PersonRepository repository;\n\n public SampleDataLoader(PersonRepository repository) {\n this.repository = repository;\n }\n\n @Override\n public void run(String... args) throws Exception {\n logger.info(\"Loading sample data...\");\n }\n\n}\n",[676,84526,84527,84533,84548,84552,84569,84578,84582,84595,84605,84609,84613,84619,84637,84650,84654,84658],{"__ignoreMap":674},[679,84528,84529,84531],{"class":681,"line":682},[679,84530,4116],{"class":693},[679,84532,13105],{"class":685},[679,84534,84535,84537,84539,84542,84544,84546],{"class":681,"line":790},[679,84536,6073],{"class":685},[679,84538,4512],{"class":685},[679,84540,84541],{"class":880}," SampleDataLoader",[679,84543,4661],{"class":685},[679,84545,16444],{"class":880},[679,84547,884],{"class":693},[679,84549,84550],{"class":681,"line":892},[679,84551,889],{"emptyLinePlaceholder":797},[679,84553,84554,84556,84558,84560,84562,84564,84566],{"class":681,"line":901},[679,84555,9232],{"class":685},[679,84557,12768],{"class":685},[679,84559,9235],{"class":693},[679,84561,686],{"class":685},[679,84563,9240],{"class":693},[679,84565,9243],{"class":880},[679,84567,84568],{"class":693},"(SampleDataLoader.class);\n",[679,84570,84571,84573,84575],{"class":681,"line":909},[679,84572,9232],{"class":685},[679,84574,12768],{"class":685},[679,84576,84577],{"class":693}," PersonRepository repository;\n",[679,84579,84580],{"class":681,"line":918},[679,84581,889],{"emptyLinePlaceholder":797},[679,84583,84584,84586,84588,84591,84593],{"class":681,"line":935},[679,84585,6089],{"class":685},[679,84587,84541],{"class":880},[679,84589,84590],{"class":693},"(PersonRepository ",[679,84592,16596],{"class":2099},[679,84594,4390],{"class":693},[679,84596,84597,84599,84601,84603],{"class":681,"line":944},[679,84598,7862],{"class":931},[679,84600,16605],{"class":693},[679,84602,686],{"class":685},[679,84604,16610],{"class":693},[679,84606,84607],{"class":681,"line":959},[679,84608,985],{"class":693},[679,84610,84611],{"class":681,"line":964},[679,84612,889],{"emptyLinePlaceholder":797},[679,84614,84615,84617],{"class":681,"line":977},[679,84616,6872],{"class":693},[679,84618,10723],{"class":685},[679,84620,84621,84623,84625,84627,84629,84631,84633,84635],{"class":681,"line":982},[679,84622,6089],{"class":685},[679,84624,6095],{"class":685},[679,84626,16486],{"class":880},[679,84628,16489],{"class":693},[679,84630,6108],{"class":2099},[679,84632,2378],{"class":693},[679,84634,9580],{"class":685},[679,84636,10466],{"class":693},[679,84638,84639,84641,84643,84645,84648],{"class":681,"line":988},[679,84640,12851],{"class":693},[679,84642,9415],{"class":880},[679,84644,745],{"class":693},[679,84646,84647],{"class":689},"\"Loading sample data...\"",[679,84649,1208],{"class":693},[679,84651,84652],{"class":681,"line":993},[679,84653,985],{"class":693},[679,84655,84656],{"class":681,"line":2129},[679,84657,889],{"emptyLinePlaceholder":797},[679,84659,84660],{"class":681,"line":2140},[679,84661,996],{"class":693},[651,84663,84664],{},[660,84665],{"alt":84666,"src":84667},"Loading Sample Data","/images/blog/2022/05/12/loading-sample-data.png",[651,84669,84670,84671,84673,84674,84677,84678,84681],{},"Now that you know that the run method will execute right before the application starts this is a good place to add some data to our database. Create a new instance of ",[676,84672,83905],{}," and use the ",[676,84675,84676],{},"CrudRepositories"," save method to persist a new record to the database. If you visit the ",[812,84679,16741],{"href":16733,"rel":84680},[816]," you should your new person and address records.",[669,84683,84685],{"className":4107,"code":84684,"language":4109,"meta":674,"style":674},"@Override\npublic void run(String... args) throws Exception {\n Person person = new Person(\"Dan\",\n \"Vega\",\n \"216.555.1212\",\n \"danvega@gmail.com\",\n new Address(\"Street\",\"City\",\"State\",\"Zip\"));\n\n repository.save(person);\n}\n",[676,84686,84687,84693,84704,84721,84728,84735,84742,84771,84775,84785],{"__ignoreMap":674},[679,84688,84689,84691],{"class":681,"line":682},[679,84690,4116],{"class":693},[679,84692,10723],{"class":685},[679,84694,84695,84697,84699,84701],{"class":681,"line":790},[679,84696,6073],{"class":685},[679,84698,6095],{"class":685},[679,84700,16486],{"class":880},[679,84702,84703],{"class":693},"(String... args) throws Exception {\n",[679,84705,84706,84709,84711,84713,84715,84717,84719],{"class":681,"line":892},[679,84707,84708],{"class":693}," Person person ",[679,84710,686],{"class":685},[679,84712,2054],{"class":685},[679,84714,83929],{"class":880},[679,84716,745],{"class":693},[679,84718,1414],{"class":689},[679,84720,12083],{"class":693},[679,84722,84723,84726],{"class":681,"line":901},[679,84724,84725],{"class":689}," \"Vega\"",[679,84727,12083],{"class":693},[679,84729,84730,84733],{"class":681,"line":909},[679,84731,84732],{"class":689}," \"216.555.1212\"",[679,84734,12083],{"class":693},[679,84736,84737,84740],{"class":681,"line":918},[679,84738,84739],{"class":689}," \"danvega@gmail.com\"",[679,84741,12083],{"class":693},[679,84743,84744,84747,84749,84751,84754,84756,84759,84761,84764,84766,84769],{"class":681,"line":935},[679,84745,84746],{"class":685}," new",[679,84748,33669],{"class":880},[679,84750,745],{"class":693},[679,84752,84753],{"class":689},"\"Street\"",[679,84755,1202],{"class":693},[679,84757,84758],{"class":689},"\"City\"",[679,84760,1202],{"class":693},[679,84762,84763],{"class":689},"\"State\"",[679,84765,1202],{"class":693},[679,84767,84768],{"class":689},"\"Zip\"",[679,84770,1669],{"class":693},[679,84772,84773],{"class":681,"line":944},[679,84774,889],{"emptyLinePlaceholder":797},[679,84776,84777,84780,84782],{"class":681,"line":959},[679,84778,84779],{"class":693}," repository.",[679,84781,7629],{"class":880},[679,84783,84784],{"class":693},"(person);\n",[679,84786,84787],{"class":681,"line":964},[679,84788,996],{"class":693},[651,84790,84791],{},"While this works it’s going to take us a long time to get enough sample data in our database to properly test out pagination.",[5909,84793,84795],{"id":84794},"java-faker","Java Faker",[651,84797,84798,84802,84803,84808],{},[812,84799,84795],{"href":84800,"rel":84801},"https://github.com/DiUS/java-faker",[816]," is a really great library that allows you to generate fake data. I ",[812,84804,84807],{"href":84805,"rel":84806},"https://youtu.be/UzBOv_SHUng",[816],"created a tutorial"," on this if you’re interested in watching that.",[651,84810,84811,84812,84815,84816,84818],{},"In the run method you can create a new ",[676,84813,84814],{},"IntStream"," which allows you to create 100 new ",[676,84817,83905],{}," objects. Inside of the person constructor you are going to use the Faker library to generate fake data for each of the fields.",[669,84820,84822],{"className":4107,"code":84821,"language":4109,"meta":674,"style":674},"private final PersonRepository repository;\nprivate final Faker faker;\n\npublic SampleDataLoader(PersonRepository repository) {\n this.repository = repository;\n this.faker = new Faker();\n}\n\n@Override\npublic void run(String... args) throws Exception {\n\n // create 100 rows of people in the database\n List\u003CPerson> people = IntStream.rangeClosed(1,100)\n .mapToObj(i -> new Person(\n faker.name().firstName(),\n faker.name().lastName(),\n faker.phoneNumber().cellPhone(),\n faker.internet().emailAddress(),\n new Address(\n faker.address().streetAddress(),\n faker.address().city(),\n faker.address().state(),\n faker.address().zipCode()\n )\n )).toList();\n\n repository.saveAll(people);\n}\n",[676,84823,84824,84832,84841,84845,84854,84864,84880,84884,84888,84894,84904,84908,84913,84941,84959,84972,84984,84997,85011,85020,85034,85046,85058,85071,85076,85086,85090,85099],{"__ignoreMap":674},[679,84825,84826,84828,84830],{"class":681,"line":682},[679,84827,21301],{"class":685},[679,84829,12768],{"class":685},[679,84831,84577],{"class":693},[679,84833,84834,84836,84838],{"class":681,"line":790},[679,84835,21301],{"class":685},[679,84837,12768],{"class":685},[679,84839,84840],{"class":693}," Faker faker;\n",[679,84842,84843],{"class":681,"line":892},[679,84844,889],{"emptyLinePlaceholder":797},[679,84846,84847,84849,84851],{"class":681,"line":901},[679,84848,6073],{"class":685},[679,84850,84541],{"class":880},[679,84852,84853],{"class":693},"(PersonRepository repository) {\n",[679,84855,84856,84858,84860,84862],{"class":681,"line":909},[679,84857,27825],{"class":931},[679,84859,16605],{"class":693},[679,84861,686],{"class":685},[679,84863,16610],{"class":693},[679,84865,84866,84868,84871,84873,84875,84878],{"class":681,"line":918},[679,84867,27825],{"class":931},[679,84869,84870],{"class":693},".faker ",[679,84872,686],{"class":685},[679,84874,2054],{"class":685},[679,84876,84877],{"class":880}," Faker",[679,84879,9317],{"class":693},[679,84881,84882],{"class":681,"line":935},[679,84883,996],{"class":693},[679,84885,84886],{"class":681,"line":944},[679,84887,889],{"emptyLinePlaceholder":797},[679,84889,84890,84892],{"class":681,"line":959},[679,84891,4116],{"class":693},[679,84893,10723],{"class":685},[679,84895,84896,84898,84900,84902],{"class":681,"line":964},[679,84897,6073],{"class":685},[679,84899,6095],{"class":685},[679,84901,16486],{"class":880},[679,84903,84703],{"class":693},[679,84905,84906],{"class":681,"line":977},[679,84907,889],{"emptyLinePlaceholder":797},[679,84909,84910],{"class":681,"line":982},[679,84911,84912],{"class":1400}," // create 100 rows of people in the database\n",[679,84914,84915,84918,84920,84923,84925,84928,84931,84933,84935,84937,84939],{"class":681,"line":988},[679,84916,84917],{"class":693}," List\u003C",[679,84919,83905],{"class":685},[679,84921,84922],{"class":693},"> people ",[679,84924,686],{"class":685},[679,84926,84927],{"class":693}," IntStream.",[679,84929,84930],{"class":880},"rangeClosed",[679,84932,745],{"class":693},[679,84934,1557],{"class":931},[679,84936,1202],{"class":693},[679,84938,57791],{"class":931},[679,84940,1339],{"class":693},[679,84942,84943,84945,84948,84951,84953,84955,84957],{"class":681,"line":993},[679,84944,40148],{"class":693},[679,84946,84947],{"class":880},"mapToObj",[679,84949,84950],{"class":693},"(i ",[679,84952,16955],{"class":685},[679,84954,2054],{"class":685},[679,84956,83929],{"class":880},[679,84958,21337],{"class":693},[679,84960,84961,84964,84966,84968,84970],{"class":681,"line":2129},[679,84962,84963],{"class":693}," faker.",[679,84965,16334],{"class":880},[679,84967,10541],{"class":693},[679,84969,84060],{"class":880},[679,84971,56208],{"class":693},[679,84973,84974,84976,84978,84980,84982],{"class":681,"line":2140},[679,84975,84963],{"class":693},[679,84977,16334],{"class":880},[679,84979,10541],{"class":693},[679,84981,84065],{"class":880},[679,84983,56208],{"class":693},[679,84985,84986,84988,84990,84992,84995],{"class":681,"line":2145},[679,84987,84963],{"class":693},[679,84989,84070],{"class":880},[679,84991,10541],{"class":693},[679,84993,84994],{"class":880},"cellPhone",[679,84996,56208],{"class":693},[679,84998,84999,85001,85004,85006,85009],{"class":681,"line":2154},[679,85000,84963],{"class":693},[679,85002,85003],{"class":880},"internet",[679,85005,10541],{"class":693},[679,85007,85008],{"class":880},"emailAddress",[679,85010,56208],{"class":693},[679,85012,85013,85016,85018],{"class":681,"line":2159},[679,85014,85015],{"class":685}," new",[679,85017,33669],{"class":880},[679,85019,21337],{"class":693},[679,85021,85022,85025,85027,85029,85032],{"class":681,"line":2164},[679,85023,85024],{"class":693}," faker.",[679,85026,1355],{"class":880},[679,85028,10541],{"class":693},[679,85030,85031],{"class":880},"streetAddress",[679,85033,56208],{"class":693},[679,85035,85036,85038,85040,85042,85044],{"class":681,"line":3134},[679,85037,85024],{"class":693},[679,85039,1355],{"class":880},[679,85041,10541],{"class":693},[679,85043,84261],{"class":880},[679,85045,56208],{"class":693},[679,85047,85048,85050,85052,85054,85056],{"class":681,"line":3139},[679,85049,85024],{"class":693},[679,85051,1355],{"class":880},[679,85053,10541],{"class":693},[679,85055,20009],{"class":880},[679,85057,56208],{"class":693},[679,85059,85060,85062,85064,85066,85069],{"class":681,"line":3144},[679,85061,85024],{"class":693},[679,85063,1355],{"class":880},[679,85065,10541],{"class":693},[679,85067,85068],{"class":880},"zipCode",[679,85070,17545],{"class":693},[679,85072,85073],{"class":681,"line":3149},[679,85074,85075],{"class":693}," )\n",[679,85077,85078,85081,85084],{"class":681,"line":3169},[679,85079,85080],{"class":693}," )).",[679,85082,85083],{"class":880},"toList",[679,85085,9317],{"class":693},[679,85087,85088],{"class":681,"line":3185},[679,85089,889],{"emptyLinePlaceholder":797},[679,85091,85092,85094,85096],{"class":681,"line":3194},[679,85093,84779],{"class":693},[679,85095,16716],{"class":880},[679,85097,85098],{"class":693},"(people);\n",[679,85100,85101],{"class":681,"line":3199},[679,85102,996],{"class":693},[651,85104,85105,85106,23212,85108,85110],{},"If you run the application you should see 100 records in the ",[676,85107,83905],{},[676,85109,83908],{}," table. Not only do you have data but you have some pretty valid data that you can also do some filtering and sorting on.",[651,85112,85113],{},[660,85114],{"alt":85115,"src":85116},"100 Rows in the database","/images/blog/2022/05/12/100-people.png",[651,85118,85119],{},"Now that you have some data in the database you can focus on pagination.",[5909,85121,390],{"id":85122},"spring-data-jpa-pagination",[651,85124,85125,85126,85129,85130,23212,85132,85134,85135,85138,85139,664],{},"To get started on the web side create a new ",[676,85127,85128],{},"PersonController"," that is annotated with ",[676,85131,12329],{},[676,85133,22845],{},". In this controller you will create a field of type ",[676,85136,85137],{},"PersonRepsoitory"," and have an instance of the repository autowired for you through constructor injection. If you want to learn why constructor injection is the preferred method for dependency injection in Spring check out ",[812,85140,85142],{"href":23786,"rel":85141},[816],"this video",[669,85144,85146],{"className":4107,"code":85145,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/people\")\npublic class PersonController {\n\n private final PersonRepository repository;\n\n public PersonController(PersonRepository repository) {\n this.repository = repository;\n }\n\n}\n",[676,85147,85148,85154,85167,85178,85182,85190,85194,85206,85216,85220,85224],{"__ignoreMap":674},[679,85149,85150,85152],{"class":681,"line":682},[679,85151,4116],{"class":693},[679,85153,9212],{"class":685},[679,85155,85156,85158,85160,85162,85165],{"class":681,"line":790},[679,85157,4116],{"class":693},[679,85159,9275],{"class":685},[679,85161,745],{"class":693},[679,85163,85164],{"class":689},"\"/api/people\"",[679,85166,1339],{"class":693},[679,85168,85169,85171,85173,85176],{"class":681,"line":892},[679,85170,6073],{"class":685},[679,85172,4512],{"class":685},[679,85174,85175],{"class":880}," PersonController",[679,85177,884],{"class":693},[679,85179,85180],{"class":681,"line":901},[679,85181,889],{"emptyLinePlaceholder":797},[679,85183,85184,85186,85188],{"class":681,"line":909},[679,85185,9232],{"class":685},[679,85187,12768],{"class":685},[679,85189,84577],{"class":693},[679,85191,85192],{"class":681,"line":918},[679,85193,889],{"emptyLinePlaceholder":797},[679,85195,85196,85198,85200,85202,85204],{"class":681,"line":935},[679,85197,6089],{"class":685},[679,85199,85175],{"class":880},[679,85201,84590],{"class":693},[679,85203,16596],{"class":2099},[679,85205,4390],{"class":693},[679,85207,85208,85210,85212,85214],{"class":681,"line":944},[679,85209,7862],{"class":931},[679,85211,16605],{"class":693},[679,85213,686],{"class":685},[679,85215,16610],{"class":693},[679,85217,85218],{"class":681,"line":959},[679,85219,985],{"class":693},[679,85221,85222],{"class":681,"line":964},[679,85223,889],{"emptyLinePlaceholder":797},[679,85225,85226],{"class":681,"line":977},[679,85227,996],{"class":693},[651,85229,85230,85231,85233,85234,85237,85238,664],{},"If you take a look at the ",[676,85232,84391],{}," you will see a method called ",[676,85235,85236],{},"findAll()"," that takes an argument of type ",[676,85239,85240],{},"Pageable",[669,85242,85244],{"className":4107,"code":85243,"language":4109,"meta":674,"style":674},"public interface PagingAndSortingRepository\u003CT, ID> extends CrudRepository\u003CT, ID> {\n\n /**\n * Returns all entities sorted by the given options.\n *\n * @param sort the {@link Sort} specification to sort the results by,\n * can be {@link Sort#unsorted()}, must not be {@literal null}.\n * @return all entities sorted by the given options\n */\n Iterable\u003CT> findAll(Sort sort);\n\n /**\n * Returns a {@link Page} of entities meeting the paging restriction provided in\n * the {@link Pageable} object.\n *\n * @param pageable the pageable to request a paged result, can be\n * {@link Pageable#unpaged()}, must not be {@literal null}.\n * @return a page of entities\n */\n Page\u003CT> findAll(Pageable pageable);\n}\n",[676,85245,85246,85278,85282,85286,85291,85296,85308,85328,85338,85342,85360,85364,85368,85373,85378,85382,85394,85411,85420,85424,85443],{"__ignoreMap":674},[679,85247,85248,85250,85252,85254,85256,85258,85260,85262,85264,85266,85268,85270,85272,85274,85276],{"class":681,"line":682},[679,85249,6073],{"class":685},[679,85251,6994],{"class":685},[679,85253,84460],{"class":880},[679,85255,4505],{"class":693},[679,85257,8482],{"class":685},[679,85259,2797],{"class":693},[679,85261,22662],{"class":685},[679,85263,20881],{"class":693},[679,85265,8809],{"class":685},[679,85267,16385],{"class":880},[679,85269,4505],{"class":693},[679,85271,8482],{"class":685},[679,85273,2797],{"class":693},[679,85275,22662],{"class":685},[679,85277,16397],{"class":693},[679,85279,85280],{"class":681,"line":790},[679,85281,889],{"emptyLinePlaceholder":797},[679,85283,85284],{"class":681,"line":892},[679,85285,16789],{"class":1400},[679,85287,85288],{"class":681,"line":901},[679,85289,85290],{"class":1400}," * Returns all entities sorted by the given options.\n",[679,85292,85293],{"class":681,"line":909},[679,85294,85295],{"class":1400}," *\n",[679,85297,85298,85300,85302,85305],{"class":681,"line":918},[679,85299,16799],{"class":1400},[679,85301,16802],{"class":685},[679,85303,85304],{"class":2099}," sort",[679,85306,85307],{"class":1400}," the {@link Sort} specification to sort the results by,\n",[679,85309,85310,85313,85316,85319,85322,85325],{"class":681,"line":935},[679,85311,85312],{"class":1400}," * can be {",[679,85314,85315],{"class":685},"@link",[679,85317,85318],{"class":880}," Sort",[679,85320,85321],{"class":1400},"#",[679,85323,85324],{"class":2099},"unsorted()",[679,85326,85327],{"class":1400},"}, must not be {@literal null}.\n",[679,85329,85330,85332,85335],{"class":681,"line":944},[679,85331,16799],{"class":1400},[679,85333,85334],{"class":685},"@return",[679,85336,85337],{"class":1400}," all entities sorted by the given options\n",[679,85339,85340],{"class":681,"line":959},[679,85341,16826],{"class":1400},[679,85343,85344,85347,85349,85351,85353,85356,85358],{"class":681,"line":964},[679,85345,85346],{"class":693}," Iterable\u003C",[679,85348,8482],{"class":685},[679,85350,20881],{"class":693},[679,85352,34142],{"class":880},[679,85354,85355],{"class":693},"(Sort ",[679,85357,51928],{"class":2099},[679,85359,1208],{"class":693},[679,85361,85362],{"class":681,"line":977},[679,85363,889],{"emptyLinePlaceholder":797},[679,85365,85366],{"class":681,"line":982},[679,85367,16789],{"class":1400},[679,85369,85370],{"class":681,"line":988},[679,85371,85372],{"class":1400}," * Returns a {@link Page} of entities meeting the paging restriction provided in\n",[679,85374,85375],{"class":681,"line":993},[679,85376,85377],{"class":1400}," * the {@link Pageable} object.\n",[679,85379,85380],{"class":681,"line":2129},[679,85381,85295],{"class":1400},[679,85383,85384,85386,85388,85391],{"class":681,"line":2140},[679,85385,16799],{"class":1400},[679,85387,16802],{"class":685},[679,85389,85390],{"class":2099}," pageable",[679,85392,85393],{"class":1400}," the pageable to request a paged result, can be\n",[679,85395,85396,85399,85401,85404,85406,85409],{"class":681,"line":2145},[679,85397,85398],{"class":1400}," * {",[679,85400,85315],{"class":685},[679,85402,85403],{"class":880}," Pageable",[679,85405,85321],{"class":1400},[679,85407,85408],{"class":2099},"unpaged()",[679,85410,85327],{"class":1400},[679,85412,85413,85415,85417],{"class":681,"line":2154},[679,85414,16799],{"class":1400},[679,85416,85334],{"class":685},[679,85418,85419],{"class":1400}," a page of entities\n",[679,85421,85422],{"class":681,"line":2159},[679,85423,16826],{"class":1400},[679,85425,85426,85429,85431,85433,85435,85438,85441],{"class":681,"line":2164},[679,85427,85428],{"class":693}," Page\u003C",[679,85430,8482],{"class":685},[679,85432,20881],{"class":693},[679,85434,34142],{"class":880},[679,85436,85437],{"class":693},"(Pageable ",[679,85439,85440],{"class":2099},"pageable",[679,85442,1208],{"class":693},[679,85444,85445],{"class":681,"line":3134},[679,85446,996],{"class":693},[651,85448,85449,85450,85455,85456,85459],{},"Pageable is actually an interface but there is an implementation of that interface that we can use called ",[812,85451,85454],{"href":85452,"rel":85453},"https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/PageRequest.html",[816],"PageRequest",". If you look at the API there is a static method ",[676,85457,85458],{},"of(int page, int size)"," that takes the page and size of the request.",[651,85461,85462,85463,85466],{},"The page is the page number which starts at 0 and the size the number of records to display per page. This means that if you want the first 10 records you can call ",[676,85464,85465],{},"PageRequest.of(0,10)",". With that information you should have what you need to create an endpoint that allows the consumer to provide those values.",[669,85468,85470],{"className":4107,"code":85469,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/people\")\npublic class PersonController {\n\n private final PersonRepository repository;\n\n public PersonController(PersonRepository repository) {\n this.repository = repository;\n }\n\n @GetMapping\n public Page\u003CPerson> findAll(@RequestParam int page, @RequestParam int size) {\n PageRequest pr = PageRequest.of(page,size);\n return repository.findAll(pr);\n }\n\n}\n",[676,85471,85472,85478,85490,85500,85504,85512,85516,85528,85538,85542,85546,85552,85585,85600,85612,85616,85620],{"__ignoreMap":674},[679,85473,85474,85476],{"class":681,"line":682},[679,85475,4116],{"class":693},[679,85477,9212],{"class":685},[679,85479,85480,85482,85484,85486,85488],{"class":681,"line":790},[679,85481,4116],{"class":693},[679,85483,9275],{"class":685},[679,85485,745],{"class":693},[679,85487,85164],{"class":689},[679,85489,1339],{"class":693},[679,85491,85492,85494,85496,85498],{"class":681,"line":892},[679,85493,6073],{"class":685},[679,85495,4512],{"class":685},[679,85497,85175],{"class":880},[679,85499,884],{"class":693},[679,85501,85502],{"class":681,"line":901},[679,85503,889],{"emptyLinePlaceholder":797},[679,85505,85506,85508,85510],{"class":681,"line":909},[679,85507,9232],{"class":685},[679,85509,12768],{"class":685},[679,85511,84577],{"class":693},[679,85513,85514],{"class":681,"line":918},[679,85515,889],{"emptyLinePlaceholder":797},[679,85517,85518,85520,85522,85524,85526],{"class":681,"line":935},[679,85519,6089],{"class":685},[679,85521,85175],{"class":880},[679,85523,84590],{"class":693},[679,85525,16596],{"class":2099},[679,85527,4390],{"class":693},[679,85529,85530,85532,85534,85536],{"class":681,"line":944},[679,85531,7862],{"class":931},[679,85533,16605],{"class":693},[679,85535,686],{"class":685},[679,85537,16610],{"class":693},[679,85539,85540],{"class":681,"line":959},[679,85541,985],{"class":693},[679,85543,85544],{"class":681,"line":964},[679,85545,889],{"emptyLinePlaceholder":797},[679,85547,85548,85550],{"class":681,"line":977},[679,85549,6872],{"class":693},[679,85551,80415],{"class":685},[679,85553,85554,85556,85559,85561,85563,85565,85567,85569,85571,85573,85576,85578,85580,85583],{"class":681,"line":982},[679,85555,6089],{"class":685},[679,85557,85558],{"class":693}," Page\u003C",[679,85560,83905],{"class":685},[679,85562,20881],{"class":693},[679,85564,34142],{"class":880},[679,85566,73246],{"class":693},[679,85568,73249],{"class":685},[679,85570,14925],{"class":685},[679,85572,72060],{"class":2099},[679,85574,85575],{"class":693},", @",[679,85577,73249],{"class":685},[679,85579,14925],{"class":685},[679,85581,85582],{"class":2099}," size",[679,85584,4390],{"class":693},[679,85586,85587,85590,85592,85595,85597],{"class":681,"line":988},[679,85588,85589],{"class":693}," PageRequest pr ",[679,85591,686],{"class":685},[679,85593,85594],{"class":693}," PageRequest.",[679,85596,16672],{"class":880},[679,85598,85599],{"class":693},"(page,size);\n",[679,85601,85602,85604,85607,85609],{"class":681,"line":993},[679,85603,9444],{"class":685},[679,85605,85606],{"class":693}," repository.",[679,85608,34142],{"class":880},[679,85610,85611],{"class":693},"(pr);\n",[679,85613,85614],{"class":681,"line":2129},[679,85615,985],{"class":693},[679,85617,85618],{"class":681,"line":2140},[679,85619,889],{"emptyLinePlaceholder":797},[679,85621,85622],{"class":681,"line":2145},[679,85623,996],{"class":693},[651,85625,85626,85627,85631],{},"If you run the application and visit ",[812,85628,85629],{"href":85629,"rel":85630},"http://localhost:8080/api/people?page=0&size=3",[816]," you should see 3 records displayed. If you change the page request parameter to 1 you should see the next 3 people in the database.",[651,85633,85634,85635,85638],{},"If you notice the return type of the endpoint it is a ",[676,85636,85637],{},"Page\u003CPerson>",". At this point it would serve you well to inspect the JSON returned from the API. There is a lot of data in there that might be useful to the client calling your API.",[669,85640,85642],{"className":4107,"code":85641,"language":4109,"meta":674,"style":674},"{\n \"content\": [\n {\n \"id\": 1,\n \"firstName\": \"Britany\",\n \"lastName\": \"Pacocha\",\n \"phoneNumber\": \"287-165-0052\",\n \"email\": \"andera.prohaska@hotmail.com\",\n \"address\": {\n \"id\": 2,\n \"address\": \"4638 Elmer Turnpike\",\n \"city\": \"Mannfort\",\n \"state\": \"Mississippi\",\n \"zip\": \"08278\"\n }\n },\n {\n \"id\": 3,\n \"firstName\": \"Darrick\",\n \"lastName\": \"Koepp\",\n \"phoneNumber\": \"900.079.5978\",\n \"email\": \"gigi.langworth@gmail.com\",\n \"address\": {\n \"id\": 4,\n \"address\": \"77288 Darleen Isle\",\n \"city\": \"Walshstad\",\n \"state\": \"Washington\",\n \"zip\": \"62580\"\n }\n },\n {\n \"id\": 5,\n \"firstName\": \"Kristal\",\n \"lastName\": \"Simonis\",\n \"phoneNumber\": \"(599) 177-7520\",\n \"email\": \"jasmine.kozey@yahoo.com\",\n \"address\": {\n \"id\": 6,\n \"address\": \"7532 Kirlin Glens\",\n \"city\": \"North Lauren\",\n \"state\": \"New Mexico\",\n \"zip\": \"09585\"\n }\n }\n ],\n \"pageable\": {\n \"sort\": {\n \"empty\": true,\n \"sorted\": false,\n \"unsorted\": true\n },\n \"offset\": 0,\n \"pageNumber\": 0,\n \"pageSize\": 3,\n \"paged\": true,\n \"unpaged\": false\n },\n \"last\": false,\n \"totalPages\": 34,\n \"totalElements\": 100,\n \"first\": true,\n \"size\": 3,\n \"number\": 0,\n \"sort\": {\n \"empty\": true,\n \"sorted\": false,\n \"unsorted\": true\n },\n \"numberOfElements\": 3,\n \"empty\": false\n}\n",[676,85643,85644,85648,85657,85661,85672,85684,85696,85708,85720,85729,85740,85752,85764,85776,85786,85790,85794,85798,85808,85819,85830,85841,85852,85860,85870,85881,85892,85903,85912,85916,85920,85924,85934,85945,85956,85967,85978,85986,85997,86008,86019,86030,86039,86043,86047,86051,86060,86069,86080,86091,86100,86104,86115,86126,86137,86148,86157,86161,86172,86184,86196,86207,86218,86229,86238,86249,86260,86269,86273,86284,86293],{"__ignoreMap":674},[679,85645,85646],{"class":681,"line":682},[679,85647,28448],{"class":693},[679,85649,85650,85653,85655],{"class":681,"line":790},[679,85651,85652],{"class":689}," \"content\"",[679,85654,2391],{"class":685},[679,85656,44671],{"class":693},[679,85658,85659],{"class":681,"line":892},[679,85660,28496],{"class":693},[679,85662,85663,85666,85668,85670],{"class":681,"line":901},[679,85664,85665],{"class":689}," \"id\"",[679,85667,2391],{"class":685},[679,85669,48606],{"class":931},[679,85671,12083],{"class":693},[679,85673,85674,85677,85679,85682],{"class":681,"line":909},[679,85675,85676],{"class":689}," \"firstName\"",[679,85678,2391],{"class":685},[679,85680,85681],{"class":689}," \"Britany\"",[679,85683,12083],{"class":693},[679,85685,85686,85689,85691,85694],{"class":681,"line":918},[679,85687,85688],{"class":689}," \"lastName\"",[679,85690,2391],{"class":685},[679,85692,85693],{"class":689}," \"Pacocha\"",[679,85695,12083],{"class":693},[679,85697,85698,85701,85703,85706],{"class":681,"line":935},[679,85699,85700],{"class":689}," \"phoneNumber\"",[679,85702,2391],{"class":685},[679,85704,85705],{"class":689}," \"287-165-0052\"",[679,85707,12083],{"class":693},[679,85709,85710,85713,85715,85718],{"class":681,"line":944},[679,85711,85712],{"class":689}," \"email\"",[679,85714,2391],{"class":685},[679,85716,85717],{"class":689}," \"andera.prohaska@hotmail.com\"",[679,85719,12083],{"class":693},[679,85721,85722,85725,85727],{"class":681,"line":959},[679,85723,85724],{"class":689}," \"address\"",[679,85726,2391],{"class":685},[679,85728,884],{"class":693},[679,85730,85731,85734,85736,85738],{"class":681,"line":964},[679,85732,85733],{"class":689}," \"id\"",[679,85735,2391],{"class":685},[679,85737,21871],{"class":931},[679,85739,12083],{"class":693},[679,85741,85742,85745,85747,85750],{"class":681,"line":977},[679,85743,85744],{"class":689}," \"address\"",[679,85746,2391],{"class":685},[679,85748,85749],{"class":689}," \"4638 Elmer Turnpike\"",[679,85751,12083],{"class":693},[679,85753,85754,85757,85759,85762],{"class":681,"line":982},[679,85755,85756],{"class":689}," \"city\"",[679,85758,2391],{"class":685},[679,85760,85761],{"class":689}," \"Mannfort\"",[679,85763,12083],{"class":693},[679,85765,85766,85769,85771,85774],{"class":681,"line":988},[679,85767,85768],{"class":689}," \"state\"",[679,85770,2391],{"class":685},[679,85772,85773],{"class":689}," \"Mississippi\"",[679,85775,12083],{"class":693},[679,85777,85778,85781,85783],{"class":681,"line":993},[679,85779,85780],{"class":689}," \"zip\"",[679,85782,2391],{"class":685},[679,85784,85785],{"class":689}," \"08278\"\n",[679,85787,85788],{"class":681,"line":2129},[679,85789,11804],{"class":693},[679,85791,85792],{"class":681,"line":2140},[679,85793,28763],{"class":693},[679,85795,85796],{"class":681,"line":2145},[679,85797,28496],{"class":693},[679,85799,85800,85802,85804,85806],{"class":681,"line":2154},[679,85801,85665],{"class":689},[679,85803,2391],{"class":685},[679,85805,55460],{"class":931},[679,85807,12083],{"class":693},[679,85809,85810,85812,85814,85817],{"class":681,"line":2159},[679,85811,85676],{"class":689},[679,85813,2391],{"class":685},[679,85815,85816],{"class":689}," \"Darrick\"",[679,85818,12083],{"class":693},[679,85820,85821,85823,85825,85828],{"class":681,"line":2164},[679,85822,85688],{"class":689},[679,85824,2391],{"class":685},[679,85826,85827],{"class":689}," \"Koepp\"",[679,85829,12083],{"class":693},[679,85831,85832,85834,85836,85839],{"class":681,"line":3134},[679,85833,85700],{"class":689},[679,85835,2391],{"class":685},[679,85837,85838],{"class":689}," \"900.079.5978\"",[679,85840,12083],{"class":693},[679,85842,85843,85845,85847,85850],{"class":681,"line":3139},[679,85844,85712],{"class":689},[679,85846,2391],{"class":685},[679,85848,85849],{"class":689}," \"gigi.langworth@gmail.com\"",[679,85851,12083],{"class":693},[679,85853,85854,85856,85858],{"class":681,"line":3144},[679,85855,85724],{"class":689},[679,85857,2391],{"class":685},[679,85859,884],{"class":693},[679,85861,85862,85864,85866,85868],{"class":681,"line":3149},[679,85863,85733],{"class":689},[679,85865,2391],{"class":685},[679,85867,68113],{"class":931},[679,85869,12083],{"class":693},[679,85871,85872,85874,85876,85879],{"class":681,"line":3169},[679,85873,85744],{"class":689},[679,85875,2391],{"class":685},[679,85877,85878],{"class":689}," \"77288 Darleen Isle\"",[679,85880,12083],{"class":693},[679,85882,85883,85885,85887,85890],{"class":681,"line":3185},[679,85884,85756],{"class":689},[679,85886,2391],{"class":685},[679,85888,85889],{"class":689}," \"Walshstad\"",[679,85891,12083],{"class":693},[679,85893,85894,85896,85898,85901],{"class":681,"line":3194},[679,85895,85768],{"class":689},[679,85897,2391],{"class":685},[679,85899,85900],{"class":689}," \"Washington\"",[679,85902,12083],{"class":693},[679,85904,85905,85907,85909],{"class":681,"line":3199},[679,85906,85780],{"class":689},[679,85908,2391],{"class":685},[679,85910,85911],{"class":689}," \"62580\"\n",[679,85913,85914],{"class":681,"line":3212},[679,85915,11804],{"class":693},[679,85917,85918],{"class":681,"line":3217},[679,85919,28763],{"class":693},[679,85921,85922],{"class":681,"line":3222},[679,85923,28496],{"class":693},[679,85925,85926,85928,85930,85932],{"class":681,"line":3227},[679,85927,85665],{"class":689},[679,85929,2391],{"class":685},[679,85931,68046],{"class":931},[679,85933,12083],{"class":693},[679,85935,85936,85938,85940,85943],{"class":681,"line":3232},[679,85937,85676],{"class":689},[679,85939,2391],{"class":685},[679,85941,85942],{"class":689}," \"Kristal\"",[679,85944,12083],{"class":693},[679,85946,85947,85949,85951,85954],{"class":681,"line":3499},[679,85948,85688],{"class":689},[679,85950,2391],{"class":685},[679,85952,85953],{"class":689}," \"Simonis\"",[679,85955,12083],{"class":693},[679,85957,85958,85960,85962,85965],{"class":681,"line":3509},[679,85959,85700],{"class":689},[679,85961,2391],{"class":685},[679,85963,85964],{"class":689}," \"(599) 177-7520\"",[679,85966,12083],{"class":693},[679,85968,85969,85971,85973,85976],{"class":681,"line":3516},[679,85970,85712],{"class":689},[679,85972,2391],{"class":685},[679,85974,85975],{"class":689}," \"jasmine.kozey@yahoo.com\"",[679,85977,12083],{"class":693},[679,85979,85980,85982,85984],{"class":681,"line":3531},[679,85981,85724],{"class":689},[679,85983,2391],{"class":685},[679,85985,884],{"class":693},[679,85987,85988,85990,85992,85995],{"class":681,"line":3536},[679,85989,85733],{"class":689},[679,85991,2391],{"class":685},[679,85993,85994],{"class":931}," 6",[679,85996,12083],{"class":693},[679,85998,85999,86001,86003,86006],{"class":681,"line":3541},[679,86000,85744],{"class":689},[679,86002,2391],{"class":685},[679,86004,86005],{"class":689}," \"7532 Kirlin Glens\"",[679,86007,12083],{"class":693},[679,86009,86010,86012,86014,86017],{"class":681,"line":3546},[679,86011,85756],{"class":689},[679,86013,2391],{"class":685},[679,86015,86016],{"class":689}," \"North Lauren\"",[679,86018,12083],{"class":693},[679,86020,86021,86023,86025,86028],{"class":681,"line":3551},[679,86022,85768],{"class":689},[679,86024,2391],{"class":685},[679,86026,86027],{"class":689}," \"New Mexico\"",[679,86029,12083],{"class":693},[679,86031,86032,86034,86036],{"class":681,"line":3557},[679,86033,85780],{"class":689},[679,86035,2391],{"class":685},[679,86037,86038],{"class":689}," \"09585\"\n",[679,86040,86041],{"class":681,"line":3567},[679,86042,11804],{"class":693},[679,86044,86045],{"class":681,"line":3574},[679,86046,985],{"class":693},[679,86048,86049],{"class":681,"line":3589},[679,86050,28705],{"class":693},[679,86052,86053,86056,86058],{"class":681,"line":3594},[679,86054,86055],{"class":689}," \"pageable\"",[679,86057,2391],{"class":685},[679,86059,884],{"class":693},[679,86061,86062,86065,86067],{"class":681,"line":3602},[679,86063,86064],{"class":689}," \"sort\"",[679,86066,2391],{"class":685},[679,86068,884],{"class":693},[679,86070,86071,86074,86076,86078],{"class":681,"line":3608},[679,86072,86073],{"class":689}," \"empty\"",[679,86075,2391],{"class":685},[679,86077,14523],{"class":931},[679,86079,12083],{"class":693},[679,86081,86082,86085,86087,86089],{"class":681,"line":3619},[679,86083,86084],{"class":689}," \"sorted\"",[679,86086,2391],{"class":685},[679,86088,14559],{"class":931},[679,86090,12083],{"class":693},[679,86092,86093,86096,86098],{"class":681,"line":3624},[679,86094,86095],{"class":689}," \"unsorted\"",[679,86097,2391],{"class":685},[679,86099,2582],{"class":931},[679,86101,86102],{"class":681,"line":3629},[679,86103,28763],{"class":693},[679,86105,86106,86109,86111,86113],{"class":681,"line":3639},[679,86107,86108],{"class":689}," \"offset\"",[679,86110,2391],{"class":685},[679,86112,14987],{"class":931},[679,86114,12083],{"class":693},[679,86116,86117,86120,86122,86124],{"class":681,"line":3644},[679,86118,86119],{"class":689}," \"pageNumber\"",[679,86121,2391],{"class":685},[679,86123,14987],{"class":931},[679,86125,12083],{"class":693},[679,86127,86128,86131,86133,86135],{"class":681,"line":3649},[679,86129,86130],{"class":689}," \"pageSize\"",[679,86132,2391],{"class":685},[679,86134,55460],{"class":931},[679,86136,12083],{"class":693},[679,86138,86139,86142,86144,86146],{"class":681,"line":3659},[679,86140,86141],{"class":689}," \"paged\"",[679,86143,2391],{"class":685},[679,86145,14523],{"class":931},[679,86147,12083],{"class":693},[679,86149,86150,86153,86155],{"class":681,"line":3664},[679,86151,86152],{"class":689}," \"unpaged\"",[679,86154,2391],{"class":685},[679,86156,5076],{"class":931},[679,86158,86159],{"class":681,"line":3669},[679,86160,28483],{"class":693},[679,86162,86163,86166,86168,86170],{"class":681,"line":3679},[679,86164,86165],{"class":689}," \"last\"",[679,86167,2391],{"class":685},[679,86169,14559],{"class":931},[679,86171,12083],{"class":693},[679,86173,86174,86177,86179,86182],{"class":681,"line":3684},[679,86175,86176],{"class":689}," \"totalPages\"",[679,86178,2391],{"class":685},[679,86180,86181],{"class":931}," 34",[679,86183,12083],{"class":693},[679,86185,86186,86189,86191,86194],{"class":681,"line":3689},[679,86187,86188],{"class":689}," \"totalElements\"",[679,86190,2391],{"class":685},[679,86192,86193],{"class":931}," 100",[679,86195,12083],{"class":693},[679,86197,86198,86201,86203,86205],{"class":681,"line":3699},[679,86199,86200],{"class":689}," \"first\"",[679,86202,2391],{"class":685},[679,86204,14523],{"class":931},[679,86206,12083],{"class":693},[679,86208,86209,86212,86214,86216],{"class":681,"line":3704},[679,86210,86211],{"class":689}," \"size\"",[679,86213,2391],{"class":685},[679,86215,55460],{"class":931},[679,86217,12083],{"class":693},[679,86219,86220,86223,86225,86227],{"class":681,"line":3709},[679,86221,86222],{"class":689}," \"number\"",[679,86224,2391],{"class":685},[679,86226,14987],{"class":931},[679,86228,12083],{"class":693},[679,86230,86231,86234,86236],{"class":681,"line":3719},[679,86232,86233],{"class":689}," \"sort\"",[679,86235,2391],{"class":685},[679,86237,884],{"class":693},[679,86239,86240,86243,86245,86247],{"class":681,"line":3724},[679,86241,86242],{"class":689}," \"empty\"",[679,86244,2391],{"class":685},[679,86246,14523],{"class":931},[679,86248,12083],{"class":693},[679,86250,86251,86254,86256,86258],{"class":681,"line":3729},[679,86252,86253],{"class":689}," \"sorted\"",[679,86255,2391],{"class":685},[679,86257,14559],{"class":931},[679,86259,12083],{"class":693},[679,86261,86262,86265,86267],{"class":681,"line":3739},[679,86263,86264],{"class":689}," \"unsorted\"",[679,86266,2391],{"class":685},[679,86268,2582],{"class":931},[679,86270,86271],{"class":681,"line":3744},[679,86272,28483],{"class":693},[679,86274,86275,86278,86280,86282],{"class":681,"line":3749},[679,86276,86277],{"class":689}," \"numberOfElements\"",[679,86279,2391],{"class":685},[679,86281,55460],{"class":931},[679,86283,12083],{"class":693},[679,86285,86286,86289,86291],{"class":681,"line":3759},[679,86287,86288],{"class":689}," \"empty\"",[679,86290,2391],{"class":685},[679,86292,5076],{"class":931},[679,86294,86295],{"class":681,"line":3764},[679,86296,996],{"class":693},[4542,86298,9042],{"id":9041},[651,86300,86301],{},"Congratulations 🥳 You just created an application that uses the Spring Data Paging and Sorting Repository. This allows the client to display a small number of records when working with a large dataset.",[651,86303,86304,86305,86310],{},"If you want to check out the code for this tutorial you can view ",[812,86306,86309],{"href":86307,"rel":86308},"https://github.com/danvega/graphql-paging",[816],"this repo on Github"," which is part of a bigger tutorial I am working on. If you have any questions about what you built today, please let me know. As always friends...",[651,86312,44143,86313,41109],{},[41107,86314],{},[786,86316,86317],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":86319},[86320,86326],{"id":83839,"depth":790,"text":41754,"children":86321},[86322,86323,86324,86325],{"id":84338,"depth":892,"text":84339},{"id":84481,"depth":892,"text":84482},{"id":84794,"depth":892,"text":84795},{"id":85122,"depth":892,"text":390},{"id":9041,"depth":790,"text":9042},"In this tutorial, you are going to learn how to work with pagination in Spring Data JPA.",{"slug":85122,"date":86329,"published":797,"author":798,"tags":86330,"cover":86331,"video":86332,"github":86307},"2022-05-12T16:00:00.000Z",[23816,84347],"./spring-data-jpa-pagination-thumbnail.png","https://www.youtube.com/embed/oq-c3D67WqM",{"title":390,"description":86327},"blog/2022/05/12/spring-data-jpa-pagination","nkhXzxmCxZTcIkEHEweZVk0T7I0oLBPV6wtM4Bv_smw",{"id":86337,"title":387,"body":86338,"description":89129,"extension":793,"meta":89130,"navigation":797,"path":388,"seo":89136,"stem":89137,"__hash__":89138},"content/blog/2022/05/17/spring-for-graphql.md",{"type":648,"value":86339,"toc":89107},[86340,86355,86358,86362,86365,86368,86371,86374,86377,86380,86386,86390,86393,86400,86403,86406,86421,86427,86431,86434,86437,86502,86505,86509,86512,86515,86520,86523,86528,86531,86570,86591,86594,86599,86602,86637,86640,86645,86648,86654,86673,86680,86704,86709,86719,86777,86780,86783,86789,86793,86796,86803,86809,86812,86815,86821,86832,86835,86846,86851,86866,86873,86881,86884,86888,86895,86901,86907,86911,86914,86917,86943,87003,87159,87168,87171,87566,87573,87926,87930,87941,87950,88038,88045,88062,88066,88069,88087,88160,88166,88266,88275,88291,88298,88425,88428,88432,88439,88453,88461,88467,88473,88480,88498,88504,88507,88563,88569,88575,88620,88628,88790,88800,88809,88868,88894,88934,88937,88995,88998,89002,89012,89023,89036,89040,89043,89067,89069,89095,89097,89104],[651,86341,86342,86343,86348,86349,86354],{},"In this tutorial you are going to learn how to get up and running with ",[812,86344,86347],{"href":86345,"rel":86346},"https://spring.io/projects/spring-graphql",[816],"Spring for GraphQL",". At the time of this article being published Spring for GraphQL 1.0 is close to being released. This is a huge milestone and the culmination of a really great collaboration between the Spring team and the ",[812,86350,86353],{"href":86351,"rel":86352},"https://www.graphql-java.com/",[816],"GraphQL Java"," team.",[651,86356,86357],{},"Before you start writing code you should take some time to understand what GraphQL is and why you might reach for it in your next application. By the end of this tutorial you will have what you need to build your first GraphQL API using Spring for GraphQL.",[4542,86359,86361],{"id":86360},"why-graphql","Why GraphQL",[651,86363,86364],{},"There are so many things to learn but your time is valuable and you need to spend it wisely. That said, don’t dive into something just because everyone else is talking about. Let’s discuss why you should spend some of that valuable time learning GraphQL.",[651,86366,86367],{},"If you’re currently building REST APIs you have probably come across this scenario. The API might have started out for a single purpose like feeding data to your frontend client for the web. Even if you stay right there this application might of evolved over time and may have even split into micro frontends. As this application grows you are constantly getting requests for new endpoints that return the data in a specific shape.",[651,86369,86370],{},"The next evolution of this API is when more and more clients have requirements for data. You might have 3rd party services, internal microservices, mobile applications and IoT applications all with their own specific data requirements.",[651,86372,86373],{},"Each of these clients have specific limitations and data requirements. The web application for this application has a ton of real estate on the screen so it can request a lot of data and display it to the user. The web client also doesn’t have to think about constraints like battery life or connection speeds.",[651,86375,86376],{},"When you move to a mobile application you do have to think about things like screen size, battery life and connection speeds. Because of those constraints you might have setup new endpoints for your mobile applications to return smaller data sets.",[651,86378,86379],{},"This is where GraphQL really stands out and provides a solution to a real problem. GraphQL has a single endpoint and lets each individual client request exactly what data they need. Gone are the days of standup up new endpoints each time a new requirement is raised.",[651,86381,86382],{},[660,86383],{"alt":86384,"src":86385},"wes-hicks-4-EeTnaC1S4-unsplash.jpg","/images/blog/2022/05/17/wes-hicks-4-EeTnaC1S4-unsplash.jpg",[4542,86387,86389],{"id":86388},"what-is-graphql","What is GraphQL",[651,86391,86392],{},"Now that you understand why you would reach for GraphQL let’s talk about some of the fundamentals of it. First off GraphQL is a query language for your API, and a server-side runtime for executing queries using a type system you define for your data. It is an alternative to REST & SOAP and in most cases will replace those, not sit alongside them.",[651,86394,86395,86396,86399],{},"GraphQL ",[2939,86397,86398],{},"isn't tied to any specific database or storage engine"," and is instead backed by your existing code and data. This is an important concept to understand so we should start there.",[651,86401,86402],{},"In your existing applications your REST API is also not tied to any specific database. In your Rest Controller endpoints you accept the arguments you need and in turn delegate to a service or repository for the data. GraphQL is no different so if you’re asking questions like “How does GraphQL connect to my database” you’re asking the wrong ones.",[651,86404,86405],{},"GraphQL was created back in 2012 by Facebook. They were facing a lot of the same problems we discussed in the previous section. They had these massive amounts of data sets and mobile was becoming the dominant way users were accessing their application.",[651,86407,86408,86409,86414,86415,86420],{},"GraphQL was open sourced back in 2015 and is now governed by a ",[812,86410,86413],{"href":86411,"rel":86412},"https://graphql.org/foundation/",[816],"neutral foundation"," made up of some really great companies. You will find implementations of the ",[812,86416,86419],{"href":86417,"rel":86418},"https://spec.graphql.org/",[816],"GraphQL Spec"," in almost every language. This means that after you learn it on the Java side you should be able to move over to JavaScript and hit the ground running.",[651,86422,86423],{},[660,86424],{"alt":86425,"src":86426},"alina-grubnyak-ZiQkhI7417A-unsplash.jpg","/images/blog/2022/05/17/alina-grubnyak-ZiQkhI7417A-unsplash.jpg",[5909,86428,86430],{"id":86429},"graphql-client-server","GraphQL Client + Server 🤝",[651,86432,86433],{},"GraphQL is technology for client / server data exchange. The client + server part means there are two sides to this story.",[651,86435,86436],{},"On the client side you’re working with the Query Language. This isn’t SQL, kind of looks like JSON and is something we refer to as a Domain Specific Language (DSL). Like anything there is a little bit of an overhead in learning how to write these queries but once you get the hang of it they are quite intuitive and expressive. This is a query you will write later on to retrieve all of the books along with its author.",[669,86438,86440],{"className":66259,"code":86439,"language":66261,"meta":674,"style":674},"query {\n allBooks {\n title\n pages\n rating {\n star\n }\n author {\n firstName\n lastName\n }\n }\n}\n",[676,86441,86442,86447,86452,86456,86461,86466,86471,86475,86480,86485,86490,86494,86498],{"__ignoreMap":674},[679,86443,86444],{"class":681,"line":682},[679,86445,86446],{},"query {\n",[679,86448,86449],{"class":681,"line":790},[679,86450,86451],{}," allBooks {\n",[679,86453,86454],{"class":681,"line":892},[679,86455,52587],{},[679,86457,86458],{"class":681,"line":901},[679,86459,86460],{}," pages\n",[679,86462,86463],{"class":681,"line":909},[679,86464,86465],{}," rating {\n",[679,86467,86468],{"class":681,"line":918},[679,86469,86470],{}," star\n",[679,86472,86473],{"class":681,"line":935},[679,86474,985],{},[679,86476,86477],{"class":681,"line":944},[679,86478,86479],{}," author {\n",[679,86481,86482],{"class":681,"line":959},[679,86483,86484],{}," firstName\n",[679,86486,86487],{"class":681,"line":964},[679,86488,86489],{}," lastName\n",[679,86491,86492],{"class":681,"line":977},[679,86493,985],{},[679,86495,86496],{"class":681,"line":982},[679,86497,21405],{},[679,86499,86500],{"class":681,"line":988},[679,86501,996],{},[651,86503,86504],{},"On the server side we have an execution engine that will execute these queries mapping each of the fields to resolvers, also known as data fetchers.",[5909,86506,86508],{"id":86507},"graphql-type-system","GraphQL Type System",[651,86510,86511],{},"If you have never seen GraphQL before you can take a good guess at what you’re doing with the query above. You are selecting fields on objects where the root object is a book and inside of that book there is a rating and author type.",[651,86513,86514],{},"Just looking at this query though you aren’t exactly sure what each type is and what fields might be available on the sub-types like rating and author. This is where a schema enters the conversation and is able to describe and validate what fields you can query for.",[651,86516,86517],{},[2939,86518,86519],{},"Type Language",[651,86521,86522],{},"The GraphQL Schema Language allows us to create a consistent schema across languages. This means that if you’re using Java, JavaScript or Go you will understand a GraphQL schema because it is language agnostic. Let’s take a look at some of the types that make up a GraphQL schema.",[651,86524,86525],{},[2939,86526,86527],{},"Object Types and Fields",[651,86529,86530],{},"Object types and fields are a common type that you will work with in any GraphQL Schema. Once you have your models in place in your Spring application you will need to define what objects and fields you want to make available in your API. The following is a schema definition for a Book object:",[669,86532,86534],{"className":66259,"code":86533,"language":66261,"meta":674,"style":674},"type Book {\n id: ID!\n title: String\n pages: Int\n rating: Rating\n author: Author\n}\n",[676,86535,86536,86541,86546,86551,86556,86561,86566],{"__ignoreMap":674},[679,86537,86538],{"class":681,"line":682},[679,86539,86540],{},"type Book {\n",[679,86542,86543],{"class":681,"line":790},[679,86544,86545],{}," id: ID!\n",[679,86547,86548],{"class":681,"line":892},[679,86549,86550],{}," title: String\n",[679,86552,86553],{"class":681,"line":901},[679,86554,86555],{}," pages: Int\n",[679,86557,86558],{"class":681,"line":909},[679,86559,86560],{}," rating: Rating\n",[679,86562,86563],{"class":681,"line":918},[679,86564,86565],{}," author: Author\n",[679,86567,86568],{"class":681,"line":935},[679,86569,996],{},[5316,86571,86572,86575,86586],{},[5332,86573,86574],{},"The values on the left are the field names",[5332,86576,86577,86578],{},"The values on the right are the types\n",[5316,86579,86580,86583],{},[5332,86581,86582],{},"ID, String & Int are built-in Scalar types",[5332,86584,86585],{},"Rating & Author are Object Types that you define",[5332,86587,40060,86588,86590],{},[676,86589,1223],{}," simply tells us that you can always expect a value back and will never need to check for null.",[651,86592,86593],{},"You will learn more about building schemas when you build your first Spring for GraphQL application later in this tutorial.",[651,86595,86596],{},[2939,86597,86598],{},"Scalar Types",[651,86600,86601],{},"As you learned in the previous section there are built-in Scalar types. GraphQL comes with the following types out of the box:",[5316,86603,86604,86610,86615,86620,86629],{},[5332,86605,86606,86609],{},[676,86607,86608],{},"Int",": A signed 32-bit integer.",[5332,86611,86612,86614],{},[676,86613,1105],{},": A signed double-precision floating-point value.",[5332,86616,86617,86619],{},[676,86618,4758],{},": A UTF-8 character sequence.",[5332,86621,86622,29867,86624,86626,86627,664],{},[676,86623,1138],{},[676,86625,3441],{}," or ",[676,86628,1135],{},[5332,86630,86631,86633,86634,86636],{},[676,86632,22662],{},": The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ",[676,86635,22662],{}," signifies that it is not intended to be human-readable.",[651,86638,86639],{},"If you need to define a custom type like a date you can do so but this needs to be configured on the server side.",[651,86641,86642],{},[2939,86643,86644],{},"Root Operation Types",[651,86646,86647],{},"After you have setup your object types you will need a way to read and write data from your API. There are 3 root operation types:",[651,86649,86650],{},[660,86651],{"alt":86652,"src":86653},"root-operation-types.png","/images/blog/2022/05/17/root-operation-types.png",[5316,86655,86656,86661,86667],{},[5332,86657,86658,86660],{},[2939,86659,36099],{},": Used to read data",[5332,86662,86663,86666],{},[2939,86664,86665],{},"Mutation",": Used to create, update and delete data",[5332,86668,86669,86672],{},[2939,86670,86671],{},"Subscription",": Similar to a query allowing you to fetch data from the server. Subscriptions offer a long-lasting operation that can change their result over time.",[651,86674,86675,86676,86679],{},"The following defines the Query operation in our schema. The values on the left are the field names (query names) and the values on the right are the return type. In the first example the brackets around ",[676,86677,86678],{},"Book"," simply means that it could return more than 1.",[669,86681,86683],{"className":66259,"code":86682,"language":66261,"meta":674,"style":674},"type Query {\n allBooks: [Book]\n findOne(id: ID!): Book\n}\n",[676,86684,86685,86690,86695,86700],{"__ignoreMap":674},[679,86686,86687],{"class":681,"line":682},[679,86688,86689],{},"type Query {\n",[679,86691,86692],{"class":681,"line":790},[679,86693,86694],{}," allBooks: [Book]\n",[679,86696,86697],{"class":681,"line":892},[679,86698,86699],{}," findOne(id: ID!): Book\n",[679,86701,86702],{"class":681,"line":901},[679,86703,996],{},[651,86705,86706],{},[2939,86707,86708],{},"Arguments",[651,86710,86711,86712,86715,86716,86718],{},"Each field on a GraphQL object type can have one or more arguments. The ",[676,86713,86714],{},"findOne"," query above defines a single argument of type ID. The ",[676,86717,1223],{}," states that the ID can’t be null and must be supplied. When you write a query against that you can pass in the id like so:",[669,86720,86722],{"className":66259,"code":86721,"language":66261,"meta":674,"style":674},"query {\n findOne(id: 2) {\n title\n pages\n rating {\n star\n }\n author {\n firstName\n lastName\n }\n }\n}\n",[676,86723,86724,86728,86733,86737,86741,86745,86749,86753,86757,86761,86765,86769,86773],{"__ignoreMap":674},[679,86725,86726],{"class":681,"line":682},[679,86727,86446],{},[679,86729,86730],{"class":681,"line":790},[679,86731,86732],{}," findOne(id: 2) {\n",[679,86734,86735],{"class":681,"line":892},[679,86736,52587],{},[679,86738,86739],{"class":681,"line":901},[679,86740,86460],{},[679,86742,86743],{"class":681,"line":909},[679,86744,86465],{},[679,86746,86747],{"class":681,"line":918},[679,86748,86470],{},[679,86750,86751],{"class":681,"line":935},[679,86752,985],{},[679,86754,86755],{"class":681,"line":944},[679,86756,86479],{},[679,86758,86759],{"class":681,"line":959},[679,86760,86484],{},[679,86762,86763],{"class":681,"line":964},[679,86764,86489],{},[679,86766,86767],{"class":681,"line":977},[679,86768,985],{},[679,86770,86771],{"class":681,"line":982},[679,86772,21405],{},[679,86774,86775],{"class":681,"line":988},[679,86776,996],{},[651,86778,86779],{},"There are more types to learn about but they basic types will get you up and running.",[4542,86781,86347],{"id":86782},"spring-for-graphql",[651,86784,86785,86786,86354],{},"Now that you have some background on the what and why of GraphQL it’s time to learn about Spring for GraphQL. As I mentioned earlier this project is result of a really great collaboration between the Spring team and the ",[812,86787,86353],{"href":86351,"rel":86788},[816],[5909,86790,86792],{"id":86791},"graphql-for-java","GraphQL for Java",[651,86794,86795],{},"GraphQL Java was released in 2015 shortly after Facebook open sourced GraphQL. GraphQL Java is the server side execution engine and it does this one thing really well. This means that it doesn’t try and focus on the whole story and it’s limited on purpose.",[651,86797,86798,86799,86802],{},"GraphQL for Java has been tried and tested by some of the biggest companies in the world, including Twitter. Here is a tweet noting that Twitter is running GraphQL for Java in production and serving 500k requests per second. That is insane and tells you the library is ready for the greatest place on the internet... ",[2939,86800,86801],{},"PRODUCTION"," 👏🏻",[651,86804,86805],{},[812,86806,86807],{"href":86807,"rel":86808},"https://twitter.com/graphql_java/status/1450195999373205506?lang=en",[816],[5909,86810,86347],{"id":86811},"spring-for-graphql-1",[651,86813,86814],{},"So GraphQL for Java is the execution engine, why do we need Spring for GraphQL? GraphQL Java Engine itself is only concerned with executing queries. It doesn't deal with any HTTP or JSON related topics. The first thing Spring does is provide server transports in the form of:",[651,86816,86817],{},[660,86818],{"alt":86819,"src":86820},"graphql-server-transports.png","/images/blog/2022/05/17/graphql-server-transports.png",[5316,86822,86823,86826,86829],{},[5332,86824,86825],{},"Http (Spring MVC or Spring Webflux)",[5332,86827,86828],{},"WebSocket",[5332,86830,86831],{},"RSocket",[651,86833,86834],{},"The next feature Spring brings to the table is what it does best. Spring makes the process of integrating with a library seamless and takes away the pain of dependency management, configuration and lets you focus on building out the features of your application.",[651,86836,86837,86838,86841,86842,86845],{},"When you select ",[2939,86839,86840],{},"Spring GraphQL"," as a dependency in the Spring Initializer you will get the ",[676,86843,86844],{},"spring-boot-starter-gaphql"," dependency added to your build. ****",[651,86847,86848],{},[2939,86849,86850],{},"Spring Boot Starter GraphQL",[5316,86852,86853,86856,86859,86861,86863],{},[5332,86854,86855],{},"Dependency Management",[5332,86857,86858],{},"Auto Configuration",[5332,86860,23076],{},[5332,86862,40367],{},[5332,86864,86865],{},"GraphiQL UI and Schema Pages",[651,86867,86868,86869,86872],{},"You will also get testing support out of the box in the form of the ",[676,86870,86871],{},"spring-graphql-test"," starter which provides:",[5316,86874,86875,86878],{},[5332,86876,86877],{},"GraphQL Slice Tests",[5332,86879,86880],{},"GraphQL Tester",[651,86882,86883],{},"Now that you have a good understand of what Spring for GraphQL is its time to write your first application 🤩",[4542,86885,86887],{"id":86886},"build-your-first-graphql-api-with-spring-for-graphql","Build your first GraphQL API with Spring for GraphQL",[651,86889,86890,86891,86894],{},"To build your first GraphQL API head over to ",[812,86892,77478],{"href":30440,"rel":86893},[816]," and while you’re there you might as well bookmark it 😉 Create a new project using Java 17+, Spring Boot 2.7+ and select the following dependencies:",[5316,86896,86897,86899],{},[5332,86898,80827],{},[5332,86900,86840],{},[651,86902,86903],{},[660,86904],{"alt":86905,"src":86906},"graphql-spring-init.png","/images/blog/2022/05/17/graphql-spring-init.png",[5909,86908,86910],{"id":86909},"the-data-layer","The Data Layer",[651,86912,86913],{},"As I mentioned earlier the GraphQL layer isn’t tied to any specific database and is backed by your existing code + data. To demonstrate this you are going to create an application that isn’t tied to any specific database and hold its records in memory.",[651,86915,86916],{},"This application is going to manage your book reading list. To get started you will need to model a Book, Author and Rating object. You could write a normal class with constructors, getters & setters, equals & hash code and a toString or you can save yourself some keystrokes and use a record type.",[669,86918,86920],{"className":4107,"code":86919,"language":4109,"meta":674,"style":674},"public record Book(Integer id, String title, Integer pages, Rating rating, Author author) {\n\n}\n",[676,86921,86922,86935,86939],{"__ignoreMap":674},[679,86923,86924,86926,86929,86932],{"class":681,"line":682},[679,86925,6073],{"class":685},[679,86927,86928],{"class":685}," record",[679,86930,86931],{"class":880}," Book",[679,86933,86934],{"class":693},"(Integer id, String title, Integer pages, Rating rating, Author author) {\n",[679,86936,86937],{"class":681,"line":790},[679,86938,889],{"emptyLinePlaceholder":797},[679,86940,86941],{"class":681,"line":892},[679,86942,996],{"class":693},[669,86944,86946],{"className":4107,"code":86945,"language":4109,"meta":674,"style":674},"public record Author(Integer id, String firstName, String lastName) {\n\n public String fullName() {\n return firstName + \" \" + lastName;\n }\n\n}\n",[676,86947,86948,86960,86964,86975,86991,86995,86999],{"__ignoreMap":674},[679,86949,86950,86952,86954,86957],{"class":681,"line":682},[679,86951,6073],{"class":685},[679,86953,86928],{"class":685},[679,86955,86956],{"class":880}," Author",[679,86958,86959],{"class":693},"(Integer id, String firstName, String lastName) {\n",[679,86961,86962],{"class":681,"line":790},[679,86963,889],{"emptyLinePlaceholder":797},[679,86965,86966,86968,86970,86973],{"class":681,"line":892},[679,86967,6089],{"class":685},[679,86969,9289],{"class":693},[679,86971,86972],{"class":880},"fullName",[679,86974,2667],{"class":693},[679,86976,86977,86979,86982,86984,86987,86989],{"class":681,"line":901},[679,86978,9444],{"class":685},[679,86980,86981],{"class":693}," firstName ",[679,86983,3065],{"class":685},[679,86985,86986],{"class":689}," \" \"",[679,86988,3059],{"class":685},[679,86990,84104],{"class":693},[679,86992,86993],{"class":681,"line":909},[679,86994,985],{"class":693},[679,86996,86997],{"class":681,"line":918},[679,86998,889],{"emptyLinePlaceholder":797},[679,87000,87001],{"class":681,"line":935},[679,87002,996],{"class":693},[669,87004,87006],{"className":4107,"code":87005,"language":4109,"meta":674,"style":674},"public enum Rating {\n FIVE_STARS(\"⭐️⭐️⭐️⭐️⭐️️️️\"),\n FOUR_STARS(\"⭐️⭐️⭐️⭐️\"),\n THREE_STARS(\"⭐️⭐️⭐️\"),\n TWO_STARS(\"⭐️⭐️\"),\n ONE_STAR(\"⭐️\");\n\n private String star;\n\n Rating(String star) {\n this.star = star;\n }\n\n @JsonValue\n public String getStar() {\n return star;\n }\n}\n",[676,87007,87008,87020,87032,87044,87056,87068,87080,87084,87091,87095,87107,87119,87123,87127,87134,87145,87151,87155],{"__ignoreMap":674},[679,87009,87010,87012,87015,87018],{"class":681,"line":682},[679,87011,6073],{"class":685},[679,87013,87014],{"class":685}," enum",[679,87016,87017],{"class":880}," Rating",[679,87019,884],{"class":693},[679,87021,87022,87025,87027,87030],{"class":681,"line":790},[679,87023,87024],{"class":931}," FIVE_STARS",[679,87026,745],{"class":693},[679,87028,87029],{"class":689},"\"⭐️⭐️⭐️⭐️⭐️️️️\"",[679,87031,66689],{"class":693},[679,87033,87034,87037,87039,87042],{"class":681,"line":892},[679,87035,87036],{"class":931}," FOUR_STARS",[679,87038,745],{"class":693},[679,87040,87041],{"class":689},"\"⭐️⭐️⭐️⭐️\"",[679,87043,66689],{"class":693},[679,87045,87046,87049,87051,87054],{"class":681,"line":901},[679,87047,87048],{"class":931}," THREE_STARS",[679,87050,745],{"class":693},[679,87052,87053],{"class":689},"\"⭐️⭐️⭐️\"",[679,87055,66689],{"class":693},[679,87057,87058,87061,87063,87066],{"class":681,"line":909},[679,87059,87060],{"class":931}," TWO_STARS",[679,87062,745],{"class":693},[679,87064,87065],{"class":689},"\"⭐️⭐️\"",[679,87067,66689],{"class":693},[679,87069,87070,87073,87075,87078],{"class":681,"line":918},[679,87071,87072],{"class":931}," ONE_STAR",[679,87074,745],{"class":693},[679,87076,87077],{"class":689},"\"⭐️\"",[679,87079,1208],{"class":693},[679,87081,87082],{"class":681,"line":935},[679,87083,889],{"emptyLinePlaceholder":797},[679,87085,87086,87088],{"class":681,"line":944},[679,87087,9232],{"class":685},[679,87089,87090],{"class":693}," String star;\n",[679,87092,87093],{"class":681,"line":959},[679,87094,889],{"emptyLinePlaceholder":797},[679,87096,87097,87100,87102,87105],{"class":681,"line":964},[679,87098,87099],{"class":880}," Rating",[679,87101,11400],{"class":693},[679,87103,87104],{"class":2099},"star",[679,87106,4390],{"class":693},[679,87108,87109,87111,87114,87116],{"class":681,"line":977},[679,87110,7862],{"class":931},[679,87112,87113],{"class":693},".star ",[679,87115,686],{"class":685},[679,87117,87118],{"class":693}," star;\n",[679,87120,87121],{"class":681,"line":982},[679,87122,985],{"class":693},[679,87124,87125],{"class":681,"line":988},[679,87126,889],{"emptyLinePlaceholder":797},[679,87128,87129,87131],{"class":681,"line":993},[679,87130,6872],{"class":693},[679,87132,87133],{"class":685},"JsonValue\n",[679,87135,87136,87138,87140,87143],{"class":681,"line":2129},[679,87137,6089],{"class":685},[679,87139,9289],{"class":693},[679,87141,87142],{"class":880},"getStar",[679,87144,2667],{"class":693},[679,87146,87147,87149],{"class":681,"line":2140},[679,87148,9444],{"class":685},[679,87150,87118],{"class":693},[679,87152,87153],{"class":681,"line":2145},[679,87154,985],{"class":693},[679,87156,87157],{"class":681,"line":2154},[679,87158,996],{"class":693},[651,87160,87161,87162,87167],{},"Now that you have your data models in place you need a way to store data and retrieve it. You could go the route of Spring Data JPA and I just created ",[812,87163,87166],{"href":87164,"rel":87165},"https://www.danvega.dev/blog/2022/05/12/spring-data-jpa-pagination/",[816],"a tutorial on that here",". To keep this simple and less focused on the data layer you can just store the data in a collection.",[651,87169,87170],{},"Create a book and author repository class using the following code:",[669,87172,87174],{"className":4107,"code":87173,"language":4109,"meta":674,"style":674},"@Repository\npublic class BookRepository {\n\n private final AuthorRepository authorRepository;\n\n private List\u003CBook> books = new ArrayList\u003C>();\n\n public BookRepository(AuthorRepository authorRepository) {\n this.authorRepository = authorRepository;\n }\n\n public List\u003CBook> findAll() {\n return books;\n }\n\n public Book findOne(Integer id) {\n return books.stream()\n .filter(book -> book.id() == id)\n .findFirst().orElseThrow(() -> new RuntimeException(\"Book not found\"));\n }\n\n @PostConstruct\n private void init() {\n books.add(new Book(1,\n \"Reactive Spring\",\n 484,\n Rating.FIVE_STARS,\n authorRepository.findByName(\"Josh Long\")));\n books.add(new Book(2,\n \"Spring Boot Up & Running\",\n 328,\n Rating.FIVE_STARS,\n authorRepository.findByName(\"Mark Heckler\")));\n books.add(new Book(3,\n \"Hacking with Spring Boot 2.3\",\n 392,\n Rating.FIVE_STARS,\n authorRepository.findByName(\"Greg Turnquist\")));\n }\n\n}\n",[676,87175,87176,87183,87194,87198,87207,87211,87230,87234,87249,87261,87265,87269,87283,87290,87294,87298,87314,87326,87349,87377,87381,87385,87392,87402,87421,87428,87435,87440,87456,87474,87481,87488,87492,87505,87523,87530,87537,87541,87554,87558,87562],{"__ignoreMap":674},[679,87177,87178,87180],{"class":681,"line":682},[679,87179,4116],{"class":693},[679,87181,87182],{"class":685},"Repository\n",[679,87184,87185,87187,87189,87192],{"class":681,"line":790},[679,87186,6073],{"class":685},[679,87188,4512],{"class":685},[679,87190,87191],{"class":880}," BookRepository",[679,87193,884],{"class":693},[679,87195,87196],{"class":681,"line":892},[679,87197,889],{"emptyLinePlaceholder":797},[679,87199,87200,87202,87204],{"class":681,"line":901},[679,87201,9232],{"class":685},[679,87203,12768],{"class":685},[679,87205,87206],{"class":693}," AuthorRepository authorRepository;\n",[679,87208,87209],{"class":681,"line":909},[679,87210,889],{"emptyLinePlaceholder":797},[679,87212,87213,87215,87218,87220,87223,87225,87227],{"class":681,"line":918},[679,87214,9232],{"class":685},[679,87216,87217],{"class":693}," List\u003C",[679,87219,86678],{"class":685},[679,87221,87222],{"class":693},"> books ",[679,87224,686],{"class":685},[679,87226,2054],{"class":685},[679,87228,87229],{"class":693}," ArrayList\u003C>();\n",[679,87231,87232],{"class":681,"line":935},[679,87233,889],{"emptyLinePlaceholder":797},[679,87235,87236,87239,87241,87244,87247],{"class":681,"line":944},[679,87237,87238],{"class":685}," public",[679,87240,87191],{"class":880},[679,87242,87243],{"class":693},"(AuthorRepository ",[679,87245,87246],{"class":2099},"authorRepository",[679,87248,4390],{"class":693},[679,87250,87251,87253,87256,87258],{"class":681,"line":959},[679,87252,7862],{"class":931},[679,87254,87255],{"class":693},".authorRepository ",[679,87257,686],{"class":685},[679,87259,87260],{"class":693}," authorRepository;\n",[679,87262,87263],{"class":681,"line":964},[679,87264,985],{"class":693},[679,87266,87267],{"class":681,"line":977},[679,87268,889],{"emptyLinePlaceholder":797},[679,87270,87271,87273,87275,87277,87279,87281],{"class":681,"line":982},[679,87272,6089],{"class":685},[679,87274,87217],{"class":693},[679,87276,86678],{"class":685},[679,87278,20881],{"class":693},[679,87280,34142],{"class":880},[679,87282,2667],{"class":693},[679,87284,87285,87287],{"class":681,"line":988},[679,87286,9444],{"class":685},[679,87288,87289],{"class":693}," books;\n",[679,87291,87292],{"class":681,"line":993},[679,87293,985],{"class":693},[679,87295,87296],{"class":681,"line":2129},[679,87297,889],{"emptyLinePlaceholder":797},[679,87299,87300,87302,87305,87307,87310,87312],{"class":681,"line":2140},[679,87301,6089],{"class":685},[679,87303,87304],{"class":693}," Book ",[679,87306,86714],{"class":880},[679,87308,87309],{"class":693},"(Integer ",[679,87311,11341],{"class":2099},[679,87313,4390],{"class":693},[679,87315,87316,87318,87321,87324],{"class":681,"line":2145},[679,87317,9444],{"class":685},[679,87319,87320],{"class":693}," books.",[679,87322,87323],{"class":880},"stream",[679,87325,17545],{"class":693},[679,87327,87328,87330,87332,87335,87337,87340,87342,87344,87346],{"class":681,"line":2154},[679,87329,73482],{"class":693},[679,87331,65029],{"class":880},[679,87333,87334],{"class":693},"(book ",[679,87336,16955],{"class":685},[679,87338,87339],{"class":693}," book.",[679,87341,11341],{"class":880},[679,87343,6700],{"class":693},[679,87345,2304],{"class":685},[679,87347,87348],{"class":693}," id)\n",[679,87350,87351,87353,87356,87358,87361,87363,87365,87367,87370,87372,87375],{"class":681,"line":2159},[679,87352,73482],{"class":693},[679,87354,87355],{"class":880},"findFirst",[679,87357,10541],{"class":693},[679,87359,87360],{"class":880},"orElseThrow",[679,87362,55186],{"class":693},[679,87364,16955],{"class":685},[679,87366,2054],{"class":685},[679,87368,87369],{"class":880}," RuntimeException",[679,87371,745],{"class":693},[679,87373,87374],{"class":689},"\"Book not found\"",[679,87376,1669],{"class":693},[679,87378,87379],{"class":681,"line":2164},[679,87380,985],{"class":693},[679,87382,87383],{"class":681,"line":3134},[679,87384,889],{"emptyLinePlaceholder":797},[679,87386,87387,87389],{"class":681,"line":3139},[679,87388,6872],{"class":693},[679,87390,87391],{"class":685},"PostConstruct\n",[679,87393,87394,87396,87398,87400],{"class":681,"line":3144},[679,87395,9232],{"class":685},[679,87397,6095],{"class":685},[679,87399,36742],{"class":880},[679,87401,2667],{"class":693},[679,87403,87404,87407,87409,87411,87413,87415,87417,87419],{"class":681,"line":3149},[679,87405,87406],{"class":693}," books.",[679,87408,12952],{"class":880},[679,87410,745],{"class":693},[679,87412,8930],{"class":685},[679,87414,86931],{"class":880},[679,87416,745],{"class":693},[679,87418,1557],{"class":931},[679,87420,12083],{"class":693},[679,87422,87423,87426],{"class":681,"line":3169},[679,87424,87425],{"class":689}," \"Reactive Spring\"",[679,87427,12083],{"class":693},[679,87429,87430,87433],{"class":681,"line":3185},[679,87431,87432],{"class":931}," 484",[679,87434,12083],{"class":693},[679,87436,87437],{"class":681,"line":3194},[679,87438,87439],{"class":693}," Rating.FIVE_STARS,\n",[679,87441,87442,87445,87448,87450,87453],{"class":681,"line":3199},[679,87443,87444],{"class":693}," authorRepository.",[679,87446,87447],{"class":880},"findByName",[679,87449,745],{"class":693},[679,87451,87452],{"class":689},"\"Josh Long\"",[679,87454,87455],{"class":693},")));\n",[679,87457,87458,87460,87462,87464,87466,87468,87470,87472],{"class":681,"line":3212},[679,87459,87406],{"class":693},[679,87461,12952],{"class":880},[679,87463,745],{"class":693},[679,87465,8930],{"class":685},[679,87467,86931],{"class":880},[679,87469,745],{"class":693},[679,87471,17262],{"class":931},[679,87473,12083],{"class":693},[679,87475,87476,87479],{"class":681,"line":3217},[679,87477,87478],{"class":689}," \"Spring Boot Up & Running\"",[679,87480,12083],{"class":693},[679,87482,87483,87486],{"class":681,"line":3222},[679,87484,87485],{"class":931}," 328",[679,87487,12083],{"class":693},[679,87489,87490],{"class":681,"line":3227},[679,87491,87439],{"class":693},[679,87493,87494,87496,87498,87500,87503],{"class":681,"line":3232},[679,87495,87444],{"class":693},[679,87497,87447],{"class":880},[679,87499,745],{"class":693},[679,87501,87502],{"class":689},"\"Mark Heckler\"",[679,87504,87455],{"class":693},[679,87506,87507,87509,87511,87513,87515,87517,87519,87521],{"class":681,"line":3499},[679,87508,87406],{"class":693},[679,87510,12952],{"class":880},[679,87512,745],{"class":693},[679,87514,8930],{"class":685},[679,87516,86931],{"class":880},[679,87518,745],{"class":693},[679,87520,66599],{"class":931},[679,87522,12083],{"class":693},[679,87524,87525,87528],{"class":681,"line":3509},[679,87526,87527],{"class":689}," \"Hacking with Spring Boot 2.3\"",[679,87529,12083],{"class":693},[679,87531,87532,87535],{"class":681,"line":3516},[679,87533,87534],{"class":931}," 392",[679,87536,12083],{"class":693},[679,87538,87539],{"class":681,"line":3531},[679,87540,87439],{"class":693},[679,87542,87543,87545,87547,87549,87552],{"class":681,"line":3536},[679,87544,87444],{"class":693},[679,87546,87447],{"class":880},[679,87548,745],{"class":693},[679,87550,87551],{"class":689},"\"Greg Turnquist\"",[679,87553,87455],{"class":693},[679,87555,87556],{"class":681,"line":3541},[679,87557,985],{"class":693},[679,87559,87560],{"class":681,"line":3546},[679,87561,889],{"emptyLinePlaceholder":797},[679,87563,87564],{"class":681,"line":3551},[679,87565,996],{"class":693},[651,87567,87568,87569,87572],{},"If you’re not aware the ",[676,87570,87571],{},"@PostConstruct"," annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.",[669,87574,87576],{"className":4107,"code":87575,"language":4109,"meta":674,"style":674},"@Repository\npublic class AuthorRepository {\n\n private List\u003CAuthor> authors = new ArrayList\u003C>();\n\n public List\u003CAuthor> findAll() {\n return authors;\n }\n\n public Author findById(int id) {\n return authors.stream()\n .filter(author -> author.id() == id)\n .findFirst()\n .orElseThrow(() -> new RuntimeException(\"Author not found\"));\n }\n\n public Author findByName(String name) {\n return authors.stream()\n .filter(author -> author.fullName().equals(name))\n .findFirst()\n .orElseThrow(() -> new RuntimeException(\"Author not found\"));\n }\n\n @PostConstruct\n private void init() {\n authors.add(new Author(1,\"Josh\",\"Long\"));\n authors.add(new Author(2,\"Mark\",\"Heckler\"));\n authors.add(new Author(3,\"Greg\",\"Turnquist\"));\n }\n}\n",[676,87577,87578,87584,87595,87599,87616,87620,87634,87641,87645,87649,87667,87678,87700,87708,87729,87733,87737,87751,87761,87782,87790,87810,87814,87818,87824,87834,87863,87890,87918,87922],{"__ignoreMap":674},[679,87579,87580,87582],{"class":681,"line":682},[679,87581,4116],{"class":693},[679,87583,87182],{"class":685},[679,87585,87586,87588,87590,87593],{"class":681,"line":790},[679,87587,6073],{"class":685},[679,87589,4512],{"class":685},[679,87591,87592],{"class":880}," AuthorRepository",[679,87594,884],{"class":693},[679,87596,87597],{"class":681,"line":892},[679,87598,889],{"emptyLinePlaceholder":797},[679,87600,87601,87603,87605,87607,87610,87612,87614],{"class":681,"line":901},[679,87602,9232],{"class":685},[679,87604,87217],{"class":693},[679,87606,55316],{"class":685},[679,87608,87609],{"class":693},"> authors ",[679,87611,686],{"class":685},[679,87613,2054],{"class":685},[679,87615,87229],{"class":693},[679,87617,87618],{"class":681,"line":909},[679,87619,889],{"emptyLinePlaceholder":797},[679,87621,87622,87624,87626,87628,87630,87632],{"class":681,"line":918},[679,87623,6089],{"class":685},[679,87625,87217],{"class":693},[679,87627,55316],{"class":685},[679,87629,20881],{"class":693},[679,87631,34142],{"class":880},[679,87633,2667],{"class":693},[679,87635,87636,87638],{"class":681,"line":935},[679,87637,9444],{"class":685},[679,87639,87640],{"class":693}," authors;\n",[679,87642,87643],{"class":681,"line":944},[679,87644,985],{"class":693},[679,87646,87647],{"class":681,"line":959},[679,87648,889],{"emptyLinePlaceholder":797},[679,87650,87651,87653,87656,87659,87661,87663,87665],{"class":681,"line":964},[679,87652,6089],{"class":685},[679,87654,87655],{"class":693}," Author ",[679,87657,87658],{"class":880},"findById",[679,87660,745],{"class":693},[679,87662,1078],{"class":685},[679,87664,5578],{"class":2099},[679,87666,4390],{"class":693},[679,87668,87669,87671,87674,87676],{"class":681,"line":977},[679,87670,9444],{"class":685},[679,87672,87673],{"class":693}," authors.",[679,87675,87323],{"class":880},[679,87677,17545],{"class":693},[679,87679,87680,87682,87684,87687,87689,87692,87694,87696,87698],{"class":681,"line":982},[679,87681,73482],{"class":693},[679,87683,65029],{"class":880},[679,87685,87686],{"class":693},"(author ",[679,87688,16955],{"class":685},[679,87690,87691],{"class":693}," author.",[679,87693,11341],{"class":880},[679,87695,6700],{"class":693},[679,87697,2304],{"class":685},[679,87699,87348],{"class":693},[679,87701,87702,87704,87706],{"class":681,"line":988},[679,87703,73482],{"class":693},[679,87705,87355],{"class":880},[679,87707,17545],{"class":693},[679,87709,87710,87712,87714,87716,87718,87720,87722,87724,87727],{"class":681,"line":993},[679,87711,73482],{"class":693},[679,87713,87360],{"class":880},[679,87715,55186],{"class":693},[679,87717,16955],{"class":685},[679,87719,2054],{"class":685},[679,87721,87369],{"class":880},[679,87723,745],{"class":693},[679,87725,87726],{"class":689},"\"Author not found\"",[679,87728,1669],{"class":693},[679,87730,87731],{"class":681,"line":2129},[679,87732,985],{"class":693},[679,87734,87735],{"class":681,"line":2140},[679,87736,889],{"emptyLinePlaceholder":797},[679,87738,87739,87741,87743,87745,87747,87749],{"class":681,"line":2145},[679,87740,6089],{"class":685},[679,87742,87655],{"class":693},[679,87744,87447],{"class":880},[679,87746,11400],{"class":693},[679,87748,16334],{"class":2099},[679,87750,4390],{"class":693},[679,87752,87753,87755,87757,87759],{"class":681,"line":2154},[679,87754,9444],{"class":685},[679,87756,87673],{"class":693},[679,87758,87323],{"class":880},[679,87760,17545],{"class":693},[679,87762,87763,87765,87767,87769,87771,87773,87775,87777,87779],{"class":681,"line":2159},[679,87764,73482],{"class":693},[679,87766,65029],{"class":880},[679,87768,87686],{"class":693},[679,87770,16955],{"class":685},[679,87772,87691],{"class":693},[679,87774,86972],{"class":880},[679,87776,10541],{"class":693},[679,87778,14592],{"class":880},[679,87780,87781],{"class":693},"(name))\n",[679,87783,87784,87786,87788],{"class":681,"line":2164},[679,87785,73482],{"class":693},[679,87787,87355],{"class":880},[679,87789,17545],{"class":693},[679,87791,87792,87794,87796,87798,87800,87802,87804,87806,87808],{"class":681,"line":3134},[679,87793,73482],{"class":693},[679,87795,87360],{"class":880},[679,87797,55186],{"class":693},[679,87799,16955],{"class":685},[679,87801,2054],{"class":685},[679,87803,87369],{"class":880},[679,87805,745],{"class":693},[679,87807,87726],{"class":689},[679,87809,1669],{"class":693},[679,87811,87812],{"class":681,"line":3139},[679,87813,985],{"class":693},[679,87815,87816],{"class":681,"line":3144},[679,87817,889],{"emptyLinePlaceholder":797},[679,87819,87820,87822],{"class":681,"line":3149},[679,87821,6872],{"class":693},[679,87823,87391],{"class":685},[679,87825,87826,87828,87830,87832],{"class":681,"line":3169},[679,87827,9232],{"class":685},[679,87829,6095],{"class":685},[679,87831,36742],{"class":880},[679,87833,2667],{"class":693},[679,87835,87836,87839,87841,87843,87845,87847,87849,87851,87853,87856,87858,87861],{"class":681,"line":3185},[679,87837,87838],{"class":693}," authors.",[679,87840,12952],{"class":880},[679,87842,745],{"class":693},[679,87844,8930],{"class":685},[679,87846,86956],{"class":880},[679,87848,745],{"class":693},[679,87850,1557],{"class":931},[679,87852,1202],{"class":693},[679,87854,87855],{"class":689},"\"Josh\"",[679,87857,1202],{"class":693},[679,87859,87860],{"class":689},"\"Long\"",[679,87862,1669],{"class":693},[679,87864,87865,87867,87869,87871,87873,87875,87877,87879,87881,87883,87885,87888],{"class":681,"line":3194},[679,87866,87838],{"class":693},[679,87868,12952],{"class":880},[679,87870,745],{"class":693},[679,87872,8930],{"class":685},[679,87874,86956],{"class":880},[679,87876,745],{"class":693},[679,87878,17262],{"class":931},[679,87880,1202],{"class":693},[679,87882,35974],{"class":689},[679,87884,1202],{"class":693},[679,87886,87887],{"class":689},"\"Heckler\"",[679,87889,1669],{"class":693},[679,87891,87892,87894,87896,87898,87900,87902,87904,87906,87908,87911,87913,87916],{"class":681,"line":3199},[679,87893,87838],{"class":693},[679,87895,12952],{"class":880},[679,87897,745],{"class":693},[679,87899,8930],{"class":685},[679,87901,86956],{"class":880},[679,87903,745],{"class":693},[679,87905,66599],{"class":931},[679,87907,1202],{"class":693},[679,87909,87910],{"class":689},"\"Greg\"",[679,87912,1202],{"class":693},[679,87914,87915],{"class":689},"\"Turnquist\"",[679,87917,1669],{"class":693},[679,87919,87920],{"class":681,"line":3212},[679,87921,985],{"class":693},[679,87923,87924],{"class":681,"line":3217},[679,87925,996],{"class":693},[5909,87927,87929],{"id":87928},"graphql-layer","GraphQL Layer",[651,87931,87932,87933,87936,87937,87940],{},"With your data layer in place it’s time to turn your attention to GraphQL. By convention Spring will look for GraphQL Schema’s in the ",[676,87934,87935],{},"/src/main/resources/graphql"," directory. You can configure this location but for this example let’s just use the default by creating a new file ",[676,87938,87939],{},"schema.graphqls"," in that directory.",[651,87942,87943,87944,87949],{},"You will start by defining your object types which look like the model objects you create in Java above. The field names are the left and the types are on the right. There are ",[812,87945,87948],{"href":87946,"rel":87947},"https://graphql.org/learn/schema/",[816],"built-in scalar types"," like ID, String, Int, Float, and Boolean and you can also reference object types that you have created.",[669,87951,87953],{"className":66259,"code":87952,"language":66261,"meta":674,"style":674},"type Book {\n id: ID!\n title: String\n pages: Int\n rating: Rating\n author: Author\n}\n\ntype Rating {\n rating: String\n star: String\n}\n\ntype Author {\n id: ID!\n firstName: String\n lastName: String\n books: [Book]\n}\n",[676,87954,87955,87959,87963,87967,87971,87975,87979,87983,87987,87992,87997,88002,88006,88010,88015,88019,88024,88029,88034],{"__ignoreMap":674},[679,87956,87957],{"class":681,"line":682},[679,87958,86540],{},[679,87960,87961],{"class":681,"line":790},[679,87962,86545],{},[679,87964,87965],{"class":681,"line":892},[679,87966,86550],{},[679,87968,87969],{"class":681,"line":901},[679,87970,86555],{},[679,87972,87973],{"class":681,"line":909},[679,87974,86560],{},[679,87976,87977],{"class":681,"line":918},[679,87978,86565],{},[679,87980,87981],{"class":681,"line":935},[679,87982,996],{},[679,87984,87985],{"class":681,"line":944},[679,87986,889],{"emptyLinePlaceholder":797},[679,87988,87989],{"class":681,"line":959},[679,87990,87991],{},"type Rating {\n",[679,87993,87994],{"class":681,"line":964},[679,87995,87996],{}," rating: String\n",[679,87998,87999],{"class":681,"line":977},[679,88000,88001],{}," star: String\n",[679,88003,88004],{"class":681,"line":982},[679,88005,996],{},[679,88007,88008],{"class":681,"line":988},[679,88009,889],{"emptyLinePlaceholder":797},[679,88011,88012],{"class":681,"line":993},[679,88013,88014],{},"type Author {\n",[679,88016,88017],{"class":681,"line":2129},[679,88018,86545],{},[679,88020,88021],{"class":681,"line":2140},[679,88022,88023],{}," firstName: String\n",[679,88025,88026],{"class":681,"line":2145},[679,88027,88028],{}," lastName: String\n",[679,88030,88031],{"class":681,"line":2154},[679,88032,88033],{}," books: [Book]\n",[679,88035,88036],{"class":681,"line":2159},[679,88037,996],{},[651,88039,88040,88041,88044],{},"Next you will create a root level operation called Query. You will name the query ",[676,88042,88043],{},"allBooks"," and this will return a list of books:",[669,88046,88048],{"className":66259,"code":88047,"language":66261,"meta":674,"style":674},"type Query {\n allBooks: [Book]\n}\n",[676,88049,88050,88054,88058],{"__ignoreMap":674},[679,88051,88052],{"class":681,"line":682},[679,88053,86689],{},[679,88055,88056],{"class":681,"line":790},[679,88057,86694],{},[679,88059,88060],{"class":681,"line":892},[679,88061,996],{},[5909,88063,88065],{"id":88064},"the-web-layer","The Web Layer",[651,88067,88068],{},"With your GraphQL Schema in place you need a transport layer for your API. In other words, how are clients going to access your API? In this example you selected Spring Web (Spring MVC) as a dependency so you can use HTTP.",[651,88070,88071,88072,88075,88076,88078,88079,70945,88082,88086],{},"Create a new controller called ",[676,88073,88074],{},"BookController"," and mark with the ",[676,88077,12300],{}," annotation. You will need a way to retrieve your data so create a new field of type ",[676,88080,88081],{},"BookRepository",[812,88083,88085],{"href":23786,"rel":88084},[816],"constructor injection"," to get an instance of that injected into your controller.",[669,88088,88090],{"className":4107,"code":88089,"language":4109,"meta":674,"style":674},"@Controller\npublic class BookController {\n\n private final BookRepository bookRepository;\n\n public BookController(BookRepository bookRepository) {\n this.bookRepository = bookRepository;\n }\n}\n",[676,88091,88092,88098,88109,88113,88122,88126,88140,88152,88156],{"__ignoreMap":674},[679,88093,88094,88096],{"class":681,"line":682},[679,88095,4116],{"class":693},[679,88097,9942],{"class":685},[679,88099,88100,88102,88104,88107],{"class":681,"line":790},[679,88101,6073],{"class":685},[679,88103,4512],{"class":685},[679,88105,88106],{"class":880}," BookController",[679,88108,884],{"class":693},[679,88110,88111],{"class":681,"line":892},[679,88112,889],{"emptyLinePlaceholder":797},[679,88114,88115,88117,88119],{"class":681,"line":901},[679,88116,9232],{"class":685},[679,88118,12768],{"class":685},[679,88120,88121],{"class":693}," BookRepository bookRepository;\n",[679,88123,88124],{"class":681,"line":909},[679,88125,889],{"emptyLinePlaceholder":797},[679,88127,88128,88130,88132,88135,88138],{"class":681,"line":918},[679,88129,6089],{"class":685},[679,88131,88106],{"class":880},[679,88133,88134],{"class":693},"(BookRepository ",[679,88136,88137],{"class":2099},"bookRepository",[679,88139,4390],{"class":693},[679,88141,88142,88144,88147,88149],{"class":681,"line":935},[679,88143,7862],{"class":931},[679,88145,88146],{"class":693},".bookRepository ",[679,88148,686],{"class":685},[679,88150,88151],{"class":693}," bookRepository;\n",[679,88153,88154],{"class":681,"line":944},[679,88155,985],{"class":693},[679,88157,88158],{"class":681,"line":959},[679,88159,996],{"class":693},[651,88161,88162,88163,88165],{},"Next create a method called ",[676,88164,34142],{}," that will delegate to the repository to return all of the books in the collection:",[669,88167,88169],{"className":4107,"code":88168,"language":4109,"meta":674,"style":674},"@Controller\npublic class BookController {\n\n private final BookRepository bookRepository;\n\n public BookController(BookRepository bookRepository) {\n this.bookRepository = bookRepository;\n }\n\n public List\u003CBook> findAll() {\n return bookRepository.findAll();\n }\n}\n",[676,88170,88171,88177,88187,88191,88199,88203,88215,88225,88229,88233,88247,88258,88262],{"__ignoreMap":674},[679,88172,88173,88175],{"class":681,"line":682},[679,88174,4116],{"class":693},[679,88176,9942],{"class":685},[679,88178,88179,88181,88183,88185],{"class":681,"line":790},[679,88180,6073],{"class":685},[679,88182,4512],{"class":685},[679,88184,88106],{"class":880},[679,88186,884],{"class":693},[679,88188,88189],{"class":681,"line":892},[679,88190,889],{"emptyLinePlaceholder":797},[679,88192,88193,88195,88197],{"class":681,"line":901},[679,88194,9232],{"class":685},[679,88196,12768],{"class":685},[679,88198,88121],{"class":693},[679,88200,88201],{"class":681,"line":909},[679,88202,889],{"emptyLinePlaceholder":797},[679,88204,88205,88207,88209,88211,88213],{"class":681,"line":918},[679,88206,6089],{"class":685},[679,88208,88106],{"class":880},[679,88210,88134],{"class":693},[679,88212,88137],{"class":2099},[679,88214,4390],{"class":693},[679,88216,88217,88219,88221,88223],{"class":681,"line":935},[679,88218,7862],{"class":931},[679,88220,88146],{"class":693},[679,88222,686],{"class":685},[679,88224,88151],{"class":693},[679,88226,88227],{"class":681,"line":944},[679,88228,985],{"class":693},[679,88230,88231],{"class":681,"line":959},[679,88232,889],{"emptyLinePlaceholder":797},[679,88234,88235,88237,88239,88241,88243,88245],{"class":681,"line":964},[679,88236,6089],{"class":685},[679,88238,87217],{"class":693},[679,88240,86678],{"class":685},[679,88242,20881],{"class":693},[679,88244,34142],{"class":880},[679,88246,2667],{"class":693},[679,88248,88249,88251,88254,88256],{"class":681,"line":977},[679,88250,9444],{"class":685},[679,88252,88253],{"class":693}," bookRepository.",[679,88255,34142],{"class":880},[679,88257,9317],{"class":693},[679,88259,88260],{"class":681,"line":982},[679,88261,985],{"class":693},[679,88263,88264],{"class":681,"line":988},[679,88265,996],{"class":693},[651,88267,88268,88269,88271,88272,88274],{},"With that method in place you need a way to map that method to the GraphQL Schema you created earlier. How can you tell Spring that when the query named ",[676,88270,88043],{}," is called to execute the ",[676,88273,34142],{}," method?",[669,88276,88277],{"className":66259,"code":88047,"language":66261,"meta":674,"style":674},[676,88278,88279,88283,88287],{"__ignoreMap":674},[679,88280,88281],{"class":681,"line":682},[679,88282,86689],{},[679,88284,88285],{"class":681,"line":790},[679,88286,86694],{},[679,88288,88289],{"class":681,"line":892},[679,88290,996],{},[651,88292,88293,88294,88297],{},"There are annotations that are going to help us with the schema mappings. The first one is called ",[676,88295,88296],{},"@SchemaMapping"," and you will need to supply 2 arguments. Type name is the top level operation like Query, Mutation or Subscription. Value is the name of the query as its defined in the GraphQL schema.",[669,88299,88301],{"className":4107,"code":88300,"language":4109,"meta":674,"style":674},"@Controller\npublic class BookController {\n\n private final BookRepository bookRepository;\n\n public BookController(BookRepository bookRepository) {\n this.bookRepository = bookRepository;\n }\n\n @SchemaMapping(typeName = \"Query\",value = \"allBooks\")\n public List\u003CBook> findAll() {\n return bookRepository.findAll();\n }\n}\n",[676,88302,88303,88309,88319,88323,88331,88335,88347,88357,88361,88365,88393,88407,88417,88421],{"__ignoreMap":674},[679,88304,88305,88307],{"class":681,"line":682},[679,88306,4116],{"class":693},[679,88308,9942],{"class":685},[679,88310,88311,88313,88315,88317],{"class":681,"line":790},[679,88312,6073],{"class":685},[679,88314,4512],{"class":685},[679,88316,88106],{"class":880},[679,88318,884],{"class":693},[679,88320,88321],{"class":681,"line":892},[679,88322,889],{"emptyLinePlaceholder":797},[679,88324,88325,88327,88329],{"class":681,"line":901},[679,88326,9232],{"class":685},[679,88328,12768],{"class":685},[679,88330,88121],{"class":693},[679,88332,88333],{"class":681,"line":909},[679,88334,889],{"emptyLinePlaceholder":797},[679,88336,88337,88339,88341,88343,88345],{"class":681,"line":918},[679,88338,6089],{"class":685},[679,88340,88106],{"class":880},[679,88342,88134],{"class":693},[679,88344,88137],{"class":2099},[679,88346,4390],{"class":693},[679,88348,88349,88351,88353,88355],{"class":681,"line":935},[679,88350,7862],{"class":931},[679,88352,88146],{"class":693},[679,88354,686],{"class":685},[679,88356,88151],{"class":693},[679,88358,88359],{"class":681,"line":944},[679,88360,985],{"class":693},[679,88362,88363],{"class":681,"line":959},[679,88364,889],{"emptyLinePlaceholder":797},[679,88366,88367,88369,88372,88374,88377,88379,88382,88384,88386,88388,88391],{"class":681,"line":964},[679,88368,6872],{"class":693},[679,88370,88371],{"class":685},"SchemaMapping",[679,88373,745],{"class":693},[679,88375,88376],{"class":931},"typeName",[679,88378,6883],{"class":685},[679,88380,88381],{"class":689}," \"Query\"",[679,88383,1202],{"class":693},[679,88385,19934],{"class":931},[679,88387,6883],{"class":685},[679,88389,88390],{"class":689}," \"allBooks\"",[679,88392,1339],{"class":693},[679,88394,88395,88397,88399,88401,88403,88405],{"class":681,"line":977},[679,88396,6089],{"class":685},[679,88398,87217],{"class":693},[679,88400,86678],{"class":685},[679,88402,20881],{"class":693},[679,88404,34142],{"class":880},[679,88406,2667],{"class":693},[679,88408,88409,88411,88413,88415],{"class":681,"line":982},[679,88410,9444],{"class":685},[679,88412,88253],{"class":693},[679,88414,34142],{"class":880},[679,88416,9317],{"class":693},[679,88418,88419],{"class":681,"line":988},[679,88420,985],{"class":693},[679,88422,88423],{"class":681,"line":993},[679,88424,996],{"class":693},[651,88426,88427],{},"This is the minimum you need to get a GraphQL API up and running. You need a way to test what you have done so far and you could write some tests but we will look at that later.",[5909,88429,88431],{"id":88430},"graphiql","GraphiQL",[651,88433,88434,88435,88438],{},"GraphiQL is a graphical interactive in-browser GraphQL IDE. This is a great place to learn and test out your GraphQL queries. GraphiQL is disabled by default so if you would like to use it you will need to open up ",[676,88436,88437],{},"[application.properties](http://application.properties)"," and enable it with the following property:",[669,88440,88442],{"className":5851,"code":88441,"language":5853,"meta":674,"style":674},"spring.graphql.graphiql.enabled=true\n",[676,88443,88444],{"__ignoreMap":674},[679,88445,88446,88449,88451],{"class":681,"line":682},[679,88447,88448],{"class":880},"spring.graphql.graphiql.enabled",[679,88450,686],{"class":689},[679,88452,5134],{"class":931},[651,88454,88455,88456,88460],{},"With that in place you can run your application and visit ",[812,88457,88458],{"href":88458,"rel":88459},"http://localhost:8080/graphiql",[816],". The left pane is for writing queries and the right pane will display your results. The first thing you will notice is that you get some help context on how to start writing queries.",[651,88462,88463],{},[660,88464],{"alt":88465,"src":88466},"graphiql.png","/images/blog/2022/05/17/graphiql.png",[651,88468,88469,88470,88472],{},"One of the features I really love about GraphQL is that it’s self documenting. If you look on the far right pane you will see a documentation explorer. The root types (Query, Mutation & Subscription) are listed if you click on the right side ",[676,88471,36099],{}," you will be given a list of queries defined on the schema. Right now you only have one and it returns a collection of books. If you click on Book you can see what fields are available along with their types.",[651,88474,88475,88476,88479],{},"Because of that schema you will also get intellisense as you start writing queries. A query starts with ",[676,88477,88478],{},"query {"," type that and on the next line type the letter a:",[669,88481,88483],{"className":66259,"code":88482,"language":66261,"meta":674,"style":674},"query {\n a\n}\n",[676,88484,88485,88489,88494],{"__ignoreMap":674},[679,88486,88487],{"class":681,"line":682},[679,88488,86446],{},[679,88490,88491],{"class":681,"line":790},[679,88492,88493],{}," a\n",[679,88495,88496],{"class":681,"line":892},[679,88497,996],{},[651,88499,88500,88501,88503],{},"You should be presented with a drop down with a value of ",[676,88502,88043],{},". Based on the schema the UI here knows what queries are available to you. This is why you will hear a lot of people praise the developer experience for GraphQL and I am in that group of people 👏🏻",[651,88505,88506],{},"Paste the following query in the left pane and hit the play button above to execute it:",[669,88508,88509],{"className":66259,"code":86439,"language":66261,"meta":674,"style":674},[676,88510,88511,88515,88519,88523,88527,88531,88535,88539,88543,88547,88551,88555,88559],{"__ignoreMap":674},[679,88512,88513],{"class":681,"line":682},[679,88514,86446],{},[679,88516,88517],{"class":681,"line":790},[679,88518,86451],{},[679,88520,88521],{"class":681,"line":892},[679,88522,52587],{},[679,88524,88525],{"class":681,"line":901},[679,88526,86460],{},[679,88528,88529],{"class":681,"line":909},[679,88530,86465],{},[679,88532,88533],{"class":681,"line":918},[679,88534,86470],{},[679,88536,88537],{"class":681,"line":935},[679,88538,985],{},[679,88540,88541],{"class":681,"line":944},[679,88542,86479],{},[679,88544,88545],{"class":681,"line":959},[679,88546,86484],{},[679,88548,88549],{"class":681,"line":964},[679,88550,86489],{},[679,88552,88553],{"class":681,"line":977},[679,88554,985],{},[679,88556,88557],{"class":681,"line":982},[679,88558,21405],{},[679,88560,88561],{"class":681,"line":988},[679,88562,996],{},[651,88564,88565],{},[660,88566],{"alt":88567,"src":88568},"books-execute-query.png","/images/blog/2022/05/17/books-execute-query.png",[651,88570,88571,88572,88574],{},"Now that you can fetch all of the records in a collection how about finding a single record. In your schema you will need to define a new query with the name ",[676,88573,86714],{}," but this time it will take an argument. This argument is named id, has a type of ID and will return a single Book.",[669,88576,88578],{"className":4107,"code":88577,"language":4109,"meta":674,"style":674},"type Query {\n allBooks: [Book]\n findOne(id: ID!): Book\n}\n",[676,88579,88580,88584,88594,88616],{"__ignoreMap":674},[679,88581,88582],{"class":681,"line":682},[679,88583,86689],{"class":693},[679,88585,88586,88589,88591],{"class":681,"line":790},[679,88587,88588],{"class":693}," allBooks",[679,88590,2391],{"class":685},[679,88592,88593],{"class":693}," [Book]\n",[679,88595,88596,88599,88602,88604,88607,88609,88611,88613],{"class":681,"line":892},[679,88597,88598],{"class":880}," findOne",[679,88600,88601],{"class":693},"(id",[679,88603,2391],{"class":685},[679,88605,88606],{"class":693}," ID",[679,88608,1223],{"class":685},[679,88610,50653],{"class":693},[679,88612,2391],{"class":685},[679,88614,88615],{"class":693}," Book\n",[679,88617,88618],{"class":681,"line":901},[679,88619,996],{"class":693},[651,88621,88622,88623,88625,88626,664],{},"If you head back over to the ",[676,88624,88074],{}," you can define a new method called ",[676,88627,86714],{},[669,88629,88631],{"className":4107,"code":88630,"language":4109,"meta":674,"style":674},"@Controller\npublic class BookController {\n\n private final BookRepository bookRepository;\n\n public BookController(BookRepository bookRepository) {\n this.bookRepository = bookRepository;\n }\n\n @SchemaMapping(typeName = \"Query\",value = \"allBooks\")\n public List\u003CBook> findAll() {\n return bookRepository.findAll();\n }\n\n public Book findOne(@Argument Integer id) {\n return bookRepository.findOne(id);\n }\n}\n",[676,88632,88633,88639,88649,88653,88661,88665,88677,88687,88691,88695,88719,88733,88743,88747,88751,88771,88782,88786],{"__ignoreMap":674},[679,88634,88635,88637],{"class":681,"line":682},[679,88636,4116],{"class":693},[679,88638,9942],{"class":685},[679,88640,88641,88643,88645,88647],{"class":681,"line":790},[679,88642,6073],{"class":685},[679,88644,4512],{"class":685},[679,88646,88106],{"class":880},[679,88648,884],{"class":693},[679,88650,88651],{"class":681,"line":892},[679,88652,889],{"emptyLinePlaceholder":797},[679,88654,88655,88657,88659],{"class":681,"line":901},[679,88656,9232],{"class":685},[679,88658,12768],{"class":685},[679,88660,88121],{"class":693},[679,88662,88663],{"class":681,"line":909},[679,88664,889],{"emptyLinePlaceholder":797},[679,88666,88667,88669,88671,88673,88675],{"class":681,"line":918},[679,88668,6089],{"class":685},[679,88670,88106],{"class":880},[679,88672,88134],{"class":693},[679,88674,88137],{"class":2099},[679,88676,4390],{"class":693},[679,88678,88679,88681,88683,88685],{"class":681,"line":935},[679,88680,7862],{"class":931},[679,88682,88146],{"class":693},[679,88684,686],{"class":685},[679,88686,88151],{"class":693},[679,88688,88689],{"class":681,"line":944},[679,88690,985],{"class":693},[679,88692,88693],{"class":681,"line":959},[679,88694,889],{"emptyLinePlaceholder":797},[679,88696,88697,88699,88701,88703,88705,88707,88709,88711,88713,88715,88717],{"class":681,"line":964},[679,88698,6872],{"class":693},[679,88700,88371],{"class":685},[679,88702,745],{"class":693},[679,88704,88376],{"class":931},[679,88706,6883],{"class":685},[679,88708,88381],{"class":689},[679,88710,1202],{"class":693},[679,88712,19934],{"class":931},[679,88714,6883],{"class":685},[679,88716,88390],{"class":689},[679,88718,1339],{"class":693},[679,88720,88721,88723,88725,88727,88729,88731],{"class":681,"line":977},[679,88722,6089],{"class":685},[679,88724,87217],{"class":693},[679,88726,86678],{"class":685},[679,88728,20881],{"class":693},[679,88730,34142],{"class":880},[679,88732,2667],{"class":693},[679,88734,88735,88737,88739,88741],{"class":681,"line":982},[679,88736,9444],{"class":685},[679,88738,88253],{"class":693},[679,88740,34142],{"class":880},[679,88742,9317],{"class":693},[679,88744,88745],{"class":681,"line":988},[679,88746,985],{"class":693},[679,88748,88749],{"class":681,"line":993},[679,88750,889],{"emptyLinePlaceholder":797},[679,88752,88753,88755,88757,88759,88761,88764,88767,88769],{"class":681,"line":2129},[679,88754,6089],{"class":685},[679,88756,87304],{"class":693},[679,88758,86714],{"class":880},[679,88760,73246],{"class":693},[679,88762,88763],{"class":685},"Argument",[679,88765,88766],{"class":693}," Integer ",[679,88768,11341],{"class":2099},[679,88770,4390],{"class":693},[679,88772,88773,88775,88777,88779],{"class":681,"line":2140},[679,88774,9444],{"class":685},[679,88776,88253],{"class":693},[679,88778,86714],{"class":880},[679,88780,88781],{"class":693},"(id);\n",[679,88783,88784],{"class":681,"line":2145},[679,88785,985],{"class":693},[679,88787,88788],{"class":681,"line":2154},[679,88789,996],{"class":693},[651,88791,88792,88793,88796,88797,88799],{},"The first thing you will notice is a new annotation ",[676,88794,88795],{},"@Argument."," In the web world if you wanted to bind a method argument to a request parameter you would use the ",[676,88798,73299],{}," annotation. This is similar but you aren’t working with request parameters you’re working with arguments. This annotation will bind a named GraphQL argument onto a method parameter.",[651,88801,88802,88803,88805,88806,88808],{},"Remember that to map the GraphQL schema query named ",[676,88804,86714],{}," to this method you need to add a ",[676,88807,88296],{}," annotation.",[669,88810,88812],{"className":4107,"code":88811,"language":4109,"meta":674,"style":674},"@SchemaMapping(typeName = \"Query\", value=\"findOne\")\npublic Book findOne(@Argument Integer id) {\n return bookRepository.findOne(id);\n}\n",[676,88813,88814,88839,88854,88864],{"__ignoreMap":674},[679,88815,88816,88818,88820,88822,88824,88826,88828,88830,88832,88834,88837],{"class":681,"line":682},[679,88817,4116],{"class":693},[679,88819,88371],{"class":685},[679,88821,745],{"class":693},[679,88823,88376],{"class":931},[679,88825,6883],{"class":685},[679,88827,88381],{"class":689},[679,88829,2797],{"class":693},[679,88831,19934],{"class":931},[679,88833,686],{"class":685},[679,88835,88836],{"class":689},"\"findOne\"",[679,88838,1339],{"class":693},[679,88840,88841,88843,88845,88847,88849,88851],{"class":681,"line":790},[679,88842,6073],{"class":685},[679,88844,87304],{"class":693},[679,88846,86714],{"class":880},[679,88848,73246],{"class":693},[679,88850,88763],{"class":685},[679,88852,88853],{"class":693}," Integer id) {\n",[679,88855,88856,88858,88860,88862],{"class":681,"line":892},[679,88857,21478],{"class":685},[679,88859,88253],{"class":693},[679,88861,86714],{"class":880},[679,88863,88781],{"class":693},[679,88865,88866],{"class":681,"line":901},[679,88867,996],{"class":693},[651,88869,88870,88871,88873,88874,2797,88876,88879,88880,2797,88883,48406,88886,28997,88889,88891,88892,88808],{},"This is a little verbose and lucky for you there is an alternative. In the web world you have specialized annotations of ",[676,88872,22845],{}," like ",[676,88875,73296],{},[676,88877,88878],{},"@PostMapping"," and so on. In GraphQL there are special annotations for root types like ",[676,88881,88882],{},"@QueryMapping",[676,88884,88885],{},"@MutationMapping",[676,88887,88888],{},"@SubscriptionMapping",[676,88890,88882],{}," is a composed annotation that acts as a shortcut for @SchemaMapping with typeName=\"Query\". The trick here is that the value is derived from the method name. This means that if your method name matches the name of the query defined in your schema all you need is the ",[676,88893,88882],{},[669,88895,88897],{"className":4107,"code":88896,"language":4109,"meta":674,"style":674},"@QueryMapping\npublic Book findOne(@Argument Integer id) {\n return bookRepository.findOne(id);\n}\n",[676,88898,88899,88906,88920,88930],{"__ignoreMap":674},[679,88900,88901,88903],{"class":681,"line":682},[679,88902,4116],{"class":693},[679,88904,88905],{"class":685},"QueryMapping\n",[679,88907,88908,88910,88912,88914,88916,88918],{"class":681,"line":790},[679,88909,6073],{"class":685},[679,88911,87304],{"class":693},[679,88913,86714],{"class":880},[679,88915,73246],{"class":693},[679,88917,88763],{"class":685},[679,88919,88853],{"class":693},[679,88921,88922,88924,88926,88928],{"class":681,"line":892},[679,88923,21478],{"class":685},[679,88925,88253],{"class":693},[679,88927,86714],{"class":880},[679,88929,88781],{"class":693},[679,88931,88932],{"class":681,"line":901},[679,88933,996],{"class":693},[651,88935,88936],{},"If you restart the application you should be able to execute the following query.",[669,88938,88940],{"className":66259,"code":88939,"language":66261,"meta":674,"style":674},"query {\n findOne(id: 1) {\n title\n pages\n rating {\n star\n }\n author {\n firstName\n lastName\n }\n }\n}\n",[676,88941,88942,88946,88951,88955,88959,88963,88967,88971,88975,88979,88983,88987,88991],{"__ignoreMap":674},[679,88943,88944],{"class":681,"line":682},[679,88945,86446],{},[679,88947,88948],{"class":681,"line":790},[679,88949,88950],{}," findOne(id: 1) {\n",[679,88952,88953],{"class":681,"line":892},[679,88954,52587],{},[679,88956,88957],{"class":681,"line":901},[679,88958,86460],{},[679,88960,88961],{"class":681,"line":909},[679,88962,86465],{},[679,88964,88965],{"class":681,"line":918},[679,88966,86470],{},[679,88968,88969],{"class":681,"line":935},[679,88970,985],{},[679,88972,88973],{"class":681,"line":944},[679,88974,86479],{},[679,88976,88977],{"class":681,"line":959},[679,88978,86484],{},[679,88980,88981],{"class":681,"line":964},[679,88982,86489],{},[679,88984,88985],{"class":681,"line":977},[679,88986,985],{},[679,88988,88989],{"class":681,"line":982},[679,88990,21405],{},[679,88992,88993],{"class":681,"line":988},[679,88994,996],{},[651,88996,88997],{},"Congratulations 🥳 you just built your first GraphQL API in Spring. There is more to learn as you start building out full featured applications but you can do a lot with the basics you learned here today!",[5909,88999,89001],{"id":89000},"frontend","Frontend",[651,89003,89004,89005,89008,89009,664],{},"I’m not going to walk through this code but there is a fronted built in Vue located in ",[676,89006,89007],{},"/src/frontend."," If you want to learn more about running the frontend and the backend you can checkout the readme. If you want to learn more about how I build full-stack Java applications with Spring boot you can check out this ",[812,89010,5100],{"href":82616,"rel":89011},[816],[651,89013,89014,89015,89018,89019,89022],{},"The important part on the GraphQL side is that everything runs through a single endpoint ",[676,89016,89017],{},"/graphql",". In a REST application you might add a ",[676,89020,89021],{},"@CORS"," annotation to a REST Controller but because you aren’t configuring request mappings in each controller you need a way to configure CORS. The easiest way to do this is by setting the one of the available CORS properties available to you. In this example I just allow everything by setting the following property:",[669,89024,89026],{"className":5851,"code":89025,"language":5853,"meta":674,"style":674},"spring.graphql.cors.allowed-origins=*\n",[676,89027,89028],{"__ignoreMap":674},[679,89029,89030,89033],{"class":681,"line":682},[679,89031,89032],{"class":880},"spring.graphql.cors.allowed-origins",[679,89034,89035],{"class":689},"=*\n",[4542,89037,89039],{"id":89038},"whats-next","What’s Next?",[651,89041,89042],{},"We really just touched on the basics on how to get started with building GraphQL APIs in Spring. If you would like to learn more I have some resources for you below. I also want to let you know that I have a bunch of content planned.",[5316,89044,89045,89051,89059],{},[5332,89046,89047,89048,14944],{},"I’m working on the outline of a course that will teach you all about the basics of building, testing and deploying GraphQL applications for Spring. If you want to be notified when this available I will be releasing it to everyone subscribed to my ",[812,89049,67441],{"href":82778,"rel":89050},[816],[5332,89052,89053,89054,664],{},"I have a webinar coming up where we will cover some of this on June 21 and you can ",[812,89055,89058],{"href":89056,"rel":89057},"https://tanzu.vmware.com/content/webinars/jun-introduction-to-spring-for-graphql",[816],"register here",[5332,89060,89061,89066],{},[812,89062,89065],{"href":89063,"rel":89064},"https://tanzu.vmware.com/developer/springone-tour/",[816],"SpringOne Tour"," - I will be giving an intro to Spring for GraphQL in some of of tour stops so if your a location where we are visiting please stop by and say hello 👋🏻",[5909,89068,21931],{"id":21930},[5316,89070,89071,89078,89083,89088],{},[5332,89072,89073],{},[812,89074,89077],{"href":89075,"rel":89076},"https://github.com/danvega/spring-books",[816],"Github Repository for this tutorial",[5332,89079,89080],{},[812,89081,86347],{"href":86345,"rel":89082},[816],[5332,89084,89085],{},[812,89086,86353],{"href":86351,"rel":89087},[816],[5332,89089,89090],{},[812,89091,89094],{"href":89092,"rel":89093},"https://graphql.org/learn/",[816],"Learn GraphQL",[4542,89096,9042],{"id":9041},[651,89098,89099,89100,89103],{},"Spring for GraphQL is an exciting new project and we are just getting started. If you have any questions about building GraphQL APIs in Spring feel free to reach out and ask them. In the meantime follow me on ",[812,89101,51474],{"href":44086,"rel":89102},[816]," to stay up to date with everything I am working on.",[786,89105,89106],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":89108},[89109,89110,89114,89118,89125,89128],{"id":86360,"depth":790,"text":86361},{"id":86388,"depth":790,"text":86389,"children":89111},[89112,89113],{"id":86429,"depth":892,"text":86430},{"id":86507,"depth":892,"text":86508},{"id":86782,"depth":790,"text":86347,"children":89115},[89116,89117],{"id":86791,"depth":892,"text":86792},{"id":86811,"depth":892,"text":86347},{"id":86886,"depth":790,"text":86887,"children":89119},[89120,89121,89122,89123,89124],{"id":86909,"depth":892,"text":86910},{"id":87928,"depth":892,"text":87929},{"id":88064,"depth":892,"text":88065},{"id":88430,"depth":892,"text":88431},{"id":89000,"depth":892,"text":89001},{"id":89038,"depth":790,"text":89039,"children":89126},[89127],{"id":21930,"depth":892,"text":21931},{"id":9041,"depth":790,"text":9042},"In this tutorial you are going to learn how to get up and running with Spring for GraphQL.",{"slug":86782,"date":89131,"published":797,"author":798,"tags":89132,"cover":89133,"video":89134,"github":89075,"keywords":89135},"2022-05-17T16:00:00.000Z",[7055,66261],"./spring-for-graphql-thumbnail.png","https://www.youtube.com/embed/3PCNqXrU-2g","spring, graphql, spring boot, spring for graphql, graphql spring boot, graphql java, api, http, websock, rsocket",{"title":387,"description":89129},"blog/2022/05/17/spring-for-graphql","cTf9kL3q_ptrqGa64CgdZPr7qBB1au2VodM8ZuHBjSc",{"id":89140,"title":384,"body":89141,"description":91851,"extension":793,"meta":91852,"navigation":797,"path":385,"seo":91860,"stem":91861,"__hash__":91862},"content/blog/2022/09/09/spring-security-jwt.md",{"type":648,"value":89142,"toc":91831},[89143,89146,89149,89152,89159,89163,89166,89169,89176,89182,89186,89189,89200,89206,89209,89212,89218,89221,89227,89229,89236,89246,89252,89257,89317,89324,89328,89342,89352,89427,89433,89437,89447,89457,89647,89661,89668,89678,89771,89784,89790,89794,89797,89805,89820,89823,89826,89829,89832,89853,89857,89868,89881,89884,89916,89922,90052,90055,90072,90075,90178,90182,90189,90192,90200,90203,90207,90218,90221,90319,90325,90328,90340,90383,90389,90410,90413,90490,90496,90570,90582,90630,90633,90637,90640,90644,90654,90751,90764,91053,91062,91256,91259,91263,91266,91270,91273,91278,91281,91287,91296,91302,91307,91316,91339,91345,91351,91360,91375,91381,91385,91388,91397,91420,91429,91799,91801,91808,91811,91828],[651,89144,89145],{},"If you perform a quick search on how to secure REST APIs in Spring Boot using JSON Web Tokens you will find a lot of the same results. These results contain a method that involves writing a custom filter chain and pulling in a 3rd party library for encoding and decoding JWTs.",[651,89147,89148],{},"After staring at these convoluted and confusing tutorials I said there has to be an easier way to do this. I did what anyone with direct access to the Spring Security team would do, I asked them for help. They informed me that indeed Spring Security has built-in support for JWTs using oAuth2 Resource Server.",[651,89150,89151],{},"In this tutorial, you are going to learn how to secure your APIs using JSON Web Tokens (JWT) with Spring Security. I’m not saying this approach is easy by any stretch but for me, it made a lot more sense than the alternatives.",[651,89153,89154],{},[812,89155,89158],{"href":89156,"rel":89157},"https://github.com/danvega/jwt",[816],"GitHub Repository",[4542,89160,89162],{"id":89161},"application-architecture","Application Architecture",[651,89164,89165],{},"Before we get into writing some code I want to make sure we are all on the same page regarding what we are building. In the example below you have a client application which could be a simple command-line application, a full frontend application written in something like Angular or Vue, or some other service in your system.",[651,89167,89168],{},"This client application will make calls to a server application written in Spring Boot that exposes data via REST API. In the following example, it’s a monolith but the same would apply if you had a distributed architecture. There are currently 3 REST controllers that expose the resources products, orders, and customers.",[651,89170,89171,89172,89175],{},"What you will do is secure all of the resources so that when the client makes a call to the REST API the client will get a ",[7300,89173,89174],{},"401 (Unauthorized)"," which means the client request has not been completed because it lacks valid authentication credentials for the requested resource**.**",[651,89177,89178],{},[660,89179],{"alt":89180,"src":89181},"Application Architecture: 401 Unauthorized","/images/blog/2022/09/09/app-arch-401.png",[5909,89183,89185],{"id":89184},"json-web-tokens-jwt","JSON Web Tokens (JWT)",[651,89187,89188],{},"A JSON Web Token is an open method for representing claims securely between two parties. A JWT is a set of claims (JSON property–value pairs) that together make up a JSON object. It consists of three parts:",[5316,89190,89191,89194,89197],{},[5332,89192,89193],{},"Header: Consists of two properties: { \"alg\": \"HS256\", \"typ\": \"JWT\" }. alg is the algorithm that is used to encrypt the JWT.",[5332,89195,89196],{},"Payload: This is where the data to be sent is stored; this data is stored as JSON property–value pairs.",[5332,89198,89199],{},"Signature: This is created by encrypting, with the algorithm specified in the header: (i) the base64Url-encoded header, (ii) base64Url-encoded payload, and (iii) a secret (or a private key):",[669,89201,89204],{"className":89202,"code":89203,"language":11464},[16247],"HMACSHA256(base64UrlEncode(header) + \".\" + base64UrlEncode(payload), secret|privateKey)\n",[676,89205,89203],{"__ignoreMap":674},[651,89207,89208],{},"The final JWT consists of three parts. Each is base64Url-encoded and separated from the next by a dot. See the openid.net and jwt.io websites for more details.",[651,89210,89211],{},"You will introduce a new authentication controller that a client can make a request to with their authentication credentials (username + password) and when they are successfully authenticated the service will return a JWT.",[651,89213,89214],{},[660,89215],{"alt":89216,"src":89217},"Application Architecture: JSON Web Token (JWT)","/images/blog/2022/09/09/app-arch-jwt.png",[651,89219,89220],{},"The client will then store the JWT and each subsequent request will pass it via the Authorization header. When the Server application receives the request with the JWT it will verify that it is a valid token and if it is will allow the request to continue.",[651,89222,89223],{},[660,89224],{"alt":89225,"src":89226},"Application Architecture: Request with JSON Web Token (JWT)","/images/blog/2022/09/09/app-arch-with-jwt-200.png",[4542,89228,39958],{"id":41932},[651,89230,89231,89232,89235],{},"To get started you are going to head over to ",[812,89233,77478],{"href":30440,"rel":89234},[816]," and create a new project. Fill in the metadata for the project and add the following dependencies:",[5316,89237,89238,89240,89243],{},[5332,89239,80827],{},[5332,89241,89242],{},"oAuth2 Resource Server",[5332,89244,89245],{},"Spring Configuration Processor",[651,89247,89248],{},[660,89249],{"alt":89250,"src":89251},"Spring Initializer","/images/blog/2022/09/09/start-spring-io.png",[651,89253,89254,89255],{},"This will generate the following dependencies in your ",[676,89256,81517],{},[669,89258,89260],{"className":9101,"code":89259,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n\u003C/dependency>\n\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-oauth2-resource-server\u003C/artifactId>\n\u003C/dependency>\n\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-configuration-processor\u003C/artifactId>\n \u003Coptional>true\u003C/optional>\n\u003C/dependency>\n",[676,89261,89262,89266,89270,89274,89278,89282,89286,89291,89295,89299,89303,89308,89313],{"__ignoreMap":674},[679,89263,89264],{"class":681,"line":682},[679,89265,9110],{},[679,89267,89268],{"class":681,"line":790},[679,89269,9115],{},[679,89271,89272],{"class":681,"line":892},[679,89273,9138],{},[679,89275,89276],{"class":681,"line":901},[679,89277,9125],{},[679,89279,89280],{"class":681,"line":909},[679,89281,9110],{},[679,89283,89284],{"class":681,"line":918},[679,89285,9115],{},[679,89287,89288],{"class":681,"line":935},[679,89289,89290],{}," \u003CartifactId>spring-boot-starter-oauth2-resource-server\u003C/artifactId>\n",[679,89292,89293],{"class":681,"line":944},[679,89294,9125],{},[679,89296,89297],{"class":681,"line":959},[679,89298,9110],{},[679,89300,89301],{"class":681,"line":964},[679,89302,9115],{},[679,89304,89305],{"class":681,"line":977},[679,89306,89307],{}," \u003CartifactId>spring-boot-configuration-processor\u003C/artifactId>\n",[679,89309,89310],{"class":681,"line":982},[679,89311,89312],{}," \u003Coptional>true\u003C/optional>\n",[679,89314,89315],{"class":681,"line":988},[679,89316,9125],{},[651,89318,89319,89320,89323],{},"I know what you’re thinking, what about Spring Security? If you dig into the ",[676,89321,89322],{},"spring-boot-starter-oauth2-resource-server"," you will find that it includes the Spring Security Starter that contains everything you need.",[4542,89325,89327],{"id":89326},"rest-api","REST API",[651,89329,89330,89331,89334,89335,89337,89338,89341],{},"The first thing you will need to do is to create a REST API that you want to be secured. For demo purposes and to keep it simple create ",[676,89332,89333],{},"HomeController"," in the ",[676,89336,4279],{}," package with a single method that returns a String. Request Mapping handler methods can accept a range of arguments, one of which is ",[676,89339,89340],{},"java.security.Principal",". This will allow you to print out the username of the currently authenticated user.",[651,89343,89344,89345,89348,89349,89351],{},"Spring Security takes a secure-by-default approach to security. This means that if you start your application and try to visit ",[812,89346,11697],{"href":11697,"rel":89347},[816]," you will be redirected to a login page. If you want to log in you can enter the username of ",[676,89350,9575],{}," and the password is generated and should be listed in the console output.",[669,89353,89355],{"className":4107,"code":89354,"language":4109,"meta":674,"style":674},"@RestController\npublic class HomeController {\n\n @GetMapping\n public String home(Principal principal) {\n return \"Hello, \" + principal.getName();\n }\n\n}\n",[676,89356,89357,89363,89373,89377,89383,89399,89415,89419,89423],{"__ignoreMap":674},[679,89358,89359,89361],{"class":681,"line":682},[679,89360,4116],{"class":693},[679,89362,9212],{"class":685},[679,89364,89365,89367,89369,89371],{"class":681,"line":790},[679,89366,6073],{"class":685},[679,89368,4512],{"class":685},[679,89370,18716],{"class":880},[679,89372,884],{"class":693},[679,89374,89375],{"class":681,"line":892},[679,89376,889],{"emptyLinePlaceholder":797},[679,89378,89379,89381],{"class":681,"line":901},[679,89380,6872],{"class":693},[679,89382,80415],{"class":685},[679,89384,89385,89387,89389,89391,89394,89397],{"class":681,"line":909},[679,89386,6089],{"class":685},[679,89388,9289],{"class":693},[679,89390,12642],{"class":880},[679,89392,89393],{"class":693},"(Principal ",[679,89395,89396],{"class":2099},"principal",[679,89398,4390],{"class":693},[679,89400,89401,89403,89406,89408,89411,89413],{"class":681,"line":918},[679,89402,9444],{"class":685},[679,89404,89405],{"class":689}," \"Hello, \"",[679,89407,3059],{"class":685},[679,89409,89410],{"class":693}," principal.",[679,89412,10577],{"class":880},[679,89414,9317],{"class":693},[679,89416,89417],{"class":681,"line":935},[679,89418,985],{"class":693},[679,89420,89421],{"class":681,"line":944},[679,89422,889],{"emptyLinePlaceholder":797},[679,89424,89425],{"class":681,"line":959},[679,89426,996],{"class":693},[651,89428,89429],{},[660,89430],{"alt":89431,"src":89432},"Spring Security Login","/images/blog/2022/09/09/please-sign-in.png",[4542,89434,89436],{"id":89435},"spring-security-configuration","Spring Security Configuration",[651,89438,89439,89440,89442,89443,664],{},"The default security configuration is enough to get you up and running but you will need to provide your own to fit the needs of your application. In the past, you would extend the ",[676,89441,40080],{}," but that has been deprecated in Spring Security 5.7.x. If you’re interested in learning more about this change you can check out ",[812,89444,34211],{"href":89445,"rel":89446},"https://youtu.be/s4X4SJv2RrU",[816],[651,89448,89449,89450,89452,89453,89456],{},"To get started create a new class in the ",[676,89451,64430],{}," package called ",[676,89454,89455],{},"SecurityConfig",". This class will have the following configuration:",[669,89458,89460],{"className":4107,"code":89459,"language":4109,"meta":674,"style":674},"@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n @Bean\n public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http\n .csrf(csrf -> csrf.disable()) // (1)\n .authorizeRequests( auth -> auth\n .anyRequest().authenticated() // (2)\n )\n .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // (3)\n .httpBasic(Customizer.withDefaults()) // (4)\n .build();\n }\n\n}\n",[676,89461,89462,89468,89475,89486,89490,89496,89518,89525,89548,89562,89580,89585,89609,89627,89635,89639,89643],{"__ignoreMap":674},[679,89463,89464,89466],{"class":681,"line":682},[679,89465,4116],{"class":693},[679,89467,6212],{"class":685},[679,89469,89470,89472],{"class":681,"line":790},[679,89471,4116],{"class":693},[679,89473,89474],{"class":685},"EnableWebSecurity\n",[679,89476,89477,89479,89481,89484],{"class":681,"line":892},[679,89478,6073],{"class":685},[679,89480,4512],{"class":685},[679,89482,89483],{"class":880}," SecurityConfig",[679,89485,884],{"class":693},[679,89487,89488],{"class":681,"line":901},[679,89489,889],{"emptyLinePlaceholder":797},[679,89491,89492,89494],{"class":681,"line":909},[679,89493,6872],{"class":693},[679,89495,16929],{"class":685},[679,89497,89498,89500,89503,89506,89509,89512,89514,89516],{"class":681,"line":918},[679,89499,6089],{"class":685},[679,89501,89502],{"class":693}," SecurityFilterChain ",[679,89504,89505],{"class":880},"securityFilterChain",[679,89507,89508],{"class":693},"(HttpSecurity ",[679,89510,89511],{"class":2099},"http",[679,89513,2378],{"class":693},[679,89515,9580],{"class":685},[679,89517,10466],{"class":693},[679,89519,89520,89522],{"class":681,"line":935},[679,89521,9444],{"class":685},[679,89523,89524],{"class":693}," http\n",[679,89526,89527,89529,89532,89535,89537,89540,89543,89545],{"class":681,"line":944},[679,89528,73482],{"class":693},[679,89530,89531],{"class":880},"csrf",[679,89533,89534],{"class":693},"(csrf ",[679,89536,16955],{"class":685},[679,89538,89539],{"class":693}," csrf.",[679,89541,89542],{"class":880},"disable",[679,89544,14554],{"class":693},[679,89546,89547],{"class":1400},"// (1)\n",[679,89549,89550,89552,89554,89557,89559],{"class":681,"line":959},[679,89551,73482],{"class":693},[679,89553,40110],{"class":880},[679,89555,89556],{"class":693},"( auth ",[679,89558,16955],{"class":685},[679,89560,89561],{"class":693}," auth\n",[679,89563,89564,89567,89570,89572,89575,89577],{"class":681,"line":964},[679,89565,89566],{"class":693}," .",[679,89568,89569],{"class":880},"anyRequest",[679,89571,10541],{"class":693},[679,89573,89574],{"class":880},"authenticated",[679,89576,6700],{"class":693},[679,89578,89579],{"class":1400},"// (2)\n",[679,89581,89582],{"class":681,"line":977},[679,89583,89584],{"class":693}," )\n",[679,89586,89587,89589,89592,89595,89597,89600,89603,89606],{"class":681,"line":982},[679,89588,73482],{"class":693},[679,89590,89591],{"class":880},"sessionManagement",[679,89593,89594],{"class":693},"(session ",[679,89596,16955],{"class":685},[679,89598,89599],{"class":693}," session.",[679,89601,89602],{"class":880},"sessionCreationPolicy",[679,89604,89605],{"class":693},"(SessionCreationPolicy.STATELESS)) ",[679,89607,89608],{"class":1400},"// (3)\n",[679,89610,89611,89613,89616,89619,89622,89624],{"class":681,"line":988},[679,89612,73482],{"class":693},[679,89614,89615],{"class":880},"httpBasic",[679,89617,89618],{"class":693},"(Customizer.",[679,89620,89621],{"class":880},"withDefaults",[679,89623,14554],{"class":693},[679,89625,89626],{"class":1400},"// (4)\n",[679,89628,89629,89631,89633],{"class":681,"line":993},[679,89630,73482],{"class":693},[679,89632,23612],{"class":880},[679,89634,9317],{"class":693},[679,89636,89637],{"class":681,"line":2129},[679,89638,985],{"class":693},[679,89640,89641],{"class":681,"line":2140},[679,89642,889],{"emptyLinePlaceholder":797},[679,89644,89645],{"class":681,"line":2145},[679,89646,996],{"class":693},[27665,89648,89649,89652,89655,89658],{},[5332,89650,89651],{},"Disable Cross-Site Request Forgery (CSRF)",[5332,89653,89654],{},"The user should be authenticated for any request in the application.",[5332,89656,89657],{},"Spring Security will never create an HttpSession and it will never use it to obtain the Security Context.",[5332,89659,89660],{},"Spring Security’s HTTP Basic Authentication support is enabled by default. However, as soon as any servlet-based configuration is provided, HTTP Basic must be explicitly provided.",[89662,89663,89665],"warning",{"title":89664},"WARNING",[651,89666,89667],{},"Never disable CSRF protection while leaving session management enabled! Doing so will open you up to a Cross-Site Request Forgery attack.",[651,89669,89670,89671,89674,89675,89677],{},"Now that you have a custom security configuration in place you need a user that isn’t the default one provided by Spring Boot. The following configuration will create an in-memory user using the ",[676,89672,89673],{},"NoOpPasswordEncoder"," This is a password encoder that does nothing and is useful for testing but should ",[2939,89676,10922],{}," be used in production.",[669,89679,89681],{"className":4107,"code":89680,"language":4109,"meta":674,"style":674},"@Bean\npublic InMemoryUserDetailsManager users() {\n return new InMemoryUserDetailsManager(\n User.withUsername(\"dvega\")\n .password(\"{noop}password\")\n .authorities(\"read\")\n .build()\n );\n}\n",[676,89682,89683,89689,89700,89711,89726,89740,89754,89762,89767],{"__ignoreMap":674},[679,89684,89685,89687],{"class":681,"line":682},[679,89686,4116],{"class":693},[679,89688,16929],{"class":685},[679,89690,89691,89693,89696,89698],{"class":681,"line":790},[679,89692,6073],{"class":685},[679,89694,89695],{"class":693}," InMemoryUserDetailsManager ",[679,89697,34174],{"class":880},[679,89699,2667],{"class":693},[679,89701,89702,89704,89706,89709],{"class":681,"line":892},[679,89703,21478],{"class":685},[679,89705,2054],{"class":685},[679,89707,89708],{"class":880}," InMemoryUserDetailsManager",[679,89710,21337],{"class":693},[679,89712,89713,89716,89719,89721,89724],{"class":681,"line":901},[679,89714,89715],{"class":693}," User.",[679,89717,89718],{"class":880},"withUsername",[679,89720,745],{"class":693},[679,89722,89723],{"class":689},"\"dvega\"",[679,89725,1339],{"class":693},[679,89727,89728,89731,89733,89735,89738],{"class":681,"line":909},[679,89729,89730],{"class":693}," .",[679,89732,24067],{"class":880},[679,89734,745],{"class":693},[679,89736,89737],{"class":689},"\"{noop}password\"",[679,89739,1339],{"class":693},[679,89741,89742,89744,89747,89749,89752],{"class":681,"line":918},[679,89743,89730],{"class":693},[679,89745,89746],{"class":880},"authorities",[679,89748,745],{"class":693},[679,89750,89751],{"class":689},"\"read\"",[679,89753,1339],{"class":693},[679,89755,89756,89758,89760],{"class":681,"line":935},[679,89757,89730],{"class":693},[679,89759,23612],{"class":880},[679,89761,17545],{"class":693},[679,89763,89764],{"class":681,"line":944},[679,89765,89766],{"class":693}," );\n",[679,89768,89769],{"class":681,"line":959},[679,89770,996],{"class":693},[651,89772,89773,89774,89777,89778,89781,89782,664],{},"With the new user configured you should be able to restart the application and visit ",[812,89775,11697],{"href":11697,"rel":89776},[816],". You will be presented with a dialog asking for a username and password and if everything works you should be able to log in with ",[676,89779,89780],{},"dvega"," + ",[676,89783,24067],{},[651,89785,89786],{},[660,89787],{"alt":89788,"src":89789},"Spring Security HTTP Basic","/images/blog/2022/09/09/http-basic-auth.png",[4542,89791,89793],{"id":89792},"oauth-20-resource-server","OAuth 2.0 Resource Server",[651,89795,89796],{},"If you watched my previous tutorial everything you have done so far should be familiar but I know that’s not what you’re here for. Spring Security supports protecting endpoints using two forms of OAuth 2.0 Bearer Tokens:",[5316,89798,89799,89802],{},[5332,89800,89801],{},"JWT",[5332,89803,89804],{},"Opaque Tokens",[651,89806,89807,89808,89813,89814,89819],{},"This is handy in circumstances where an application has delegated its authority management to an ",[812,89809,89812],{"href":89810,"rel":89811},"https://tools.ietf.org/html/rfc6749",[816],"authorization server"," (for example, Okta or ",[812,89815,89818],{"href":89816,"rel":89817},"https://spring.io/projects/spring-authorization-server",[816],"Spring Authorization Server","). This authorization server can be consulted by resource servers to authorize requests.",[651,89821,89822],{},"In this tutorial, you will use self-signed JWTs which will eliminate the need to introduce an authorization server. While this works for this example, your application requirements might be different so when is it no longer acceptable to use self-signed JWTs? This is a question I also posed to the Spring Security team and got some really great answers.",[1004,89824,89825],{},"\nWhen you reach the point where the trade-offs for self-signed JWTs are not acceptable. An example might be the moment you want to introduce refresh tokens.\n",[1004,89827,89828],{},"\nI'd add that a distinct authorization server makes more sense when you have more than one service or you want to be able to harden security (isolating something as critical as authentication provides value because the attack surface is reduced)\n",[651,89830,89831],{},"We could spend a lot of time talking about Authorization and Resource servers. To keep this tutorial on the topic I will leave you some really great resources that I would recommend you go through them when you have some time.",[5316,89833,89834,89841,89848],{},[5332,89835,89836],{},[812,89837,89840],{"href":89838,"rel":89839},"https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html",[816],"OAuth2 Resource Server",[5332,89842,89843],{},[812,89844,89847],{"href":89845,"rel":89846},"https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html",[816],"OAuth2 Resource Server JWT",[5332,89849,89850],{},[812,89851,89818],{"href":89816,"rel":89852},[816],[5909,89854,89856],{"id":89855},"oauth-2-resource-server-configuration","OAuth 2 Resource Server Configuration",[651,89858,89859,89860,89863,89864,89867],{},"Now that you know what a resource server is and what it is used for you need to configure one. You can do so in your security config by setting ",[676,89861,89862],{},".oauth2ResourceServer()",". This could be a custom resource server configurer or you can use the ",[676,89865,89866],{},"OAuth2ResourceServerConfigurer"," class provided by Spring.",[651,89869,40060,89870,89872,89873,89876,89877,89880],{},[676,89871,89866],{}," is an ",[676,89874,89875],{},"AbstractHttpConfigurer"," for OAuth 2.0 Resource Server Support. By default, this wires a ",[676,89878,89879],{},"BearerTokenAuthenticationFilter",", which can be used to parse the request for bearer tokens and make an authentication attempt.",[651,89882,89883],{},"This configuration class has the following options available:",[5316,89885,89886,89892,89898,89904,89910],{},[5332,89887,89888,89891],{},[676,89889,89890],{},"accessDeniedHandler"," - Customizes how access denied errors are handled.",[5332,89893,89894,89897],{},[676,89895,89896],{},"authenticationEntryPoint"," - Customizes how authentication failures are handled.",[5332,89899,89900,89903],{},[676,89901,89902],{},"bearerTokenResolver"," - Customizes how to resolve a bearer token from the request.",[5332,89905,89906,89909],{},[676,89907,89908],{},"jwt","(Customizer) - Enables Jwt-encoded bearer token support.",[5332,89911,89912,89915],{},[676,89913,89914],{},"opaqueToken","(Customizer) - Enables opaque bearer token support.",[651,89917,89918,89919],{},"You are going to use JWT so the configuration option can use a method reference and will look like ",[676,89920,89921],{},"OAuth2ResourceServerConfigurer::jwt",[669,89923,89925],{"className":4107,"code":89924,"language":4109,"meta":674,"style":674},"@Bean\npublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http\n .csrf(csrf -> csrf.disable())\n .authorizeRequests( auth -> auth\n .anyRequest().authenticated()\n )\n .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)\n .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))\n .httpBasic(withDefaults())\n .build();\n}\n",[676,89926,89927,89933,89944,89950,89966,89978,89990,89995,90011,90028,90040,90048],{"__ignoreMap":674},[679,89928,89929,89931],{"class":681,"line":682},[679,89930,4116],{"class":693},[679,89932,16929],{"class":685},[679,89934,89935,89937,89939,89941],{"class":681,"line":790},[679,89936,6073],{"class":685},[679,89938,89502],{"class":693},[679,89940,89505],{"class":880},[679,89942,89943],{"class":693},"(HttpSecurity http) throws Exception {\n",[679,89945,89946,89948],{"class":681,"line":892},[679,89947,21478],{"class":685},[679,89949,89524],{"class":693},[679,89951,89952,89954,89956,89958,89960,89962,89964],{"class":681,"line":901},[679,89953,40148],{"class":693},[679,89955,89531],{"class":880},[679,89957,89534],{"class":693},[679,89959,16955],{"class":685},[679,89961,89539],{"class":693},[679,89963,89542],{"class":880},[679,89965,40172],{"class":693},[679,89967,89968,89970,89972,89974,89976],{"class":681,"line":909},[679,89969,40148],{"class":693},[679,89971,40110],{"class":880},[679,89973,89556],{"class":693},[679,89975,16955],{"class":685},[679,89977,89561],{"class":693},[679,89979,89980,89982,89984,89986,89988],{"class":681,"line":918},[679,89981,89730],{"class":693},[679,89983,89569],{"class":880},[679,89985,10541],{"class":693},[679,89987,89574],{"class":880},[679,89989,17545],{"class":693},[679,89991,89992],{"class":681,"line":935},[679,89993,89994],{"class":693}," )\n",[679,89996,89997,89999,90002,90005,90008],{"class":681,"line":944},[679,89998,40148],{"class":693},[679,90000,90001],{"class":880},"oauth2ResourceServer",[679,90003,90004],{"class":693},"(OAuth2ResourceServerConfigurer",[679,90006,90007],{"class":685},"::",[679,90009,90010],{"class":693},"jwt)\n",[679,90012,90013,90015,90017,90019,90021,90023,90025],{"class":681,"line":959},[679,90014,40148],{"class":693},[679,90016,89591],{"class":880},[679,90018,89594],{"class":693},[679,90020,16955],{"class":685},[679,90022,89599],{"class":693},[679,90024,89602],{"class":880},[679,90026,90027],{"class":693},"(SessionCreationPolicy.STATELESS))\n",[679,90029,90030,90032,90034,90036,90038],{"class":681,"line":964},[679,90031,40148],{"class":693},[679,90033,89615],{"class":880},[679,90035,745],{"class":693},[679,90037,89621],{"class":880},[679,90039,40172],{"class":693},[679,90041,90042,90044,90046],{"class":681,"line":977},[679,90043,40148],{"class":693},[679,90045,23612],{"class":880},[679,90047,9317],{"class":693},[679,90049,90050],{"class":681,"line":982},[679,90051,996],{"class":693},[651,90053,90054],{},"When you use the JWT customizer you need to provide one of the following:",[5316,90056,90057,90063,90069],{},[5332,90058,90059,90060],{},"Supply a Jwk Set Uri via ",[676,90061,90062],{},"OAuth2ResourceServerConfigurer.JwtConfigurer.jwkSetUri",[5332,90064,90065,90066],{},"Supply a JwtDecoder instance via ",[676,90067,90068],{},"OAuth2ResourceServerConfigurer.JwtConfigurer.decoder",[5332,90070,90071],{},"Expose a JwtDecoder bean.",[651,90073,90074],{},"If you try and run the app without providing one of the options above you will receive the following error:",[669,90076,90078],{"className":5851,"code":90077,"language":5853,"meta":674,"style":674},"Description:\n\nParameter 0 of method setFilterChains in\norg.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration\nrequired a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' that could not be found.\n\nAction:\n\nConsider defining a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder'\nin your configuration.\n",[676,90079,90080,90085,90089,90107,90112,90141,90145,90150,90154,90173],{"__ignoreMap":674},[679,90081,90082],{"class":681,"line":682},[679,90083,90084],{"class":880},"Description:\n",[679,90086,90087],{"class":681,"line":790},[679,90088,889],{"emptyLinePlaceholder":797},[679,90090,90091,90094,90096,90098,90101,90104],{"class":681,"line":892},[679,90092,90093],{"class":880},"Parameter",[679,90095,14987],{"class":931},[679,90097,19757],{"class":689},[679,90099,90100],{"class":689}," method",[679,90102,90103],{"class":689}," setFilterChains",[679,90105,90106],{"class":689}," in\n",[679,90108,90109],{"class":681,"line":901},[679,90110,90111],{"class":880},"org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration\n",[679,90113,90114,90117,90119,90122,90124,90126,90129,90131,90134,90136,90138],{"class":681,"line":909},[679,90115,90116],{"class":880},"required",[679,90118,21697],{"class":689},[679,90120,90121],{"class":689}," bean",[679,90123,19757],{"class":689},[679,90125,27725],{"class":689},[679,90127,90128],{"class":689}," 'org.springframework.security.oauth2.jwt.JwtDecoder'",[679,90130,6417],{"class":689},[679,90132,90133],{"class":689}," could",[679,90135,62357],{"class":689},[679,90137,62019],{"class":689},[679,90139,90140],{"class":689}," found.\n",[679,90142,90143],{"class":681,"line":918},[679,90144,889],{"emptyLinePlaceholder":797},[679,90146,90147],{"class":681,"line":935},[679,90148,90149],{"class":880},"Action:\n",[679,90151,90152],{"class":681,"line":944},[679,90153,889],{"emptyLinePlaceholder":797},[679,90155,90156,90159,90162,90164,90166,90168,90170],{"class":681,"line":959},[679,90157,90158],{"class":880},"Consider",[679,90160,90161],{"class":689}," defining",[679,90163,21697],{"class":689},[679,90165,90121],{"class":689},[679,90167,19757],{"class":689},[679,90169,27725],{"class":689},[679,90171,90172],{"class":689}," 'org.springframework.security.oauth2.jwt.JwtDecoder'\n",[679,90174,90175],{"class":681,"line":964},[679,90176,90177],{"class":693},"in your configuration.\n",[5909,90179,90181],{"id":90180},"signing-json-web-tokens","Signing JSON Web Tokens",[651,90183,90184,90185,90188],{},"The next step is to create a new ",[676,90186,90187],{},"JwtDecoder"," bean but I think we need to talk about what we are going to do here. As you learned earlier there are 3 parts to the JWT, the header, payload, and signature. The signature is created using by encrypting the header + payload and a secret (or private key).",[651,90190,90191],{},"A JWT can be encrypted using either a symmetric key (shared secret) or asymmetric keys (the private key of a private-public pair).",[5316,90193,90194,90197],{},[5332,90195,90196],{},"Symmetric key: The same key is used for both encryption (when the JWT is created) and decryption (MobileTogether Server uses the key to verify the JWT). The symmetric key—also known as the shared secret—is stored as a setting in MobileTogether Server. See Symmetric Key: Shared Secret for details of working with symmetric keys.",[5332,90198,90199],{},"Asymmetric keys: Different keys are used for encryption (private key) and decryption (public key). The public key is stored as a setting in MobileTogether Server so that the JWT can be verified. For information about using asymmetric encryption for JWTs, see Asymmetric Keys: Public Key.",[651,90201,90202],{},"There are pros/cons to each but it is generally recommended that you use Asymmetric keys so that is the approach you will take here.",[5909,90204,90206],{"id":90205},"rsa-public-private-keys","RSA Public & Private Keys",[651,90208,90209,90210,90213,90214,90217],{},"You are going to create a public / private key pair. This is something that you can do via code but I think it might make more sense if you do it manually here. I would create these in a new folder under ",[676,90211,90212],{},"/src/main/rescurces/certs",". I am going to use ",[2939,90215,90216],{},"OpenSSL"," which is installed by default on macOS but you should be able to install it on whatever OS you’re using.",[651,90219,90220],{},"Normally you could get away with running the first 2 commands. The reason for the 3rd command is that the private key needs to be in PEM-encoded PKCS#8 format. Switch to that certs directory and run each of the following commands separately.",[669,90222,90224],{"className":5851,"code":90223,"language":5853,"meta":674,"style":674},"# create rsa key pair\nopenssl genrsa -out keypair.pem 2048\n\n# extract public key\nopenssl rsa -in keypair.pem -pubout -out public.pem\n\n# create private key in PKCS#8 format\nopenssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out private.pem\n",[676,90225,90226,90231,90248,90252,90257,90277,90281,90286],{"__ignoreMap":674},[679,90227,90228],{"class":681,"line":682},[679,90229,90230],{"class":1400},"# create rsa key pair\n",[679,90232,90233,90236,90239,90242,90245],{"class":681,"line":790},[679,90234,90235],{"class":880},"openssl",[679,90237,90238],{"class":689}," genrsa",[679,90240,90241],{"class":931}," -out",[679,90243,90244],{"class":689}," keypair.pem",[679,90246,90247],{"class":931}," 2048\n",[679,90249,90250],{"class":681,"line":892},[679,90251,889],{"emptyLinePlaceholder":797},[679,90253,90254],{"class":681,"line":901},[679,90255,90256],{"class":1400},"# extract public key\n",[679,90258,90259,90261,90264,90267,90269,90272,90274],{"class":681,"line":909},[679,90260,90235],{"class":880},[679,90262,90263],{"class":689}," rsa",[679,90265,90266],{"class":931}," -in",[679,90268,90244],{"class":689},[679,90270,90271],{"class":931}," -pubout",[679,90273,90241],{"class":931},[679,90275,90276],{"class":689}," public.pem\n",[679,90278,90279],{"class":681,"line":918},[679,90280,889],{"emptyLinePlaceholder":797},[679,90282,90283],{"class":681,"line":935},[679,90284,90285],{"class":1400},"# create private key in PKCS#8 format\n",[679,90287,90288,90290,90293,90296,90299,90302,90305,90307,90310,90312,90314,90316],{"class":681,"line":944},[679,90289,90235],{"class":880},[679,90291,90292],{"class":689}," pkcs8",[679,90294,90295],{"class":931}," -topk8",[679,90297,90298],{"class":931}," -inform",[679,90300,90301],{"class":689}," PEM",[679,90303,90304],{"class":931}," -outform",[679,90306,90301],{"class":689},[679,90308,90309],{"class":931}," -nocrypt",[679,90311,90266],{"class":931},[679,90313,90244],{"class":689},[679,90315,90241],{"class":931},[679,90317,90318],{"class":689}," private.pem\n",[651,90320,90321,90322],{},"If everything runs without error and you have both a public and private key you can delete ",[676,90323,90324],{},"keypair.pem",[5909,90326,90187],{"id":90327},"jwtdecoder",[651,90329,90330,90331,90333,90334,89452,90336,90339],{},"With the public and private keys in place, you can return your focus to defining a ",[676,90332,90187],{}," bean. First, create a new record class in the ",[676,90335,64430],{},[676,90337,90338],{},"RsaKeyProperties"," This will be used to externalize both the public and private key.",[669,90341,90343],{"className":4107,"code":90342,"language":4109,"meta":674,"style":674},"@ConfigurationProperties(prefix = \"rsa\")\npublic record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) {\n\n}\n",[676,90344,90345,90363,90375,90379],{"__ignoreMap":674},[679,90346,90347,90349,90351,90353,90356,90358,90361],{"class":681,"line":682},[679,90348,4116],{"class":693},[679,90350,35506],{"class":685},[679,90352,745],{"class":693},[679,90354,90355],{"class":931},"prefix",[679,90357,6883],{"class":685},[679,90359,90360],{"class":689}," \"rsa\"",[679,90362,1339],{"class":693},[679,90364,90365,90367,90369,90372],{"class":681,"line":790},[679,90366,6073],{"class":685},[679,90368,86928],{"class":685},[679,90370,90371],{"class":880}," RsaKeyProperties",[679,90373,90374],{"class":693},"(RSAPublicKey publicKey, RSAPrivateKey privateKey) {\n",[679,90376,90377],{"class":681,"line":892},[679,90378,889],{"emptyLinePlaceholder":797},[679,90380,90381],{"class":681,"line":901},[679,90382,996],{"class":693},[651,90384,90385,90386,90388],{},"If you run a build and open up ",[676,90387,16242],{}," you should get IntelliSense for the private and public key configuration. Add the following configuration so your application can find your keys.",[669,90390,90392],{"className":76589,"code":90391,"language":35538,"meta":674,"style":674},"rsa.private-key=classpath:certs/private.pem\nrsa.public-key=classpath:certs/public.pem\n",[676,90393,90394,90402],{"__ignoreMap":674},[679,90395,90396,90399],{"class":681,"line":682},[679,90397,90398],{"class":685},"rsa.private-key",[679,90400,90401],{"class":693},"=classpath:certs/private.pem\n",[679,90403,90404,90407],{"class":681,"line":790},[679,90405,90406],{"class":685},"rsa.public-key",[679,90408,90409],{"class":693},"=classpath:certs/public.pem\n",[651,90411,90412],{},"Next you need to enable configuration properties on your main class:",[669,90414,90416],{"className":4107,"code":90415,"language":4109,"meta":674,"style":674},"@SpringBootApplication\n@EnableConfigurationProperties(RsaKeyProperties.class)\npublic class JwtDemoApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(JwtDemoApplication.class, args);\n }\n\n}\n",[676,90417,90418,90424,90434,90445,90449,90469,90478,90482,90486],{"__ignoreMap":674},[679,90419,90420,90422],{"class":681,"line":682},[679,90421,4116],{"class":693},[679,90423,6068],{"class":685},[679,90425,90426,90428,90431],{"class":681,"line":790},[679,90427,4116],{"class":693},[679,90429,90430],{"class":685},"EnableConfigurationProperties",[679,90432,90433],{"class":693},"(RsaKeyProperties.class)\n",[679,90435,90436,90438,90440,90443],{"class":681,"line":892},[679,90437,6073],{"class":685},[679,90439,4512],{"class":685},[679,90441,90442],{"class":880}," JwtDemoApplication",[679,90444,884],{"class":693},[679,90446,90447],{"class":681,"line":901},[679,90448,889],{"emptyLinePlaceholder":797},[679,90450,90451,90453,90455,90457,90459,90461,90463,90465,90467],{"class":681,"line":909},[679,90452,6089],{"class":685},[679,90454,6092],{"class":685},[679,90456,6095],{"class":685},[679,90458,6098],{"class":880},[679,90460,745],{"class":693},[679,90462,4758],{"class":685},[679,90464,16901],{"class":693},[679,90466,6108],{"class":2099},[679,90468,4390],{"class":693},[679,90470,90471,90473,90475],{"class":681,"line":918},[679,90472,6115],{"class":693},[679,90474,6118],{"class":880},[679,90476,90477],{"class":693},"(JwtDemoApplication.class, args);\n",[679,90479,90480],{"class":681,"line":935},[679,90481,985],{"class":693},[679,90483,90484],{"class":681,"line":944},[679,90485,889],{"emptyLinePlaceholder":797},[679,90487,90488],{"class":681,"line":959},[679,90489,996],{"class":693},[651,90491,90492,90493,90495],{},"Back in ",[676,90494,89455],{},", you can get an instance of that autowired in for you:",[669,90497,90499],{"className":4107,"code":90498,"language":4109,"meta":674,"style":674},"@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n private final RsaKeyProperties rsaKeys;\n\n public SecurityConfig(RsaKeyProperties rsaKeys) {\n this.rsaKeys = rsaKeys;\n }\n",[676,90500,90501,90507,90513,90523,90527,90536,90540,90554,90566],{"__ignoreMap":674},[679,90502,90503,90505],{"class":681,"line":682},[679,90504,4116],{"class":693},[679,90506,6212],{"class":685},[679,90508,90509,90511],{"class":681,"line":790},[679,90510,4116],{"class":693},[679,90512,89474],{"class":685},[679,90514,90515,90517,90519,90521],{"class":681,"line":892},[679,90516,6073],{"class":685},[679,90518,4512],{"class":685},[679,90520,89483],{"class":880},[679,90522,884],{"class":693},[679,90524,90525],{"class":681,"line":901},[679,90526,889],{"emptyLinePlaceholder":797},[679,90528,90529,90531,90533],{"class":681,"line":909},[679,90530,9232],{"class":685},[679,90532,12768],{"class":685},[679,90534,90535],{"class":693}," RsaKeyProperties rsaKeys;\n",[679,90537,90538],{"class":681,"line":918},[679,90539,889],{"emptyLinePlaceholder":797},[679,90541,90542,90544,90546,90549,90552],{"class":681,"line":935},[679,90543,6089],{"class":685},[679,90545,89483],{"class":880},[679,90547,90548],{"class":693},"(RsaKeyProperties ",[679,90550,90551],{"class":2099},"rsaKeys",[679,90553,4390],{"class":693},[679,90555,90556,90558,90561,90563],{"class":681,"line":944},[679,90557,7862],{"class":931},[679,90559,90560],{"class":693},".rsaKeys ",[679,90562,686],{"class":685},[679,90564,90565],{"class":693}," rsaKeys;\n",[679,90567,90568],{"class":681,"line":959},[679,90569,985],{"class":693},[651,90571,90572,90573,90575,90576,90581],{},"Now you can create a ",[676,90574,90187],{}," using the public key. This is something you would normally need to bring in a third-party library for but you won’t need to. One of the dependencies that the resource server brings in for you is ``spring-security-oauth2-jose` which contains a library called ",[812,90577,90580],{"href":90578,"rel":90579},"https://connect2id.com/products/nimbus-jose-jwt",[816],"Nimbus Jose JWT",". You can return a Nimbus JWT Decoder using the public key you just created.",[669,90583,90585],{"className":4107,"code":90584,"language":4109,"meta":674,"style":674},"@Bean\nJwtDecoder jwtDecoder() {\n return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build();\n}\n",[676,90586,90587,90593,90603,90626],{"__ignoreMap":674},[679,90588,90589,90591],{"class":681,"line":682},[679,90590,4116],{"class":693},[679,90592,16929],{"class":685},[679,90594,90595,90598,90601],{"class":681,"line":790},[679,90596,90597],{"class":693},"JwtDecoder ",[679,90599,90600],{"class":880},"jwtDecoder",[679,90602,2667],{"class":693},[679,90604,90605,90607,90610,90613,90616,90619,90622,90624],{"class":681,"line":892},[679,90606,21478],{"class":685},[679,90608,90609],{"class":693}," NimbusJwtDecoder.",[679,90611,90612],{"class":880},"withPublicKey",[679,90614,90615],{"class":693},"(rsaKeys.",[679,90617,90618],{"class":880},"publicKey",[679,90620,90621],{"class":693},"()).",[679,90623,23612],{"class":880},[679,90625,9317],{"class":693},[679,90627,90628],{"class":681,"line":901},[679,90629,996],{"class":693},[651,90631,90632],{},"At this point, you should be able to run the application without any errors.",[4542,90634,90636],{"id":90635},"auth-controller-token-service","Auth Controller & Token Service",[651,90638,90639],{},"You have the keys in place and you have defined a decoder which is a way to decipher the JWT. If you remember back to our architecture diagrams earlier the user will need to log in with their username and password. If they pass authentication you will generate a new JSON Web Token and send it back in the response.",[651,90641,90642],{},[660,90643],{"alt":89216,"src":89217},[651,90645,90646,90647,90650,90651,90653],{},"To do this you first need to create a bean of type ",[676,90648,90649],{},"JwtEncoder"," and you can do this in the ",[676,90652,89455],{},". The encoder will be used to encode the signature we learned about earlier into a token and sign it using our private key.",[669,90655,90657],{"className":4107,"code":90656,"language":4109,"meta":674,"style":674},"@Bean\nJwtEncoder jwtEncoder() {\n JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build();\n JWKSource\u003CSecurityContext> jwks = new ImmutableJWKSet\u003C>(new JWKSet(jwk));\n return new NimbusJwtEncoder(jwks);\n}\n",[676,90658,90659,90665,90675,90709,90735,90747],{"__ignoreMap":674},[679,90660,90661,90663],{"class":681,"line":682},[679,90662,4116],{"class":693},[679,90664,16929],{"class":685},[679,90666,90667,90670,90673],{"class":681,"line":790},[679,90668,90669],{"class":693},"JwtEncoder ",[679,90671,90672],{"class":880},"jwtEncoder",[679,90674,2667],{"class":693},[679,90676,90677,90680,90682,90684,90687,90690,90692,90694,90696,90699,90701,90703,90705,90707],{"class":681,"line":892},[679,90678,90679],{"class":693}," JWK jwk ",[679,90681,686],{"class":685},[679,90683,2054],{"class":685},[679,90685,90686],{"class":693}," RSAKey.",[679,90688,90689],{"class":880},"Builder",[679,90691,90615],{"class":693},[679,90693,90618],{"class":880},[679,90695,90621],{"class":693},[679,90697,90698],{"class":880},"privateKey",[679,90700,90615],{"class":693},[679,90702,90698],{"class":880},[679,90704,90621],{"class":693},[679,90706,23612],{"class":880},[679,90708,9317],{"class":693},[679,90710,90711,90714,90717,90720,90722,90724,90727,90729,90732],{"class":681,"line":901},[679,90712,90713],{"class":693}," JWKSource\u003C",[679,90715,90716],{"class":685},"SecurityContext",[679,90718,90719],{"class":693},"> jwks ",[679,90721,686],{"class":685},[679,90723,2054],{"class":685},[679,90725,90726],{"class":693}," ImmutableJWKSet\u003C>(",[679,90728,8930],{"class":685},[679,90730,90731],{"class":880}," JWKSet",[679,90733,90734],{"class":693},"(jwk));\n",[679,90736,90737,90739,90741,90744],{"class":681,"line":909},[679,90738,21478],{"class":685},[679,90740,2054],{"class":685},[679,90742,90743],{"class":880}," NimbusJwtEncoder",[679,90745,90746],{"class":693},"(jwks);\n",[679,90748,90749],{"class":681,"line":918},[679,90750,996],{"class":693},[651,90752,90753,90754,90757,90758,90760,90761,90763],{},"You could just use the encoder directly in the authentication controller but I feel like is something you should extract out to a service layer. Create a new class called ",[676,90755,90756],{},"TokenService"," in a new package called ",[676,90759,27529],{}," which will use the new ",[676,90762,90649],{}," to generate a token. In the following example, the token will expire after 1 hour but you can adjust it to fit your needs.",[669,90765,90767],{"className":4107,"code":90766,"language":4109,"meta":674,"style":674},"@Service\npublic class TokenService {\n\n private final JwtEncoder encoder;\n\n public TokenService(JwtEncoder encoder) {\n this.encoder = encoder;\n }\n\n public String generateToken(Authentication authentication) {\n Instant now = Instant.now();\n String scope = authentication.getAuthorities().stream()\n .map(GrantedAuthority::getAuthority)\n .collect(Collectors.joining(\" \"));\n JwtClaimsSet claims = JwtClaimsSet.builder()\n .issuer(\"self\")\n .issuedAt(now)\n .expiresAt(now.plus(1, ChronoUnit.HOURS))\n .subject(authentication.getName())\n .claim(\"scope\", scope)\n .build();\n return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();\n }\n\n}\n",[676,90768,90769,90775,90786,90790,90799,90803,90817,90829,90833,90837,90854,90869,90888,90902,90922,90937,90951,90960,90980,90994,91009,91017,91041,91045,91049],{"__ignoreMap":674},[679,90770,90771,90773],{"class":681,"line":682},[679,90772,4116],{"class":693},[679,90774,9486],{"class":685},[679,90776,90777,90779,90781,90784],{"class":681,"line":790},[679,90778,6073],{"class":685},[679,90780,4512],{"class":685},[679,90782,90783],{"class":880}," TokenService",[679,90785,884],{"class":693},[679,90787,90788],{"class":681,"line":892},[679,90789,889],{"emptyLinePlaceholder":797},[679,90791,90792,90794,90796],{"class":681,"line":901},[679,90793,9232],{"class":685},[679,90795,12768],{"class":685},[679,90797,90798],{"class":693}," JwtEncoder encoder;\n",[679,90800,90801],{"class":681,"line":909},[679,90802,889],{"emptyLinePlaceholder":797},[679,90804,90805,90807,90809,90812,90815],{"class":681,"line":918},[679,90806,6089],{"class":685},[679,90808,90783],{"class":880},[679,90810,90811],{"class":693},"(JwtEncoder ",[679,90813,90814],{"class":2099},"encoder",[679,90816,4390],{"class":693},[679,90818,90819,90821,90824,90826],{"class":681,"line":935},[679,90820,7862],{"class":931},[679,90822,90823],{"class":693},".encoder ",[679,90825,686],{"class":685},[679,90827,90828],{"class":693}," encoder;\n",[679,90830,90831],{"class":681,"line":944},[679,90832,985],{"class":693},[679,90834,90835],{"class":681,"line":959},[679,90836,889],{"emptyLinePlaceholder":797},[679,90838,90839,90841,90843,90846,90849,90852],{"class":681,"line":964},[679,90840,6089],{"class":685},[679,90842,9289],{"class":693},[679,90844,90845],{"class":880},"generateToken",[679,90847,90848],{"class":693},"(Authentication ",[679,90850,90851],{"class":2099},"authentication",[679,90853,4390],{"class":693},[679,90855,90856,90859,90861,90864,90867],{"class":681,"line":977},[679,90857,90858],{"class":693}," Instant now ",[679,90860,686],{"class":685},[679,90862,90863],{"class":693}," Instant.",[679,90865,90866],{"class":880},"now",[679,90868,9317],{"class":693},[679,90870,90871,90874,90876,90879,90882,90884,90886],{"class":681,"line":982},[679,90872,90873],{"class":693}," String scope ",[679,90875,686],{"class":685},[679,90877,90878],{"class":693}," authentication.",[679,90880,90881],{"class":880},"getAuthorities",[679,90883,10541],{"class":693},[679,90885,87323],{"class":880},[679,90887,17545],{"class":693},[679,90889,90890,90892,90894,90897,90899],{"class":681,"line":988},[679,90891,73482],{"class":693},[679,90893,51904],{"class":880},[679,90895,90896],{"class":693},"(GrantedAuthority",[679,90898,90007],{"class":685},[679,90900,90901],{"class":693},"getAuthority)\n",[679,90903,90904,90906,90909,90912,90915,90917,90920],{"class":681,"line":993},[679,90905,73482],{"class":693},[679,90907,90908],{"class":880},"collect",[679,90910,90911],{"class":693},"(Collectors.",[679,90913,90914],{"class":880},"joining",[679,90916,745],{"class":693},[679,90918,90919],{"class":689},"\" \"",[679,90921,1669],{"class":693},[679,90923,90924,90927,90929,90932,90935],{"class":681,"line":2129},[679,90925,90926],{"class":693}," JwtClaimsSet claims ",[679,90928,686],{"class":685},[679,90930,90931],{"class":693}," JwtClaimsSet.",[679,90933,90934],{"class":880},"builder",[679,90936,17545],{"class":693},[679,90938,90939,90941,90944,90946,90949],{"class":681,"line":2140},[679,90940,73482],{"class":693},[679,90942,90943],{"class":880},"issuer",[679,90945,745],{"class":693},[679,90947,90948],{"class":689},"\"self\"",[679,90950,1339],{"class":693},[679,90952,90953,90955,90958],{"class":681,"line":2145},[679,90954,73482],{"class":693},[679,90956,90957],{"class":880},"issuedAt",[679,90959,17584],{"class":693},[679,90961,90962,90964,90967,90970,90973,90975,90977],{"class":681,"line":2154},[679,90963,73482],{"class":693},[679,90965,90966],{"class":880},"expiresAt",[679,90968,90969],{"class":693},"(now.",[679,90971,90972],{"class":880},"plus",[679,90974,745],{"class":693},[679,90976,1557],{"class":931},[679,90978,90979],{"class":693},", ChronoUnit.HOURS))\n",[679,90981,90982,90984,90987,90990,90992],{"class":681,"line":2159},[679,90983,73482],{"class":693},[679,90985,90986],{"class":880},"subject",[679,90988,90989],{"class":693},"(authentication.",[679,90991,10577],{"class":880},[679,90993,40172],{"class":693},[679,90995,90996,90998,91001,91003,91006],{"class":681,"line":2164},[679,90997,73482],{"class":693},[679,90999,91000],{"class":880},"claim",[679,91002,745],{"class":693},[679,91004,91005],{"class":689},"\"scope\"",[679,91007,91008],{"class":693},", scope)\n",[679,91010,91011,91013,91015],{"class":681,"line":3134},[679,91012,73482],{"class":693},[679,91014,23612],{"class":880},[679,91016,9317],{"class":693},[679,91018,91019,91021,91023,91026,91028,91031,91033,91036,91039],{"class":681,"line":3139},[679,91020,9444],{"class":685},[679,91022,21353],{"class":931},[679,91024,91025],{"class":693},".encoder.",[679,91027,76840],{"class":880},[679,91029,91030],{"class":693},"(JwtEncoderParameters.",[679,91032,28887],{"class":880},[679,91034,91035],{"class":693},"(claims)).",[679,91037,91038],{"class":880},"getTokenValue",[679,91040,9317],{"class":693},[679,91042,91043],{"class":681,"line":3144},[679,91044,985],{"class":693},[679,91046,91047],{"class":681,"line":3149},[679,91048,889],{"emptyLinePlaceholder":797},[679,91050,91051],{"class":681,"line":3169},[679,91052,996],{"class":693},[651,91054,91055,91056,89452,91058,91061],{},"Next create a new controller in the ",[676,91057,4279],{},[676,91059,91060],{},"AuthController",". This is going to contain a single POST method that will use your new token service to generate a token for the authenticated user. As you can see there is some logging for debugging purposes so that in development you will see the user requesting a JWT and the token that was created.",[669,91063,91065],{"className":4107,"code":91064,"language":4109,"meta":674,"style":674},"@RestController\npublic class AuthController {\n\n private static final Logger LOG = LoggerFactory.getLogger(AuthController.class);\n\n private final TokenService tokenService;\n\n public AuthController(TokenService tokenService) {\n this.tokenService = tokenService;\n }\n\n @PostMapping(\"/token\")\n public String token(Authentication authentication) {\n LOG.debug(\"Token requested for user: '{}'\", authentication.getName());\n String token = tokenService.generateToken(authentication);\n LOG.debug(\"Token granted: {}\", token);\n return token;\n }\n\n}\n",[676,91066,91067,91073,91084,91088,91108,91112,91121,91125,91139,91151,91155,91159,91173,91188,91208,91223,91237,91244,91248,91252],{"__ignoreMap":674},[679,91068,91069,91071],{"class":681,"line":682},[679,91070,4116],{"class":693},[679,91072,9212],{"class":685},[679,91074,91075,91077,91079,91082],{"class":681,"line":790},[679,91076,6073],{"class":685},[679,91078,4512],{"class":685},[679,91080,91081],{"class":880}," AuthController",[679,91083,884],{"class":693},[679,91085,91086],{"class":681,"line":892},[679,91087,889],{"emptyLinePlaceholder":797},[679,91089,91090,91092,91094,91096,91099,91101,91103,91105],{"class":681,"line":901},[679,91091,9232],{"class":685},[679,91093,6092],{"class":685},[679,91095,12768],{"class":685},[679,91097,91098],{"class":693}," Logger LOG ",[679,91100,686],{"class":685},[679,91102,9240],{"class":693},[679,91104,9243],{"class":880},[679,91106,91107],{"class":693},"(AuthController.class);\n",[679,91109,91110],{"class":681,"line":909},[679,91111,889],{"emptyLinePlaceholder":797},[679,91113,91114,91116,91118],{"class":681,"line":918},[679,91115,9232],{"class":685},[679,91117,12768],{"class":685},[679,91119,91120],{"class":693}," TokenService tokenService;\n",[679,91122,91123],{"class":681,"line":935},[679,91124,889],{"emptyLinePlaceholder":797},[679,91126,91127,91129,91131,91134,91137],{"class":681,"line":944},[679,91128,6089],{"class":685},[679,91130,91081],{"class":880},[679,91132,91133],{"class":693},"(TokenService ",[679,91135,91136],{"class":2099},"tokenService",[679,91138,4390],{"class":693},[679,91140,91141,91143,91146,91148],{"class":681,"line":959},[679,91142,7862],{"class":931},[679,91144,91145],{"class":693},".tokenService ",[679,91147,686],{"class":685},[679,91149,91150],{"class":693}," tokenService;\n",[679,91152,91153],{"class":681,"line":964},[679,91154,985],{"class":693},[679,91156,91157],{"class":681,"line":977},[679,91158,889],{"emptyLinePlaceholder":797},[679,91160,91161,91163,91166,91168,91171],{"class":681,"line":982},[679,91162,6872],{"class":693},[679,91164,91165],{"class":685},"PostMapping",[679,91167,745],{"class":693},[679,91169,91170],{"class":689},"\"/token\"",[679,91172,1339],{"class":693},[679,91174,91175,91177,91179,91182,91184,91186],{"class":681,"line":988},[679,91176,6089],{"class":685},[679,91178,9289],{"class":693},[679,91180,91181],{"class":880},"token",[679,91183,90848],{"class":693},[679,91185,90851],{"class":2099},[679,91187,4390],{"class":693},[679,91189,91190,91193,91196,91198,91201,91204,91206],{"class":681,"line":993},[679,91191,91192],{"class":693}," LOG.",[679,91194,91195],{"class":880},"debug",[679,91197,745],{"class":693},[679,91199,91200],{"class":689},"\"Token requested for user: '{}'\"",[679,91202,91203],{"class":693},", authentication.",[679,91205,10577],{"class":880},[679,91207,9431],{"class":693},[679,91209,91210,91213,91215,91218,91220],{"class":681,"line":2129},[679,91211,91212],{"class":693}," String token ",[679,91214,686],{"class":685},[679,91216,91217],{"class":693}," tokenService.",[679,91219,90845],{"class":880},[679,91221,91222],{"class":693},"(authentication);\n",[679,91224,91225,91227,91229,91231,91234],{"class":681,"line":2140},[679,91226,91192],{"class":693},[679,91228,91195],{"class":880},[679,91230,745],{"class":693},[679,91232,91233],{"class":689},"\"Token granted: {}\"",[679,91235,91236],{"class":693},", token);\n",[679,91238,91239,91241],{"class":681,"line":2145},[679,91240,9444],{"class":685},[679,91242,91243],{"class":693}," token;\n",[679,91245,91246],{"class":681,"line":2154},[679,91247,985],{"class":693},[679,91249,91250],{"class":681,"line":2159},[679,91251,889],{"emptyLinePlaceholder":797},[679,91253,91254],{"class":681,"line":2164},[679,91255,996],{"class":693},[651,91257,91258],{},"If everything was done correctly you should be able to start your application without error.",[4542,91260,91262],{"id":91261},"spring-security-jwt-testing","Spring Security JWT Testing",[651,91264,91265],{},"With that, you should have your root path secured using JWT. Now you just need to test it.",[5909,91267,91269],{"id":91268},"manual-testing","Manual Testing.",[651,91271,91272],{},"There are many ways that you can manually test this but In this tutorial, I will show you 2.",[651,91274,91275],{},[2939,91276,91277],{},"Postman",[651,91279,91280],{},"An easy way to test this is by using a tool like Postman. If you create a new POST request to the token endpoint you can select Basic Auth from the Authorization tab and enter your credentials. If everything works you will get back the generated JWT in the response.",[651,91282,91283],{},[660,91284],{"alt":91285,"src":91286},"Postman Basic Auth","/images/blog/2022/09/09/postman-basic-auth.png",[651,91288,91289,91290,91293,91294,664],{},"Copy the JWT and create a new GET request for ",[812,91291,11697],{"href":11697,"rel":91292},[816],". Go to the Authorization tab and select Bearer Token and paste in the generated token. If you send the request you should get back the string returned from the home method in the ",[676,91295,89333],{},[651,91297,91298],{},[660,91299],{"alt":91300,"src":91301},"Postman with JWT Response","/images/blog/2022/09/09/postman-with-jwt-response.png",[651,91303,91304],{},[2939,91305,91306],{},"CommandLine",[651,91308,91309,91310,91315],{},"I’m a big fan of the command line and a tool called ",[812,91311,91314],{"href":91312,"rel":91313},"https://httpie.io/",[816],"httpie",". It simplifies writing commands for testing our APIs in the terminal. You can send a request to the token endpoint with your credentials using the following command:",[669,91317,91319],{"className":5851,"code":91318,"language":5853,"meta":674,"style":674},"http POST :8080/token --auth dvega:password -v\n",[676,91320,91321],{"__ignoreMap":674},[679,91322,91323,91325,91328,91331,91334,91337],{"class":681,"line":682},[679,91324,89511],{"class":880},[679,91326,91327],{"class":689}," POST",[679,91329,91330],{"class":689}," :8080/token",[679,91332,91333],{"class":931}," --auth",[679,91335,91336],{"class":689}," dvega:password",[679,91338,27336],{"class":931},[651,91340,40060,91341,91344],{},[676,91342,91343],{},"-v"," argument will print the request and the response",[651,91346,91347],{},[660,91348],{"alt":91349,"src":91350},"Httpie with Authorization","/images/blog/2022/09/09/httpie-auth.png",[651,91352,91353,91354,91357,91358,664],{},"The response will contain the generated JWT token. If you make a request to the root path without the authorization header or without the correct token you will receive a ",[7300,91355,91356],{},"401 (Denied)"," response. If however, you include the Authorization header in the correct format you will get the string returned from the home method in the ",[676,91359,89333],{},[669,91361,91363],{"className":5851,"code":91362,"language":5853,"meta":674,"style":674},"http :8080 'Authorization: Bearer JWT_TOKEN_HERE'\n",[676,91364,91365],{"__ignoreMap":674},[679,91366,91367,91369,91372],{"class":681,"line":682},[679,91368,89511],{"class":880},[679,91370,91371],{"class":689}," :8080",[679,91373,91374],{"class":689}," 'Authorization: Bearer JWT_TOKEN_HERE'\n",[651,91376,91377],{},[660,91378],{"alt":91379,"src":91380},"Httpie Response Success","/images/blog/2022/09/09/httpie-success.png",[5909,91382,91384],{"id":91383},"automated-testing","Automated Testing",[651,91386,91387],{},"Manually testing is great because you can see that everything is working as intended. However, you will need some automated tests in place so that as you make changes you can be confident that nothing has broken the existing functionality. I am not going to go to much into this but I wanted to provide you with a simple example of how to write such a test.",[651,91389,91390,91391,91394,91395,664],{},"When you brought in the resource server there was one dependency that didn’t get brought in and that is ",[676,91392,91393],{},"spring-security-test",". Before writing any security-related tests you will need to add this to your ",[676,91396,81517],{},[669,91398,91400],{"className":9101,"code":91399,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.security\u003C/groupId>\n \u003CartifactId>spring-security-test\u003C/artifactId>\n\u003C/dependency>\n",[676,91401,91402,91406,91411,91416],{"__ignoreMap":674},[679,91403,91404],{"class":681,"line":682},[679,91405,9110],{},[679,91407,91408],{"class":681,"line":790},[679,91409,91410],{}," \u003CgroupId>org.springframework.security\u003C/groupId>\n",[679,91412,91413],{"class":681,"line":892},[679,91414,91415],{}," \u003CartifactId>spring-security-test\u003C/artifactId>\n",[679,91417,91418],{"class":681,"line":901},[679,91419,9125],{},[651,91421,91422,91423,23212,91426,91428],{},"When you write a slice test that focuses on just the web layer configuration and service classes will not be added to the application context. To make everything work you will need to manually import the ",[676,91424,91425],{},"SercurityConfig",[676,91427,90756],{}," classes. These tests should be pretty self-explanatory but if you would like me to go though them please reach out and let me know.",[669,91430,91432],{"className":4107,"code":91431,"language":4109,"meta":674,"style":674},"@WebMvcTest({HomeController.class, AuthController.class})\n@Import({SecurityConfig.class, TokenService.class})\nclass HomeControllerTest {\n\n @Autowired\n MockMvc mvc;\n\n @Test\n void rootWhenUnauthenticatedThen401() throws Exception {\n this.mvc.perform(get(\"/\"))\n .andExpect(status().isUnauthorized());\n }\n\n @Test\n void rootWhenAuthenticatedThenSaysHelloUser() throws Exception {\n MvcResult result = this.mvc.perform(post(\"/token\")\n .with(httpBasic(\"dvega\", \"password\")))\n .andExpect(status().isOk())\n .andReturn();\n\n String token = result.getResponse().getContentAsString();\n\n this.mvc.perform(get(\"/\")\n .header(\"Authorization\", \"Bearer \" + token))\n .andExpect(content().string(\"Hello, dvega\"));\n }\n\n @Test\n @WithMockUser\n public void rootWithMockUserStatusIsOK() throws Exception {\n this.mvc.perform(get(\"/\")).andExpect(status().isOk());\n }\n\n}\n",[676,91433,91434,91443,91453,91462,91466,91472,91477,91481,91487,91500,91519,91536,91540,91544,91550,91563,91586,91608,91624,91633,91637,91656,91660,91678,91700,91721,91725,91729,91735,91742,91757,91787,91791,91795],{"__ignoreMap":674},[679,91435,91436,91438,91440],{"class":681,"line":682},[679,91437,4116],{"class":693},[679,91439,73398],{"class":685},[679,91441,91442],{"class":693},"({HomeController.class, AuthController.class})\n",[679,91444,91445,91447,91450],{"class":681,"line":790},[679,91446,4116],{"class":693},[679,91448,91449],{"class":685},"Import",[679,91451,91452],{"class":693},"({SecurityConfig.class, TokenService.class})\n",[679,91454,91455,91457,91460],{"class":681,"line":892},[679,91456,877],{"class":685},[679,91458,91459],{"class":880}," HomeControllerTest",[679,91461,884],{"class":693},[679,91463,91464],{"class":681,"line":901},[679,91465,889],{"emptyLinePlaceholder":797},[679,91467,91468,91470],{"class":681,"line":909},[679,91469,6872],{"class":693},[679,91471,9257],{"class":685},[679,91473,91474],{"class":681,"line":918},[679,91475,91476],{"class":693}," MockMvc mvc;\n",[679,91478,91479],{"class":681,"line":935},[679,91480,889],{"emptyLinePlaceholder":797},[679,91482,91483,91485],{"class":681,"line":944},[679,91484,6872],{"class":693},[679,91486,73087],{"class":685},[679,91488,91489,91491,91494,91496,91498],{"class":681,"line":959},[679,91490,3314],{"class":685},[679,91492,91493],{"class":880}," rootWhenUnauthenticatedThen401",[679,91495,6700],{"class":693},[679,91497,9580],{"class":685},[679,91499,10466],{"class":693},[679,91501,91502,91504,91507,91509,91511,91513,91515,91517],{"class":681,"line":964},[679,91503,7862],{"class":931},[679,91505,91506],{"class":693},".mvc.",[679,91508,73467],{"class":880},[679,91510,745],{"class":693},[679,91512,7626],{"class":880},[679,91514,745],{"class":693},[679,91516,10032],{"class":689},[679,91518,40143],{"class":693},[679,91520,91521,91523,91525,91527,91529,91531,91534],{"class":681,"line":977},[679,91522,73482],{"class":693},[679,91524,73485],{"class":880},[679,91526,745],{"class":693},[679,91528,24114],{"class":880},[679,91530,10541],{"class":693},[679,91532,91533],{"class":880},"isUnauthorized",[679,91535,9431],{"class":693},[679,91537,91538],{"class":681,"line":982},[679,91539,985],{"class":693},[679,91541,91542],{"class":681,"line":988},[679,91543,889],{"emptyLinePlaceholder":797},[679,91545,91546,91548],{"class":681,"line":993},[679,91547,6872],{"class":693},[679,91549,73087],{"class":685},[679,91551,91552,91554,91557,91559,91561],{"class":681,"line":2129},[679,91553,3314],{"class":685},[679,91555,91556],{"class":880}," rootWhenAuthenticatedThenSaysHelloUser",[679,91558,6700],{"class":693},[679,91560,9580],{"class":685},[679,91562,10466],{"class":693},[679,91564,91565,91568,91570,91572,91574,91576,91578,91580,91582,91584],{"class":681,"line":2140},[679,91566,91567],{"class":693}," MvcResult result ",[679,91569,686],{"class":685},[679,91571,21353],{"class":931},[679,91573,91506],{"class":693},[679,91575,73467],{"class":880},[679,91577,745],{"class":693},[679,91579,5100],{"class":880},[679,91581,745],{"class":693},[679,91583,91170],{"class":689},[679,91585,1339],{"class":693},[679,91587,91588,91590,91592,91594,91596,91598,91600,91602,91605],{"class":681,"line":2145},[679,91589,89566],{"class":693},[679,91591,25207],{"class":880},[679,91593,745],{"class":693},[679,91595,89615],{"class":880},[679,91597,745],{"class":693},[679,91599,89723],{"class":689},[679,91601,2797],{"class":693},[679,91603,91604],{"class":689},"\"password\"",[679,91606,91607],{"class":693},")))\n",[679,91609,91610,91612,91614,91616,91618,91620,91622],{"class":681,"line":2154},[679,91611,73482],{"class":693},[679,91613,73485],{"class":880},[679,91615,745],{"class":693},[679,91617,24114],{"class":880},[679,91619,10541],{"class":693},[679,91621,73494],{"class":880},[679,91623,40172],{"class":693},[679,91625,91626,91628,91631],{"class":681,"line":2159},[679,91627,73482],{"class":693},[679,91629,91630],{"class":880},"andReturn",[679,91632,9317],{"class":693},[679,91634,91635],{"class":681,"line":2164},[679,91636,889],{"emptyLinePlaceholder":797},[679,91638,91639,91641,91643,91646,91649,91651,91654],{"class":681,"line":3134},[679,91640,91212],{"class":693},[679,91642,686],{"class":685},[679,91644,91645],{"class":693}," result.",[679,91647,91648],{"class":880},"getResponse",[679,91650,10541],{"class":693},[679,91652,91653],{"class":880},"getContentAsString",[679,91655,9317],{"class":693},[679,91657,91658],{"class":681,"line":3139},[679,91659,889],{"emptyLinePlaceholder":797},[679,91661,91662,91664,91666,91668,91670,91672,91674,91676],{"class":681,"line":3144},[679,91663,7862],{"class":931},[679,91665,91506],{"class":693},[679,91667,73467],{"class":880},[679,91669,745],{"class":693},[679,91671,7626],{"class":880},[679,91673,745],{"class":693},[679,91675,10032],{"class":689},[679,91677,1339],{"class":693},[679,91679,91680,91682,91685,91687,91690,91692,91695,91697],{"class":681,"line":3149},[679,91681,89566],{"class":693},[679,91683,91684],{"class":880},"header",[679,91686,745],{"class":693},[679,91688,91689],{"class":689},"\"Authorization\"",[679,91691,2797],{"class":693},[679,91693,91694],{"class":689},"\"Bearer \"",[679,91696,3059],{"class":685},[679,91698,91699],{"class":693}," token))\n",[679,91701,91702,91704,91706,91708,91710,91712,91714,91716,91719],{"class":681,"line":3169},[679,91703,73482],{"class":693},[679,91705,73485],{"class":880},[679,91707,745],{"class":693},[679,91709,47833],{"class":880},[679,91711,10541],{"class":693},[679,91713,73511],{"class":880},[679,91715,745],{"class":693},[679,91717,91718],{"class":689},"\"Hello, dvega\"",[679,91720,1669],{"class":693},[679,91722,91723],{"class":681,"line":3185},[679,91724,985],{"class":693},[679,91726,91727],{"class":681,"line":3194},[679,91728,889],{"emptyLinePlaceholder":797},[679,91730,91731,91733],{"class":681,"line":3199},[679,91732,6872],{"class":693},[679,91734,73087],{"class":685},[679,91736,91737,91739],{"class":681,"line":3212},[679,91738,6872],{"class":693},[679,91740,91741],{"class":685},"WithMockUser\n",[679,91743,91744,91746,91748,91751,91753,91755],{"class":681,"line":3217},[679,91745,6089],{"class":685},[679,91747,6095],{"class":685},[679,91749,91750],{"class":880}," rootWithMockUserStatusIsOK",[679,91752,6700],{"class":693},[679,91754,9580],{"class":685},[679,91756,10466],{"class":693},[679,91758,91759,91761,91763,91765,91767,91769,91771,91773,91775,91777,91779,91781,91783,91785],{"class":681,"line":3222},[679,91760,7862],{"class":931},[679,91762,91506],{"class":693},[679,91764,73467],{"class":880},[679,91766,745],{"class":693},[679,91768,7626],{"class":880},[679,91770,745],{"class":693},[679,91772,10032],{"class":689},[679,91774,65060],{"class":693},[679,91776,73485],{"class":880},[679,91778,745],{"class":693},[679,91780,24114],{"class":880},[679,91782,10541],{"class":693},[679,91784,73494],{"class":880},[679,91786,9431],{"class":693},[679,91788,91789],{"class":681,"line":3227},[679,91790,985],{"class":693},[679,91792,91793],{"class":681,"line":3232},[679,91794,889],{"emptyLinePlaceholder":797},[679,91796,91797],{"class":681,"line":3499},[679,91798,996],{"class":693},[4542,91800,9042],{"id":9041},[651,91802,91803,91804,664],{},"When I started going down the route of creating this tutorial my whole goal was to let you know there was an easier way to secure your APIs using JWTs. My hope is that now that you know Spring Security has built-in support for JSON Web Tokens using the oAuth2 Resource Server you can reach for it in your next project. This was just the starting line of how to use JWTs in your Spring Boot applications and by no means the finish line. If you have questions about your specific configuration please ",[812,91805,91807],{"href":51472,"rel":91806},[816],"reach out to me",[651,91809,91810],{},"I feel very fortunate that I get to work for a company like VMware and that I have access to some really smart people. That access means, even more, when you work with such a great group of people who are always willing to share their knowledge and help out. I would like to give a special shoutout to the following individuals for helping me put this together:",[5316,91812,91813,91816,91819,91822,91825],{},[5332,91814,91815],{},"Daniel Garnier-Moiroux",[5332,91817,91818],{},"Steve Riesenberg",[5332,91820,91821],{},"Rob Winch",[5332,91823,91824],{},"Josh Cummings",[5332,91826,91827],{},"Toshiaki Maki",[786,91829,91830],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":91832},[91833,91836,91837,91838,91839,91845,91846,91850],{"id":89161,"depth":790,"text":89162,"children":91834},[91835],{"id":89184,"depth":892,"text":89185},{"id":41932,"depth":790,"text":39958},{"id":89326,"depth":790,"text":89327},{"id":89435,"depth":790,"text":89436},{"id":89792,"depth":790,"text":89793,"children":91840},[91841,91842,91843,91844],{"id":89855,"depth":892,"text":89856},{"id":90180,"depth":892,"text":90181},{"id":90205,"depth":892,"text":90206},{"id":90327,"depth":892,"text":90187},{"id":90635,"depth":790,"text":90636},{"id":91261,"depth":790,"text":91262,"children":91847},[91848,91849],{"id":91268,"depth":892,"text":91269},{"id":91383,"depth":892,"text":91384},{"id":9041,"depth":790,"text":9042},"In this tutorial, you will learn how to secure REST APIs with Spring Security and JSON Web Tokens (JWTs).",{"slug":91853,"date":91854,"updatedOn":91855,"published":797,"author":798,"tags":91856,"cover":91857,"github":89156,"video":91858,"keywords":91859},"spring-security-jwt","2022-09-09T16:00:00.000Z","2023-12-18T18:00:00.000Z",[7077,23988],"spring-security-jwt-cover.jpeg","https://www.youtube.com/embed/KYNR5js2cXE","spring, spring boot, spring security, java, JWT, JSON Web Tokens, REST API, Spring Security JWT, Spring Boot JWT",{"title":384,"description":91851},"blog/2022/09/09/spring-security-jwt","Aw-JH22LPF-Becruee3OsrQ2DR2G-i91kecu0fdCZnY",{"id":91864,"title":381,"body":91865,"description":849,"extension":793,"meta":92884,"navigation":797,"path":382,"seo":92891,"stem":92892,"__hash__":92893},"content/blog/2022/09/22/spring-security-cors.md",{"type":648,"value":91866,"toc":92874},[91867,91870,91872,91895,91899,91914,91919,91922,91926,91966,92059,92063,92077,92095,92316,92326,92330,92333,92348,92352,92358,92366,92415,92421,92437,92460,92463,92481,92487,92493,92497,92507,92620,92630,92634,92637,92650,92656,92857,92863,92865,92868,92871],[651,91868,91869],{},"Authentication is a vital aspect of most applications, and Spring Boot provides several methods to control access. One of these methods is the use of CORS - a flexible, HTTP header-based mechanism that allows for the specification of authorized cross-domain requests. This article guides you through configuring CORS in Spring Boot at the controller, method, and global levels, as well as managing CORS when adding Spring Security.",[4542,91871,39958],{"id":41932},[651,91873,91874,91875,23212,91879,91883,91884,91888,91889,91894],{},"The tutorial assumes you have ",[812,91876,36422],{"href":91877,"rel":91878},"https://www.java.com/en/download/",[816],[812,91880,38919],{"href":91881,"rel":91882},"https://maven.apache.org/",[816]," installed on your machine. You would also need an IDE such as ",[812,91885,91887],{"href":20753,"rel":91886},[816],"IntelliJ",". All the resources and code used in this article are available ",[812,91890,91893],{"href":91891,"rel":91892},"https://github.com/danvega/spring-security-cors-demo",[816],"on this GitHub repository",". Let's dive right in!",[4542,91896,91898],{"id":91897},"creating-a-new-spring-boot-project","Creating a New Spring Boot Project",[651,91900,91901,91902,91905,91906,91909,91910,91913],{},"Start by creating a new Maven project at ",[812,91903,77478],{"href":7115,"rel":91904},[816],", choosing Java as the language, Java 17 as the version, and fill in the ",[676,91907,91908],{},"Group"," as \"dev.danvega\" and ",[676,91911,91912],{},"Artifact"," as \"cors-demo\". The only dependency we would need for now is Spring Web.",[651,91915,91916],{},[660,91917],{"alt":7117,"src":91918},"/images/blog/2022/09/22/cors-spring-init.png",[651,91920,91921],{},"Once done with the setup, generate the project and open the downloaded .zip file in your preferred text editor or IDE.",[4542,91923,91925],{"id":91924},"building-our-model","Building Our Model",[651,91927,91928,91929,91932,91933,91935,91936,91939,91940,91942,91943,91945,91946,91948,91949,63504,91951,91954,91955,2797,91957,2797,91960,48406,91963,664],{},"For our demo, we will create a ",[676,91930,91931],{},"Coffee"," model. In the ",[676,91934,77538],{}," package within the ",[676,91937,91938],{},"src"," folder, create a new package named ",[676,91941,10048],{},". Within the ",[676,91944,10048],{}," package, create a new class named ",[676,91947,91931],{},". Here, we will create an ",[676,91950,27495],{},[676,91952,91953],{},"Size"," that contains the values ",[676,91956,1068],{},[676,91958,91959],{},"tall",[676,91961,91962],{},"grande",[676,91964,91965],{},"venti",[669,91967,91969],{"className":4107,"code":91968,"language":4109,"meta":674,"style":674},"public class Coffee {\n private Integer id;\n private String name;\n private Size size;\n\n // TODO: Add getters and setters\n}\n\npublic enum Size {\n SHORT,\n TALL,\n GRANDE,\n VENTI\n}\n",[676,91970,91971,91982,91988,91994,92001,92005,92010,92014,92018,92029,92036,92043,92050,92055],{"__ignoreMap":674},[679,91972,91973,91975,91977,91980],{"class":681,"line":682},[679,91974,6073],{"class":685},[679,91976,4512],{"class":685},[679,91978,91979],{"class":880}," Coffee",[679,91981,884],{"class":693},[679,91983,91984,91986],{"class":681,"line":790},[679,91985,9232],{"class":685},[679,91987,83952],{"class":693},[679,91989,91990,91992],{"class":681,"line":892},[679,91991,9232],{"class":685},[679,91993,16319],{"class":693},[679,91995,91996,91998],{"class":681,"line":901},[679,91997,9232],{"class":685},[679,91999,92000],{"class":693}," Size size;\n",[679,92002,92003],{"class":681,"line":909},[679,92004,889],{"emptyLinePlaceholder":797},[679,92006,92007],{"class":681,"line":918},[679,92008,92009],{"class":1400}," // TODO: Add getters and setters\n",[679,92011,92012],{"class":681,"line":935},[679,92013,996],{"class":693},[679,92015,92016],{"class":681,"line":944},[679,92017,889],{"emptyLinePlaceholder":797},[679,92019,92020,92022,92024,92027],{"class":681,"line":959},[679,92021,6073],{"class":685},[679,92023,87014],{"class":685},[679,92025,92026],{"class":880}," Size",[679,92028,884],{"class":693},[679,92030,92031,92034],{"class":681,"line":964},[679,92032,92033],{"class":931}," SHORT",[679,92035,12083],{"class":693},[679,92037,92038,92041],{"class":681,"line":977},[679,92039,92040],{"class":931}," TALL",[679,92042,12083],{"class":693},[679,92044,92045,92048],{"class":681,"line":982},[679,92046,92047],{"class":931}," GRANDE",[679,92049,12083],{"class":693},[679,92051,92052],{"class":681,"line":988},[679,92053,92054],{"class":931}," VENTI\n",[679,92056,92057],{"class":681,"line":993},[679,92058,996],{"class":693},[11859,92060,92062],{"id":92061},"creating-our-controller","Creating Our Controller",[651,92064,92065,92066,89334,92068,92070,92071,92073,92074,664],{},"To begin, create a new package called ",[676,92067,4279],{},[676,92069,77538],{}," package. Inside the ",[676,92072,4279],{}," package, create a new Java class called ",[676,92075,92076],{},"CoffeeController",[651,92078,56985,92079,92081,92082,92084,92085,92087,92088,92090,92091,92094],{},[676,92080,92076],{}," class, we will use the ",[676,92083,12329],{}," annotation to indicate that the class is capable of accepting requests and returning responses. We will also set the ",[676,92086,22845],{}," to \"API/coffee\". Finally, we will create a list of coffees and define two mappings: a ",[676,92089,73296],{}," to return all the coffees, and a ",[676,92092,92093],{},"@DeleteMapping"," to delete a specific coffee by ID.",[669,92096,92098],{"className":4107,"code":92097,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"API/coffee\")\npublic class CoffeeController {\n private List\u003CCoffee> coffeeList = new ArrayList\u003C>(Arrays.asList(\n new Coffee(1, \"Americano\", Size.GRANDE),\n new Coffee(2, \"Latte\", Size.VENTI),\n new Coffee(3, \"Macchiato\", Size.TALL)\n ));\n\n @GetMapping\n public List\u003CCoffee> findAll() {\n return coffeeList;\n }\n\n @DeleteMapping(\"/{id}\")\n public void delete(@PathVariable Integer id) {\n coffeeList.removeIf(c -> c.getId().equals(id));\n }\n}\n\n",[676,92099,92100,92106,92119,92130,92152,92170,92188,92206,92211,92215,92221,92235,92242,92246,92250,92264,92284,92308,92312],{"__ignoreMap":674},[679,92101,92102,92104],{"class":681,"line":682},[679,92103,4116],{"class":693},[679,92105,9212],{"class":685},[679,92107,92108,92110,92112,92114,92117],{"class":681,"line":790},[679,92109,4116],{"class":693},[679,92111,9275],{"class":685},[679,92113,745],{"class":693},[679,92115,92116],{"class":689},"\"API/coffee\"",[679,92118,1339],{"class":693},[679,92120,92121,92123,92125,92128],{"class":681,"line":892},[679,92122,6073],{"class":685},[679,92124,4512],{"class":685},[679,92126,92127],{"class":880}," CoffeeController",[679,92129,884],{"class":693},[679,92131,92132,92134,92136,92138,92141,92143,92145,92148,92150],{"class":681,"line":901},[679,92133,9232],{"class":685},[679,92135,87217],{"class":693},[679,92137,91931],{"class":685},[679,92139,92140],{"class":693},"> coffeeList ",[679,92142,686],{"class":685},[679,92144,2054],{"class":685},[679,92146,92147],{"class":693}," ArrayList\u003C>(Arrays.",[679,92149,38277],{"class":880},[679,92151,21337],{"class":693},[679,92153,92154,92156,92158,92160,92162,92164,92167],{"class":681,"line":909},[679,92155,84746],{"class":685},[679,92157,91979],{"class":880},[679,92159,745],{"class":693},[679,92161,1557],{"class":931},[679,92163,2797],{"class":693},[679,92165,92166],{"class":689},"\"Americano\"",[679,92168,92169],{"class":693},", Size.GRANDE),\n",[679,92171,92172,92174,92176,92178,92180,92182,92185],{"class":681,"line":918},[679,92173,84746],{"class":685},[679,92175,91979],{"class":880},[679,92177,745],{"class":693},[679,92179,17262],{"class":931},[679,92181,2797],{"class":693},[679,92183,92184],{"class":689},"\"Latte\"",[679,92186,92187],{"class":693},", Size.VENTI),\n",[679,92189,92190,92192,92194,92196,92198,92200,92203],{"class":681,"line":935},[679,92191,84746],{"class":685},[679,92193,91979],{"class":880},[679,92195,745],{"class":693},[679,92197,66599],{"class":931},[679,92199,2797],{"class":693},[679,92201,92202],{"class":689},"\"Macchiato\"",[679,92204,92205],{"class":693},", Size.TALL)\n",[679,92207,92208],{"class":681,"line":944},[679,92209,92210],{"class":693}," ));\n",[679,92212,92213],{"class":681,"line":959},[679,92214,889],{"emptyLinePlaceholder":797},[679,92216,92217,92219],{"class":681,"line":964},[679,92218,6872],{"class":693},[679,92220,80415],{"class":685},[679,92222,92223,92225,92227,92229,92231,92233],{"class":681,"line":977},[679,92224,6089],{"class":685},[679,92226,87217],{"class":693},[679,92228,91931],{"class":685},[679,92230,20881],{"class":693},[679,92232,34142],{"class":880},[679,92234,2667],{"class":693},[679,92236,92237,92239],{"class":681,"line":982},[679,92238,9444],{"class":685},[679,92240,92241],{"class":693}," coffeeList;\n",[679,92243,92244],{"class":681,"line":988},[679,92245,985],{"class":693},[679,92247,92248],{"class":681,"line":993},[679,92249,889],{"emptyLinePlaceholder":797},[679,92251,92252,92254,92257,92259,92262],{"class":681,"line":2129},[679,92253,6872],{"class":693},[679,92255,92256],{"class":685},"DeleteMapping",[679,92258,745],{"class":693},[679,92260,92261],{"class":689},"\"/{id}\"",[679,92263,1339],{"class":693},[679,92265,92266,92268,92270,92273,92275,92278,92280,92282],{"class":681,"line":2140},[679,92267,6089],{"class":685},[679,92269,6095],{"class":685},[679,92271,92272],{"class":880}," delete",[679,92274,73246],{"class":693},[679,92276,92277],{"class":685},"PathVariable",[679,92279,88766],{"class":693},[679,92281,11341],{"class":2099},[679,92283,4390],{"class":693},[679,92285,92286,92289,92292,92295,92297,92299,92301,92303,92305],{"class":681,"line":2145},[679,92287,92288],{"class":693}," coffeeList.",[679,92290,92291],{"class":880},"removeIf",[679,92293,92294],{"class":693},"(c ",[679,92296,16955],{"class":685},[679,92298,14885],{"class":693},[679,92300,11309],{"class":880},[679,92302,10541],{"class":693},[679,92304,14592],{"class":880},[679,92306,92307],{"class":693},"(id));\n",[679,92309,92310],{"class":681,"line":2154},[679,92311,985],{"class":693},[679,92313,92314],{"class":681,"line":2159},[679,92315,996],{"class":693},[651,92317,92318,92319,92322,92323,664],{},"If you run the application using the command ",[676,92320,92321],{},"mvn spring-boot:run"," (or directly from your IDE), you can view the coffee list at ",[676,92324,92325],{},"localhost:8080/API/coffee",[4542,92327,92329],{"id":92328},"understanding-cors-cross-origin-resource-sharing","Understanding CORS: Cross-Origin Resource Sharing",[651,92331,92332],{},"You're able to view the coffee list at the provided localhost URL because the request is coming from the same origin, where the domain and port match. However, issues may arise when cross-origin requests occur, such as when the domain, scheme, or port differ.",[651,92334,92335,92336,92339,92340,92342,92343,664],{},"For example, suppose another application is running on a different port, say ",[676,92337,92338],{},"localhost:5173",", and it attempts to fetch our coffee list from ",[676,92341,92325],{},". In this case, CORS intervenes and blocks the request because it originates from a different port. If you're interested in learning more about this topic, ",[812,92344,92347],{"href":92345,"rel":92346},"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",[816],"Mozilla's network documentation provides an in-depth explanation",[4542,92349,92351],{"id":92350},"configuring-cors-in-your-spring-boot-application","Configuring CORS in Your Spring Boot Application",[651,92353,92354,92355,88808],{},"So, how do we allow requests from other origins to access our APIs? Spring Boot provides a handy way to configure CORS using the ",[676,92356,92357],{},"@CrossOrigin",[651,92359,92360,92361,92363,92364,80369],{},"This annotation can be used at both method-level and class-level. For now, let's start by applying it at the class-level by adding ",[676,92362,92357],{}," at the top of our ",[676,92365,92076],{},[669,92367,92369],{"className":4107,"code":92368,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"API/coffee\")\n@CrossOrigin\npublic class CoffeeController {\n // controller content here...\n}\n",[676,92370,92371,92377,92389,92396,92406,92411],{"__ignoreMap":674},[679,92372,92373,92375],{"class":681,"line":682},[679,92374,4116],{"class":693},[679,92376,9212],{"class":685},[679,92378,92379,92381,92383,92385,92387],{"class":681,"line":790},[679,92380,4116],{"class":693},[679,92382,9275],{"class":685},[679,92384,745],{"class":693},[679,92386,92116],{"class":689},[679,92388,1339],{"class":693},[679,92390,92391,92393],{"class":681,"line":892},[679,92392,4116],{"class":693},[679,92394,92395],{"class":685},"CrossOrigin\n",[679,92397,92398,92400,92402,92404],{"class":681,"line":901},[679,92399,6073],{"class":685},[679,92401,4512],{"class":685},[679,92403,92127],{"class":880},[679,92405,884],{"class":693},[679,92407,92408],{"class":681,"line":909},[679,92409,92410],{"class":1400}," // controller content here...\n",[679,92412,92413],{"class":681,"line":918},[679,92414,996],{"class":693},[651,92416,92417,92418,92420],{},"With this configuration, the CORS error we received earlier when we tried to fetch the coffee list from ",[676,92419,92338],{}," should be resolved. However, this configuration allows access to all origins, which may not be suitable for most applications.",[651,92422,92423,92424,92426,92427,92430,92431,92433,92434,92436],{},"Therefore, if you want to allow specific origins, you can do so by passing them to the ",[676,92425,92357],{}," annotation. You can either use the ",[676,92428,92429],{},"origins"," property or pass them directly as the ",[676,92432,19934],{}," property (which is an alias for ",[676,92435,92429],{},"). The syntax would look like this:",[669,92438,92440],{"className":4107,"code":92439,"language":4109,"meta":674,"style":674},"@CrossOrigin(origins = \"http://localhost:5173\")\n",[676,92441,92442],{"__ignoreMap":674},[679,92443,92444,92446,92449,92451,92453,92455,92458],{"class":681,"line":682},[679,92445,4116],{"class":693},[679,92447,92448],{"class":685},"CrossOrigin",[679,92450,745],{"class":693},[679,92452,92429],{"class":931},[679,92454,6883],{"class":685},[679,92456,92457],{"class":689}," \"http://localhost:5173\"",[679,92459,1339],{"class":693},[651,92461,92462],{},"or",[669,92464,92466],{"className":4107,"code":92465,"language":4109,"meta":674,"style":674},"@CrossOrigin(\"http://localhost:5173\")\n",[676,92467,92468],{"__ignoreMap":674},[679,92469,92470,92472,92474,92476,92479],{"class":681,"line":682},[679,92471,4116],{"class":693},[679,92473,92448],{"class":685},[679,92475,745],{"class":693},[679,92477,92478],{"class":689},"\"http://localhost:5173\"",[679,92480,1339],{"class":693},[651,92482,92483,92484,92486],{},"In either case, only requests from ",[676,92485,92338],{}," will be allowed, and other cross-origin requests will be blocked. This is more suitable for a production-grade application where you want only specific applications to access your APIs.",[651,92488,92489,92490,92492],{},"Moreover, the ",[676,92491,92357],{}," annotation can also be applied at a granular level, specifically on methods, which allows for fine-grained control over which API endpoints should allow cross-origin requests.",[4542,92494,92496],{"id":92495},"configuring-cors-globally","Configuring CORS Globally",[651,92498,92499,92500,92503,92504,78287],{},"For larger applications with many controllers, it may be more efficient to configure CORS globally instead of annotating each controller or method separately. This can be done by creating a configuration class that implements the ",[676,92501,92502],{},"WebMvcConfigurer"," interface and overrides its ",[676,92505,92506],{},"addCorsMappings",[669,92508,92510],{"className":4107,"code":92509,"language":4109,"meta":674,"style":674},"@Configuration\npublic class WebMvcConfig implements WebMvcConfigurer {\n @Override\n public void addCorsMappings(CorsRegistry registry) {\n registry.addMapping(\"/**\")\n .allowedOrigins(\"http://localhost:5173\")\n .allowedMethods(\"GET\", \"POST\", \"PUT\", \"DELETE\");\n }\n}\n\n",[676,92511,92512,92518,92534,92540,92556,92570,92583,92612,92616],{"__ignoreMap":674},[679,92513,92514,92516],{"class":681,"line":682},[679,92515,4116],{"class":693},[679,92517,6212],{"class":685},[679,92519,92520,92522,92524,92527,92529,92532],{"class":681,"line":790},[679,92521,6073],{"class":685},[679,92523,4512],{"class":685},[679,92525,92526],{"class":880}," WebMvcConfig",[679,92528,4661],{"class":685},[679,92530,92531],{"class":880}," WebMvcConfigurer",[679,92533,884],{"class":693},[679,92535,92536,92538],{"class":681,"line":892},[679,92537,6872],{"class":693},[679,92539,10723],{"class":685},[679,92541,92542,92544,92546,92549,92552,92554],{"class":681,"line":901},[679,92543,6089],{"class":685},[679,92545,6095],{"class":685},[679,92547,92548],{"class":880}," addCorsMappings",[679,92550,92551],{"class":693},"(CorsRegistry ",[679,92553,10738],{"class":2099},[679,92555,4390],{"class":693},[679,92557,92558,92560,92563,92565,92568],{"class":681,"line":909},[679,92559,10745],{"class":693},[679,92561,92562],{"class":880},"addMapping",[679,92564,745],{"class":693},[679,92566,92567],{"class":689},"\"/**\"",[679,92569,1339],{"class":693},[679,92571,92572,92574,92577,92579,92581],{"class":681,"line":918},[679,92573,21331],{"class":693},[679,92575,92576],{"class":880},"allowedOrigins",[679,92578,745],{"class":693},[679,92580,92478],{"class":689},[679,92582,1339],{"class":693},[679,92584,92585,92587,92590,92592,92595,92597,92600,92602,92605,92607,92610],{"class":681,"line":935},[679,92586,21331],{"class":693},[679,92588,92589],{"class":880},"allowedMethods",[679,92591,745],{"class":693},[679,92593,92594],{"class":689},"\"GET\"",[679,92596,2797],{"class":693},[679,92598,92599],{"class":689},"\"POST\"",[679,92601,2797],{"class":693},[679,92603,92604],{"class":689},"\"PUT\"",[679,92606,2797],{"class":693},[679,92608,92609],{"class":689},"\"DELETE\"",[679,92611,1208],{"class":693},[679,92613,92614],{"class":681,"line":944},[679,92615,985],{"class":693},[679,92617,92618],{"class":681,"line":959},[679,92619,996],{"class":693},[651,92621,92622,92623,92626,92627,92629],{},"In the code above, we allow all endpoints (indicated by ",[676,92624,92625],{},"/**",") to accept cross-origin requests from ",[676,92628,92338],{}," and permit the HTTP methods GET, POST, PUT, and DELETE.",[4542,92631,92633],{"id":92632},"configuring-cors-with-spring-security","Configuring CORS With Spring Security",[651,92635,92636],{},"Adding Spring Security to a Spring Boot application changes things a bit. The issue is that CORS needs to be processed before Spring Security. This is because CORS makes a preflight request that does not contain any cookies. If Spring Security is processed first, it will reject the request as unauthenticated.",[651,92638,92639,92640,92643,92644,92646,92647,664],{},"To solve this, we need to include a CORS configuration in our Spring Security configuration class. Spring's HTTP security has a ",[676,92641,92642],{},"cors()"," method that can be used to apply CORS configuration. Calling ",[676,92645,92642],{}," without arguments configures Spring Security to use a bean named ",[676,92648,92649],{},"CorsConfigurationSource",[651,92651,92652,92653,92655],{},"We can define this ",[676,92654,92649],{}," bean in our security configuration class itself.",[669,92657,92659],{"className":4107,"code":92658,"language":4109,"meta":674,"style":674},"@EnableWebSecurity\npublic class SecurityConfig {\n @Bean\n public CorsConfigurationSource corsConfigurationSource() {\n CorsConfiguration configuration = new CorsConfiguration();\n configuration.setAllowedOrigins(Arrays.asList(\"http://localhost:5173\"));\n configuration.setAllowedMethods(Arrays.asList(\"GET\", \"POST\"));\n UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();\n source.registerCorsConfiguration(\"/**\", configuration);\n return source;\n }\n\n @Bean\n public SecurityFilterChain securityFilterChain(HttpSecurity http) {\n http.authorizeRequests().anyRequest().authenticated().and().httpBasic();\n return http.build();\n }\n}\n\n",[676,92660,92661,92667,92677,92683,92695,92709,92728,92749,92763,92778,92785,92789,92793,92799,92813,92838,92849,92853],{"__ignoreMap":674},[679,92662,92663,92665],{"class":681,"line":682},[679,92664,4116],{"class":693},[679,92666,89474],{"class":685},[679,92668,92669,92671,92673,92675],{"class":681,"line":790},[679,92670,6073],{"class":685},[679,92672,4512],{"class":685},[679,92674,89483],{"class":880},[679,92676,884],{"class":693},[679,92678,92679,92681],{"class":681,"line":892},[679,92680,6872],{"class":693},[679,92682,16929],{"class":685},[679,92684,92685,92687,92690,92693],{"class":681,"line":901},[679,92686,6089],{"class":685},[679,92688,92689],{"class":693}," CorsConfigurationSource ",[679,92691,92692],{"class":880},"corsConfigurationSource",[679,92694,2667],{"class":693},[679,92696,92697,92700,92702,92704,92707],{"class":681,"line":909},[679,92698,92699],{"class":693}," CorsConfiguration configuration ",[679,92701,686],{"class":685},[679,92703,2054],{"class":685},[679,92705,92706],{"class":880}," CorsConfiguration",[679,92708,9317],{"class":693},[679,92710,92711,92714,92717,92720,92722,92724,92726],{"class":681,"line":918},[679,92712,92713],{"class":693}," configuration.",[679,92715,92716],{"class":880},"setAllowedOrigins",[679,92718,92719],{"class":693},"(Arrays.",[679,92721,38277],{"class":880},[679,92723,745],{"class":693},[679,92725,92478],{"class":689},[679,92727,1669],{"class":693},[679,92729,92730,92732,92735,92737,92739,92741,92743,92745,92747],{"class":681,"line":935},[679,92731,92713],{"class":693},[679,92733,92734],{"class":880},"setAllowedMethods",[679,92736,92719],{"class":693},[679,92738,38277],{"class":880},[679,92740,745],{"class":693},[679,92742,92594],{"class":689},[679,92744,2797],{"class":693},[679,92746,92599],{"class":689},[679,92748,1669],{"class":693},[679,92750,92751,92754,92756,92758,92761],{"class":681,"line":944},[679,92752,92753],{"class":693}," UrlBasedCorsConfigurationSource source ",[679,92755,686],{"class":685},[679,92757,2054],{"class":685},[679,92759,92760],{"class":880}," UrlBasedCorsConfigurationSource",[679,92762,9317],{"class":693},[679,92764,92765,92768,92771,92773,92775],{"class":681,"line":959},[679,92766,92767],{"class":693}," source.",[679,92769,92770],{"class":880},"registerCorsConfiguration",[679,92772,745],{"class":693},[679,92774,92567],{"class":689},[679,92776,92777],{"class":693},", configuration);\n",[679,92779,92780,92782],{"class":681,"line":964},[679,92781,9444],{"class":685},[679,92783,92784],{"class":693}," source;\n",[679,92786,92787],{"class":681,"line":977},[679,92788,985],{"class":693},[679,92790,92791],{"class":681,"line":982},[679,92792,889],{"emptyLinePlaceholder":797},[679,92794,92795,92797],{"class":681,"line":988},[679,92796,6872],{"class":693},[679,92798,16929],{"class":685},[679,92800,92801,92803,92805,92807,92809,92811],{"class":681,"line":993},[679,92802,6089],{"class":685},[679,92804,89502],{"class":693},[679,92806,89505],{"class":880},[679,92808,89508],{"class":693},[679,92810,89511],{"class":2099},[679,92812,4390],{"class":693},[679,92814,92815,92818,92820,92822,92824,92826,92828,92830,92832,92834,92836],{"class":681,"line":2129},[679,92816,92817],{"class":693}," http.",[679,92819,40110],{"class":880},[679,92821,10541],{"class":693},[679,92823,89569],{"class":880},[679,92825,10541],{"class":693},[679,92827,89574],{"class":880},[679,92829,10541],{"class":693},[679,92831,40257],{"class":880},[679,92833,10541],{"class":693},[679,92835,89615],{"class":880},[679,92837,9317],{"class":693},[679,92839,92840,92842,92845,92847],{"class":681,"line":2140},[679,92841,9444],{"class":685},[679,92843,92844],{"class":693}," http.",[679,92846,23612],{"class":880},[679,92848,9317],{"class":693},[679,92850,92851],{"class":681,"line":2145},[679,92852,985],{"class":693},[679,92854,92855],{"class":681,"line":2154},[679,92856,996],{"class":693},[651,92858,92859,92860,92862],{},"In the code above, we create a ",[676,92861,92649],{}," bean, define allowed origins and methods for CORS, register CORS configuration, and use this CORS configuration bean in our Spring Security configuration.",[4542,92864,78006],{"id":78005},[651,92866,92867],{},"In this detailed step-by-step guide, we covered ways to configure CORS for your Spring controllers and methods, create global configurations for ease, and do the same for when you introduce Spring Security into your Spring application. Configuring CORS properly ensures that your APIs are only accessed by trusted domains, providing an extra layer of security for your application.",[651,92869,92870],{},"If you found this article helpful or have any questions or comments, feel free to reach out. Happy coding!",[786,92872,92873],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":92875},[92876,92877,92878,92879,92880,92881,92882,92883],{"id":41932,"depth":790,"text":39958},{"id":91897,"depth":790,"text":91898},{"id":91924,"depth":790,"text":91925},{"id":92328,"depth":790,"text":92329},{"id":92350,"depth":790,"text":92351},{"id":92495,"depth":790,"text":92496},{"id":92632,"depth":790,"text":92633},{"id":78005,"depth":790,"text":78006},{"slug":92885,"date":92886,"published":797,"author":798,"tags":92887,"cover":92888,"video":92889,"github":91891,"keywords":92890},"spring-security-cors","2022-09-22T08:00:00.000Z",[23988],"./ss-cors-thumbnail.jpeg","https://www.youtube.com/embed/HRwlT_etr60","Spring Framework, Spring Boot, Spring Security, Spring Security CORS, CORS, Spring Security CORS Configuration",{"title":381,"description":849},"blog/2022/09/22/spring-security-cors","sGE_KnfHuGVDpJByRgNlrKtY1kQxRWigGlqYXM0hGkM",{"id":92895,"title":378,"body":92896,"description":93822,"extension":793,"meta":93823,"navigation":797,"path":379,"seo":93830,"stem":93831,"__hash__":93832},"content/blog/2022/09/29/domain-class-converter.md",{"type":648,"value":92897,"toc":93813},[92898,92901,92909,92911,92918,92950,92957,92961,92989,93174,93202,93262,93289,93464,93468,93474,93524,93531,93535,93541,93556,93654,93658,93661,93672,93694,93742,93751,93755,93766,93774,93797,93799,93802,93810],[651,92899,92900],{},"In this article, we'll discuss a recent tweet I sent out regarding the Domain Class Converter in Spring Data. The Domain Class Converter can convert any arbitrary input into a domain class managed by a Spring Data Repository (specifically a Crud Repository). We'll go through a tutorial that demonstrates how to use this feature.",[651,92902,92903,92904,92908],{},"If you're not already following me on Twitter, be sure to do so! I'm on Twitter as ",[812,92905,92907],{"href":44086,"rel":92906},[816],"TheRealDanVega",", where I share tips, discussions, and tutorials about Spring and programming in general.",[4542,92910,39958],{"id":41932},[651,92912,92913,92914,92917],{},"We'll begin by bootstrapping a simple Spring application that uses Spring Data JPA. Head over to ",[812,92915,7115],{"href":7115,"rel":92916},[816]," and create a new project with the following settings:",[5316,92919,92920,92926,92933,92939],{},[5332,92921,92922,92923],{},"Group ID: ",[676,92924,92925],{},"dev.danvega",[5332,92927,92928,92929,92932],{},"Artifact ID: ",[676,92930,92931],{},"dcc"," (for Domain Class Converter)",[5332,92934,92935,92936],{},"Java: ",[676,92937,92938],{},"version 17",[5332,92940,92941,92942,2797,92945,2797,92948],{},"Dependencies: ",[676,92943,92944],{},"web",[676,92946,92947],{},"data-jpa",[676,92949,4542],{},[651,92951,92952,92956],{},[660,92953],{"alt":92954,"src":92955},"Create new project using Spring Initializr","/images/blog/2022/09/29/spring-init.png","Once your project is generated, you can open it in your favorite IDE (I'll be using IntelliJ in this tutorial).",[4542,92958,92960],{"id":92959},"creating-the-model-repository-and-controller","Creating the Model, Repository, and Controller",[651,92962,92963,92964,92966,92967,2797,92969,2797,92971,2797,92973,48406,92976,92979,92980,92982,92983,92985,92986,92988],{},"Our application will revolve around blog posts. We'll create a simple ",[676,92965,5105],{}," entity with fields such as ",[676,92968,11341],{},[676,92970,11750],{},[676,92972,47833],{},[676,92974,92975],{},"publishedOn",[676,92977,92978],{},"updatedOn",". To get started, create a ",[676,92981,10048],{}," package in your project and add a new class called ",[676,92984,5105],{},". Annotate this class with ",[676,92987,16256],{},", and then create the necessary fields and methods, such as a no-argument constructor, getters, and setters.",[669,92990,92992],{"className":4107,"code":92991,"language":4109,"meta":674,"style":674},"@Entity\npublic class Post {\n\n @Id\n private Integer id;\n private String title;\n private String content;\n private LocalDateTime publishedOn;\n private LocalDateTime updatedOn;\n\n public Post() {}\n\n public Post(Integer id, String title, String content) {\n this.id = id;\n this.title = title;\n this.content = content;\n this.publishedOn = LocalDateTime.now();\n this.updatedOn = LocalDateTime.now();;\n }\n\n // getters, setters & toString omitted\n\n}\n",[676,92993,92994,93000,93010,93014,93020,93026,93033,93039,93046,93053,93057,93065,93069,93089,93099,93111,93121,93137,93153,93157,93161,93166,93170],{"__ignoreMap":674},[679,92995,92996,92998],{"class":681,"line":682},[679,92997,4116],{"class":693},[679,92999,11234],{"class":685},[679,93001,93002,93004,93006,93008],{"class":681,"line":790},[679,93003,6073],{"class":685},[679,93005,4512],{"class":685},[679,93007,4658],{"class":880},[679,93009,884],{"class":693},[679,93011,93012],{"class":681,"line":892},[679,93013,889],{"emptyLinePlaceholder":797},[679,93015,93016,93018],{"class":681,"line":901},[679,93017,6872],{"class":693},[679,93019,33530],{"class":685},[679,93021,93022,93024],{"class":681,"line":909},[679,93023,9232],{"class":685},[679,93025,83952],{"class":693},[679,93027,93028,93030],{"class":681,"line":918},[679,93029,9232],{"class":685},[679,93031,93032],{"class":693}," String title;\n",[679,93034,93035,93037],{"class":681,"line":935},[679,93036,9232],{"class":685},[679,93038,77597],{"class":693},[679,93040,93041,93043],{"class":681,"line":944},[679,93042,9232],{"class":685},[679,93044,93045],{"class":693}," LocalDateTime publishedOn;\n",[679,93047,93048,93050],{"class":681,"line":959},[679,93049,9232],{"class":685},[679,93051,93052],{"class":693}," LocalDateTime updatedOn;\n",[679,93054,93055],{"class":681,"line":964},[679,93056,889],{"emptyLinePlaceholder":797},[679,93058,93059,93061,93063],{"class":681,"line":977},[679,93060,6089],{"class":685},[679,93062,4658],{"class":880},[679,93064,11295],{"class":693},[679,93066,93067],{"class":681,"line":982},[679,93068,889],{"emptyLinePlaceholder":797},[679,93070,93071,93073,93075,93077,93079,93081,93083,93085,93087],{"class":681,"line":988},[679,93072,6089],{"class":685},[679,93074,4658],{"class":880},[679,93076,87309],{"class":693},[679,93078,11341],{"class":2099},[679,93080,20006],{"class":693},[679,93082,11750],{"class":2099},[679,93084,20006],{"class":693},[679,93086,47833],{"class":2099},[679,93088,4390],{"class":693},[679,93090,93091,93093,93095,93097],{"class":681,"line":993},[679,93092,7862],{"class":931},[679,93094,11350],{"class":693},[679,93096,686],{"class":685},[679,93098,11318],{"class":693},[679,93100,93101,93103,93106,93108],{"class":681,"line":2129},[679,93102,7862],{"class":931},[679,93104,93105],{"class":693},".title ",[679,93107,686],{"class":685},[679,93109,93110],{"class":693}," title;\n",[679,93112,93113,93115,93117,93119],{"class":681,"line":2140},[679,93114,7862],{"class":931},[679,93116,77637],{"class":693},[679,93118,686],{"class":685},[679,93120,77642],{"class":693},[679,93122,93123,93125,93128,93130,93133,93135],{"class":681,"line":2145},[679,93124,7862],{"class":931},[679,93126,93127],{"class":693},".publishedOn ",[679,93129,686],{"class":685},[679,93131,93132],{"class":693}," LocalDateTime.",[679,93134,90866],{"class":880},[679,93136,9317],{"class":693},[679,93138,93139,93141,93144,93146,93148,93150],{"class":681,"line":2154},[679,93140,7862],{"class":931},[679,93142,93143],{"class":693},".updatedOn ",[679,93145,686],{"class":685},[679,93147,93132],{"class":693},[679,93149,90866],{"class":880},[679,93151,93152],{"class":693},"();;\n",[679,93154,93155],{"class":681,"line":2159},[679,93156,985],{"class":693},[679,93158,93159],{"class":681,"line":2164},[679,93160,889],{"emptyLinePlaceholder":797},[679,93162,93163],{"class":681,"line":3134},[679,93164,93165],{"class":1400}," // getters, setters & toString omitted\n",[679,93167,93168],{"class":681,"line":3139},[679,93169,889],{"emptyLinePlaceholder":797},[679,93171,93172],{"class":681,"line":3144},[679,93173,996],{"class":693},[651,93175,93176,93177,93179,93180,93182,93183,93185,93186,23212,93188,93190,93191,93193,93194,2797,93197,48406,93199,664],{},"Next, create a ",[676,93178,16596],{}," package and add a new interface called ",[676,93181,23351],{},". This interface should extend the ",[676,93184,16365],{}," and take ",[676,93187,5105],{},[676,93189,1083],{}," as its type arguments. By extending ",[676,93192,16365],{},", we get basic CRUD functionality out of the box, such as ",[676,93195,93196],{},"findById()",[676,93198,85236],{},[676,93200,93201],{},"save()",[669,93203,93205],{"className":4107,"code":93204,"language":4109,"meta":674,"style":674},"package dev.danvega.dcc.repository;\n\nimport dev.danvega.dcc.model.Post;\nimport org.springframework.data.repository.CrudRepository;\n\npublic interface PostRepository extends CrudRepository\u003CPost,Integer> {\n}\n",[676,93206,93207,93214,93218,93225,93231,93235,93258],{"__ignoreMap":674},[679,93208,93209,93211],{"class":681,"line":682},[679,93210,2543],{"class":685},[679,93212,93213],{"class":693}," dev.danvega.dcc.repository;\n",[679,93215,93216],{"class":681,"line":790},[679,93217,889],{"emptyLinePlaceholder":797},[679,93219,93220,93222],{"class":681,"line":892},[679,93221,1999],{"class":685},[679,93223,93224],{"class":693}," dev.danvega.dcc.model.Post;\n",[679,93226,93227,93229],{"class":681,"line":901},[679,93228,1999],{"class":685},[679,93230,26986],{"class":693},[679,93232,93233],{"class":681,"line":909},[679,93234,889],{"emptyLinePlaceholder":797},[679,93236,93237,93239,93241,93244,93246,93248,93250,93252,93254,93256],{"class":681,"line":918},[679,93238,6073],{"class":685},[679,93240,6994],{"class":685},[679,93242,93243],{"class":880}," PostRepository",[679,93245,2767],{"class":685},[679,93247,16385],{"class":880},[679,93249,4505],{"class":693},[679,93251,5105],{"class":685},[679,93253,1202],{"class":693},[679,93255,1083],{"class":685},[679,93257,16397],{"class":693},[679,93259,93260],{"class":681,"line":935},[679,93261,996],{"class":693},[651,93263,93264,93265,93267,93268,92985,93271,93273,93274,93276,93277,93280,93281,93283,93284,23212,93286,93288],{},"Finally, create a ",[676,93266,4279],{}," package and add a new class called ",[676,93269,93270],{},"PostController",[676,93272,12329],{}," and add a ",[676,93275,22845],{}," with the path set to ",[676,93278,93279],{},"/api/posts",". Inject an instance of ",[676,93282,23351],{}," using constructor injection, then create ",[676,93285,85236],{},[676,93287,93196],{}," methods that leverage the repository's methods to return posts.",[669,93290,93292],{"className":4107,"code":93291,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private final PostRepository posts;\n\n public PostController(PostRepository posts) {\n this.posts = posts;\n }\n\n @GetMapping\n public Iterable\u003CPost> findAll() {\n return posts.findAll();\n }\n\n @GetMapping(\"/{id}\")\n public Post findById(@PathVariable Integer id) {\n return posts.findById(id);\n }\n\n}\n",[676,93293,93294,93300,93312,93322,93326,93335,93339,93352,93364,93368,93372,93378,93392,93403,93407,93411,93423,93442,93452,93456,93460],{"__ignoreMap":674},[679,93295,93296,93298],{"class":681,"line":682},[679,93297,4116],{"class":693},[679,93299,9212],{"class":685},[679,93301,93302,93304,93306,93308,93310],{"class":681,"line":790},[679,93303,4116],{"class":693},[679,93305,9275],{"class":685},[679,93307,745],{"class":693},[679,93309,23141],{"class":689},[679,93311,1339],{"class":693},[679,93313,93314,93316,93318,93320],{"class":681,"line":892},[679,93315,6073],{"class":685},[679,93317,4512],{"class":685},[679,93319,23152],{"class":880},[679,93321,884],{"class":693},[679,93323,93324],{"class":681,"line":901},[679,93325,889],{"emptyLinePlaceholder":797},[679,93327,93328,93330,93332],{"class":681,"line":909},[679,93329,9232],{"class":685},[679,93331,12768],{"class":685},[679,93333,93334],{"class":693}," PostRepository posts;\n",[679,93336,93337],{"class":681,"line":918},[679,93338,889],{"emptyLinePlaceholder":797},[679,93340,93341,93343,93345,93347,93350],{"class":681,"line":935},[679,93342,6089],{"class":685},[679,93344,23152],{"class":880},[679,93346,23405],{"class":693},[679,93348,93349],{"class":2099},"posts",[679,93351,4390],{"class":693},[679,93353,93354,93356,93359,93361],{"class":681,"line":944},[679,93355,7862],{"class":931},[679,93357,93358],{"class":693},".posts ",[679,93360,686],{"class":685},[679,93362,93363],{"class":693}," posts;\n",[679,93365,93366],{"class":681,"line":959},[679,93367,985],{"class":693},[679,93369,93370],{"class":681,"line":964},[679,93371,889],{"emptyLinePlaceholder":797},[679,93373,93374,93376],{"class":681,"line":977},[679,93375,6872],{"class":693},[679,93377,80415],{"class":685},[679,93379,93380,93382,93384,93386,93388,93390],{"class":681,"line":982},[679,93381,6089],{"class":685},[679,93383,20875],{"class":693},[679,93385,5105],{"class":685},[679,93387,20881],{"class":693},[679,93389,34142],{"class":880},[679,93391,2667],{"class":693},[679,93393,93394,93396,93399,93401],{"class":681,"line":988},[679,93395,9444],{"class":685},[679,93397,93398],{"class":693}," posts.",[679,93400,34142],{"class":880},[679,93402,9317],{"class":693},[679,93404,93405],{"class":681,"line":993},[679,93406,985],{"class":693},[679,93408,93409],{"class":681,"line":2129},[679,93410,889],{"emptyLinePlaceholder":797},[679,93412,93413,93415,93417,93419,93421],{"class":681,"line":2140},[679,93414,6872],{"class":693},[679,93416,20852],{"class":685},[679,93418,745],{"class":693},[679,93420,92261],{"class":689},[679,93422,1339],{"class":693},[679,93424,93425,93427,93430,93432,93434,93436,93438,93440],{"class":681,"line":2145},[679,93426,6089],{"class":685},[679,93428,93429],{"class":693}," Post ",[679,93431,87658],{"class":880},[679,93433,73246],{"class":693},[679,93435,92277],{"class":685},[679,93437,88766],{"class":693},[679,93439,11341],{"class":2099},[679,93441,4390],{"class":693},[679,93443,93444,93446,93448,93450],{"class":681,"line":2154},[679,93445,9444],{"class":685},[679,93447,93398],{"class":693},[679,93449,87658],{"class":880},[679,93451,88781],{"class":693},[679,93453,93454],{"class":681,"line":2159},[679,93455,985],{"class":693},[679,93457,93458],{"class":681,"line":2164},[679,93459,889],{"emptyLinePlaceholder":797},[679,93461,93462],{"class":681,"line":3134},[679,93463,996],{"class":693},[4542,93465,93467],{"id":93466},"configuring-the-application-properties","Configuring the Application Properties",[651,93469,93470,93471,93473],{},"We need to configure our application to work with the H2 in-memory database. Open your ",[676,93472,16242],{}," file and add the following properties:",[669,93475,93477],{"className":76589,"code":93476,"language":35538,"meta":674,"style":674},"spring.h2.console.enabled=true\nspring.h2.console.name=jpablog\nspring.datasource.url=jdbc:h2:mem:japablog\nspring.datasource.username=sa\nspring.datasource.password=\nspring.jpa.show-sql=true\n",[676,93478,93479,93486,93494,93502,93510,93518],{"__ignoreMap":674},[679,93480,93481,93483],{"class":681,"line":682},[679,93482,7323],{"class":685},[679,93484,93485],{"class":693},"=true\n",[679,93487,93488,93491],{"class":681,"line":790},[679,93489,93490],{"class":685},"spring.h2.console.name",[679,93492,93493],{"class":693},"=jpablog\n",[679,93495,93496,93499],{"class":681,"line":892},[679,93497,93498],{"class":685},"spring.datasource.url",[679,93500,93501],{"class":693},"=jdbc:h2:mem:japablog\n",[679,93503,93504,93507],{"class":681,"line":901},[679,93505,93506],{"class":685},"spring.datasource.username",[679,93508,93509],{"class":693},"=sa\n",[679,93511,93512,93515],{"class":681,"line":909},[679,93513,93514],{"class":685},"spring.datasource.password",[679,93516,93517],{"class":693},"=\n",[679,93519,93520,93522],{"class":681,"line":918},[679,93521,83895],{"class":685},[679,93523,93485],{"class":693},[651,93525,93526,93527,93530],{},"These properties enable the H2 console, set its name to ",[676,93528,93529],{},"jpablog",", configure the data source URL, and enable the display of generated SQL.",[4542,93532,93534],{"id":93533},"seeding-the-database","Seeding the Database",[651,93536,93537,93538,93540],{},"To test our application, we need some initial data in our database. We can achieve this by creating a ",[676,93539,16415],{}," bean. This functional interface allows us to execute some code after the application context has been created and before the application runs.",[651,93542,93543,93544,93546,93547,93549,93550,93552,93553,93555],{},"In our ",[676,93545,16415],{}," bean, we'll inject an instance of ",[676,93548,23351],{}," and use its ",[676,93551,93201],{}," method to create and save some sample ",[676,93554,5105],{}," objects to the database.",[669,93557,93559],{"className":4107,"code":93558,"language":4109,"meta":674,"style":674},"@Bean\npublic CommandLineRunner seedData(PostRepository posts) {\n return args -> {\n posts.save(new Post(1, \"Hello World\", \"Welcome to my blog\"));\n posts.save(new Post(2, \"Hello JPA\", \"Working with Spring Data JPA\"));\n };\n}\n\n",[676,93560,93561,93567,93579,93589,93618,93646,93650],{"__ignoreMap":674},[679,93562,93563,93565],{"class":681,"line":682},[679,93564,4116],{"class":693},[679,93566,16929],{"class":685},[679,93568,93569,93571,93573,93576],{"class":681,"line":790},[679,93570,6073],{"class":685},[679,93572,16936],{"class":693},[679,93574,93575],{"class":880},"seedData",[679,93577,93578],{"class":693},"(PostRepository posts) {\n",[679,93580,93581,93583,93585,93587],{"class":681,"line":892},[679,93582,44767],{"class":685},[679,93584,16952],{"class":693},[679,93586,16955],{"class":685},[679,93588,884],{"class":693},[679,93590,93591,93594,93596,93598,93600,93602,93604,93606,93608,93611,93613,93616],{"class":681,"line":901},[679,93592,93593],{"class":693}," posts.",[679,93595,7629],{"class":880},[679,93597,745],{"class":693},[679,93599,8930],{"class":685},[679,93601,4658],{"class":880},[679,93603,745],{"class":693},[679,93605,1557],{"class":931},[679,93607,2797],{"class":693},[679,93609,93610],{"class":689},"\"Hello World\"",[679,93612,2797],{"class":693},[679,93614,93615],{"class":689},"\"Welcome to my blog\"",[679,93617,1669],{"class":693},[679,93619,93620,93622,93624,93626,93628,93630,93632,93634,93636,93639,93641,93644],{"class":681,"line":909},[679,93621,93593],{"class":693},[679,93623,7629],{"class":880},[679,93625,745],{"class":693},[679,93627,8930],{"class":685},[679,93629,4658],{"class":880},[679,93631,745],{"class":693},[679,93633,17262],{"class":931},[679,93635,2797],{"class":693},[679,93637,93638],{"class":689},"\"Hello JPA\"",[679,93640,2797],{"class":693},[679,93642,93643],{"class":689},"\"Working with Spring Data JPA\"",[679,93645,1669],{"class":693},[679,93647,93648],{"class":681,"line":918},[679,93649,53075],{"class":693},[679,93651,93652],{"class":681,"line":935},[679,93653,996],{"class":693},[4542,93655,93657],{"id":93656},"using-the-domain-class-converter","Using the Domain Class Converter",[651,93659,93660],{},"With our application setup and initial data in place, we can now explore the main topic of this tutorial: the Domain Class Converter.",[651,93662,93543,93663,93665,93666,93668,93669,93671],{},[676,93664,93270],{},", we have a ",[676,93667,93196],{}," method that takes an ",[676,93670,11341],{}," path variable and returns the corresponding post. We can simplify this method by leveraging the Domain Class Converter.",[651,93673,93674,93675,93677,93678,93680,93681,93683,93684,93687,93688,93690,93691,93693],{},"Instead of using an ",[676,93676,1083],{}," parameter for the ",[676,93679,11341],{},", we can use a ",[676,93682,5105],{}," parameter directly, and annotate it with ",[676,93685,93686],{},"@PathVariable(\"id\")",". This tells Spring to use the Domain Class Converter to look up the ",[676,93689,5105],{}," by its ",[676,93692,11341],{}," and assign it to the parameter.",[669,93695,93697],{"className":4107,"code":93696,"language":4109,"meta":674,"style":674},"@GetMapping(\"/{id}\")\npublic Post findById(@PathVariable(\"id\") Post post) {\n return post;\n}\n",[676,93698,93699,93711,93731,93738],{"__ignoreMap":674},[679,93700,93701,93703,93705,93707,93709],{"class":681,"line":682},[679,93702,4116],{"class":693},[679,93704,20852],{"class":685},[679,93706,745],{"class":693},[679,93708,92261],{"class":689},[679,93710,1339],{"class":693},[679,93712,93713,93715,93717,93719,93721,93723,93725,93728],{"class":681,"line":790},[679,93714,6073],{"class":685},[679,93716,93429],{"class":693},[679,93718,87658],{"class":880},[679,93720,73246],{"class":693},[679,93722,92277],{"class":685},[679,93724,745],{"class":693},[679,93726,93727],{"class":689},"\"id\"",[679,93729,93730],{"class":693},") Post post) {\n",[679,93732,93733,93735],{"class":681,"line":892},[679,93734,44767],{"class":685},[679,93736,93737],{"class":693}," post;\n",[679,93739,93740],{"class":681,"line":901},[679,93741,996],{"class":693},[651,93743,93744,93745,93747,93748,93750],{},"Now our ",[676,93746,93196],{}," method is simplified, and we no longer need to call the repository's ",[676,93749,93196],{}," method explicitly in our controller.",[4542,93752,93754],{"id":93753},"testing-the-application","Testing the Application",[651,93756,93757,93758,93761,93762,93765],{},"Start the application and navigate to the H2 console at ",[812,93759,16733],{"href":16733,"rel":93760},[816],". Connect to the database using the data source name (jpablog) and view the contents of the ",[676,93763,93764],{},"POST"," table. You should see the two sample posts we added earlier.",[651,93767,93768,93769,23212,93771,93773],{},"Now, test the ",[676,93770,85236],{},[676,93772,93196],{}," endpoints in your browser:",[5316,93775,93776,93783,93790],{},[5332,93777,93778,93782],{},[812,93779,93780],{"href":93780,"rel":93781},"http://localhost:8080/api/posts",[816]," should return a list of all posts.",[5332,93784,93785,93789],{},[812,93786,93787],{"href":93787,"rel":93788},"http://localhost:8080/api/posts/1",[816]," should return the post with ID 1.",[5332,93791,93792,93796],{},[812,93793,93794],{"href":93794,"rel":93795},"http://localhost:8080/api/posts/2",[816]," should return the post with ID 2.",[4542,93798,9042],{"id":9041},[651,93800,93801],{},"The Domain Class Converter is a useful feature that can simplify your Spring Data application by converting arbitrary input, like an ID, into a domain class managed by a Crud Repository. Though it may seem like a small change, it can make your code cleaner and more concise.",[651,93803,93804,93805,93809],{},"I hope you found this tutorial helpful! If you did, please give it a thumbs up, and don't forget to follow me on ",[812,93806,51474],{"href":93807,"rel":93808},"https://twitter.com/the_real_DanVega",[816]," for more tips, tutorials, and discussions about Spring Boot, Java, and programming in general. Happy coding!",[786,93811,93812],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":93814},[93815,93816,93817,93818,93819,93820,93821],{"id":41932,"depth":790,"text":39958},{"id":92959,"depth":790,"text":92960},{"id":93466,"depth":790,"text":93467},{"id":93533,"depth":790,"text":93534},{"id":93656,"depth":790,"text":93657},{"id":93753,"depth":790,"text":93754},{"id":9041,"depth":790,"text":9042},"In this tutorial, you will learn about a class called the Domain Class Converter in Spring Data. This class will allow you to take arbitrary input like an id from a path variable in a REST API and automatically create a domain object by using Spring Data's CrudRepository.",{"slug":93824,"date":93825,"published":797,"author":798,"tags":93826,"cover":93827,"video":93828,"keywords":93829},"domain-class-converter","2022-09-29T11:00:00.000Z",[7077,84347],"./domain-class-converter-thumbnail-small.png","https://www.youtube.com/embed/_QBe2ZiXV-0","Spring Data, Spring Boot, Spring Boot REST API, Domain Class Converter, CrudRepository",{"title":378,"description":93822},"blog/2022/09/29/domain-class-converter","8xp1OSSIl-nk49vNeDzLhrM5aQQGG6vUTh8J335AQg8",{"id":93834,"title":375,"body":93835,"description":94245,"extension":793,"meta":94246,"navigation":797,"path":376,"seo":94253,"stem":94254,"__hash__":94255},"content/blog/2022/11/09/hello-aws-lambda-java.md",{"type":648,"value":93836,"toc":94238},[93837,93840,93842,93845,93852,93858,93871,93922,93925,93929,93939,94043,94046,94050,94053,94062,94170,94173,94185,94191,94195,94207,94213,94216,94219,94222,94226,94229,94232,94235],[651,93838,93839],{},"In this tutorial, I'll show you how to create a simple AWS Lambda function using Java. We'll start with a plain Java project, upload it to the AWS console, and test it. Along the way, I'll share some of the pain points I encountered while building serverless functions, and in a later tutorial you will see how you can overcome them using tools like Spring Cloud Function.",[4542,93841,39958],{"id":41932},[651,93843,93844],{},"First, create a new Java project using your favorite IDE or text editor. I'll be using IntelliJ IDEA Ultimate Edition, but you can also use the Community Edition or another tool of your choice.",[651,93846,93847,93848,93851],{},"Create a new Maven project and choose the ",[676,93849,93850],{},"quickstart"," archetype, which gives us a basic shell of an application. We'll be using Java 11 and JUnit 5.8.2 for our project.",[651,93853,93854],{},[660,93855],{"alt":93856,"src":93857},"Maven QuickStart Archetype","/images/blog/2022/11/09/maven-quickstart.png",[651,93859,93860,93861,93864,93865,93868,93869,664],{},"To start, let's create a simple Java class that will hold our Lambda function. Name the class ",[676,93862,93863],{},"HelloLambda",", and create a method called ",[676,93866,93867],{},"handleRequest"," that returns a ",[676,93870,4758],{},[669,93872,93874],{"className":4107,"code":93873,"language":4109,"meta":674,"style":674},"public class HelloLambda {\n\n public String handleRequest() {\n return \"Hello, AWS Lambda!\";\n }\n\n}\n",[676,93875,93876,93887,93891,93901,93910,93914,93918],{"__ignoreMap":674},[679,93877,93878,93880,93882,93885],{"class":681,"line":682},[679,93879,6073],{"class":685},[679,93881,4512],{"class":685},[679,93883,93884],{"class":880}," HelloLambda",[679,93886,884],{"class":693},[679,93888,93889],{"class":681,"line":790},[679,93890,889],{"emptyLinePlaceholder":797},[679,93892,93893,93895,93897,93899],{"class":681,"line":892},[679,93894,6089],{"class":685},[679,93896,9289],{"class":693},[679,93898,93867],{"class":880},[679,93900,2667],{"class":693},[679,93902,93903,93905,93908],{"class":681,"line":901},[679,93904,9444],{"class":685},[679,93906,93907],{"class":689}," \"Hello, AWS Lambda!\"",[679,93909,1186],{"class":693},[679,93911,93912],{"class":681,"line":909},[679,93913,985],{"class":693},[679,93915,93916],{"class":681,"line":918},[679,93917,889],{"emptyLinePlaceholder":797},[679,93919,93920],{"class":681,"line":935},[679,93921,996],{"class":693},[651,93923,93924],{},"This is a very simple function that just takes a name as input and returns a greeting message. Now, let's create a test for our function using JUnit.",[4542,93926,93928],{"id":93927},"writing-tests","Writing Tests",[651,93930,93931,93932,93935,93936,664],{},"It's always a good practice to write tests for your functions. Create a new test class called ",[676,93933,93934],{},"HelloLambdaTest"," and add a method named ",[676,93937,93938],{},"shouldReturnHelloMessage",[669,93940,93942],{"className":4107,"code":93941,"language":4109,"meta":674,"style":674},"import org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass HelloLambdaTest {\n\n @Test\n void shouldReturnHelloMessage() {\n var sut = new HelloLambda();\n assertEquals(\"Hello, AWS Lambda!\",sut.handleRequest());\n }\n\n}\n",[676,93943,93944,93950,93954,93967,93971,93980,93984,93990,93999,94015,94031,94035,94039],{"__ignoreMap":674},[679,93945,93946,93948],{"class":681,"line":682},[679,93947,1999],{"class":685},[679,93949,73054],{"class":693},[679,93951,93952],{"class":681,"line":790},[679,93953,889],{"emptyLinePlaceholder":797},[679,93955,93956,93958,93960,93963,93965],{"class":681,"line":892},[679,93957,1999],{"class":685},[679,93959,6092],{"class":685},[679,93961,93962],{"class":693}," org.junit.jupiter.api.Assertions.",[679,93964,4150],{"class":931},[679,93966,1186],{"class":693},[679,93968,93969],{"class":681,"line":901},[679,93970,889],{"emptyLinePlaceholder":797},[679,93972,93973,93975,93978],{"class":681,"line":909},[679,93974,877],{"class":685},[679,93976,93977],{"class":880}," HelloLambdaTest",[679,93979,884],{"class":693},[679,93981,93982],{"class":681,"line":918},[679,93983,889],{"emptyLinePlaceholder":797},[679,93985,93986,93988],{"class":681,"line":935},[679,93987,6872],{"class":693},[679,93989,73087],{"class":685},[679,93991,93992,93994,93997],{"class":681,"line":944},[679,93993,3314],{"class":685},[679,93995,93996],{"class":880}," shouldReturnHelloMessage",[679,93998,2667],{"class":693},[679,94000,94001,94004,94007,94009,94011,94013],{"class":681,"line":959},[679,94002,94003],{"class":685}," var",[679,94005,94006],{"class":693}," sut ",[679,94008,686],{"class":685},[679,94010,2054],{"class":685},[679,94012,93884],{"class":880},[679,94014,9317],{"class":693},[679,94016,94017,94019,94021,94024,94027,94029],{"class":681,"line":964},[679,94018,73133],{"class":880},[679,94020,745],{"class":693},[679,94022,94023],{"class":689},"\"Hello, AWS Lambda!\"",[679,94025,94026],{"class":693},",sut.",[679,94028,93867],{"class":880},[679,94030,9431],{"class":693},[679,94032,94033],{"class":681,"line":977},[679,94034,985],{"class":693},[679,94036,94037],{"class":681,"line":982},[679,94038,889],{"emptyLinePlaceholder":797},[679,94040,94041],{"class":681,"line":988},[679,94042,996],{"class":693},[651,94044,94045],{},"Run the test to make sure it passes. Now that we have our function and test, we need to package the function using the Maven Shade Plugin.",[4542,94047,94049],{"id":94048},"maven-shade-plugin","Maven Shade Plugin",[651,94051,94052],{},"The Maven Shade Plugin is responsible for packaging the project classes together with their dependencies into a single \"Uber JAR\". This is what AWS Lambda expects when we upload our function.",[651,94054,94055,94056,94058,94059,94061],{},"To configure the Maven Shade Plugin, add a ",[676,94057,23612],{}," section containing the plugin configuration to the ",[676,94060,81517],{}," file:",[669,94063,94065],{"className":9101,"code":94064,"language":9103,"meta":674,"style":674},"\u003Cbuild>\n \u003Cplugins>\n \u003Cplugin>\n \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n \u003CartifactId>maven-shade-plugin\u003C/artifactId>\n \u003Cversion>3.2.4\u003C/version>\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cphase>package\u003C/phase>\n \u003Cgoals>\n \u003Cgoal>shade\u003C/goal>\n \u003C/goals>\n \u003Cconfiguration>\n \u003CshadedArtifactAttached>true\u003C/shadedArtifactAttached>\n \u003CshadedClassifierName>aws-lambda\u003C/shadedClassifierName>\n \u003C/configuration>\n \u003C/execution>\n \u003C/executions>\n \u003C/plugin>\n \u003C/plugins>\n\u003C/build>\n",[676,94066,94067,94071,94076,94081,94086,94091,94096,94101,94106,94111,94116,94121,94126,94131,94136,94141,94146,94151,94156,94161,94166],{"__ignoreMap":674},[679,94068,94069],{"class":681,"line":682},[679,94070,40689],{},[679,94072,94073],{"class":681,"line":790},[679,94074,94075],{}," \u003Cplugins>\n",[679,94077,94078],{"class":681,"line":892},[679,94079,94080],{}," \u003Cplugin>\n",[679,94082,94083],{"class":681,"line":901},[679,94084,94085],{}," \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n",[679,94087,94088],{"class":681,"line":909},[679,94089,94090],{}," \u003CartifactId>maven-shade-plugin\u003C/artifactId>\n",[679,94092,94093],{"class":681,"line":918},[679,94094,94095],{}," \u003Cversion>3.2.4\u003C/version>\n",[679,94097,94098],{"class":681,"line":935},[679,94099,94100],{}," \u003Cexecutions>\n",[679,94102,94103],{"class":681,"line":944},[679,94104,94105],{}," \u003Cexecution>\n",[679,94107,94108],{"class":681,"line":959},[679,94109,94110],{}," \u003Cphase>package\u003C/phase>\n",[679,94112,94113],{"class":681,"line":964},[679,94114,94115],{}," \u003Cgoals>\n",[679,94117,94118],{"class":681,"line":977},[679,94119,94120],{}," \u003Cgoal>shade\u003C/goal>\n",[679,94122,94123],{"class":681,"line":982},[679,94124,94125],{}," \u003C/goals>\n",[679,94127,94128],{"class":681,"line":988},[679,94129,94130],{}," \u003Cconfiguration>\n",[679,94132,94133],{"class":681,"line":993},[679,94134,94135],{}," \u003CshadedArtifactAttached>true\u003C/shadedArtifactAttached>\n",[679,94137,94138],{"class":681,"line":2129},[679,94139,94140],{}," \u003CshadedClassifierName>aws-lambda\u003C/shadedClassifierName>\n",[679,94142,94143],{"class":681,"line":2140},[679,94144,94145],{}," \u003C/configuration>\n",[679,94147,94148],{"class":681,"line":2145},[679,94149,94150],{}," \u003C/execution>\n",[679,94152,94153],{"class":681,"line":2154},[679,94154,94155],{}," \u003C/executions>\n",[679,94157,94158],{"class":681,"line":2159},[679,94159,94160],{}," \u003C/plugin>\n",[679,94162,94163],{"class":681,"line":2164},[679,94164,94165],{}," \u003C/plugins>\n",[679,94167,94168],{"class":681,"line":3134},[679,94169,40776],{},[651,94171,94172],{},"Now let's build our project using the following command:",[669,94174,94175],{"className":5851,"code":81915,"language":5853,"meta":674,"style":674},[676,94176,94177],{"__ignoreMap":674},[679,94178,94179,94181,94183],{"class":681,"line":682},[679,94180,81922],{"class":880},[679,94182,81925],{"class":689},[679,94184,32496],{"class":689},[651,94186,94187,94188,94190],{},"This will create a JAR file in the ",[676,94189,77995],{}," directory of our project.",[4542,94192,94194],{"id":94193},"deploying-and-testing-the-lambda-function-on-aws","Deploying and Testing the Lambda Function on AWS",[651,94196,94197,94198,94203,94204,664],{},"Log into the ",[812,94199,94202],{"href":94200,"rel":94201},"https://console.aws.amazon.com/",[816],"AWS Management Console",", navigate to the Lambda service, and create a new function using the \"Author from scratch\" option. Give your function a name, choose the Java 11 (Corretto) runtime, and specify the Lambda handler using the format ",[676,94205,94206],{},"package.class::method",[651,94208,94209],{},[660,94210],{"alt":94211,"src":94212},"Create new Function","/images/blog/2022/11/09/create-function.png",[651,94214,94215],{},"Upload your JAR file built earlier by choosing \"Upload a file from Amazon S3\" or \"Upload a .zip file\", and configure the remaining settings according to your needs.",[651,94217,94218],{},"Once the function is created, you can test it using the AWS Console by creating a test event with an appropriate input. In our case, just pass a string as input. When you run the test, you should see the Lambda function return your greeting message.",[651,94220,94221],{},"You can also view logs and metrics for your Lambda function in the AWS Console.",[4542,94223,94225],{"id":94224},"conclusion-and-next-steps","Conclusion and Next Steps",[651,94227,94228],{},"In this tutorial, we've created a simple AWS Lambda function using plain Java and deployed it to AWS. We did not use any AWS libraries or services directly in our Lambda function, making it easy to build and test locally.",[651,94230,94231],{},"As you start building more complex serverless functions, you may want to explore using AWS SDKs, integrating with other AWS services like S3 or DynamoDB, or using frameworks like Spring Cloud Function to simplify the development process.",[651,94233,94234],{},"Stay tuned for more tutorials in this series where we'll dive deeper into various AWS Lambda topics, like handling events, using the AWS CLI, and leveraging the AWS CDK for infrastructure-as-code.",[786,94236,94237],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":94239},[94240,94241,94242,94243,94244],{"id":41932,"depth":790,"text":39958},{"id":93927,"depth":790,"text":93928},{"id":94048,"depth":790,"text":94049},{"id":94193,"depth":790,"text":94194},{"id":94224,"depth":790,"text":94225},"In this tutorial, I'll show you how to create a simple AWS Lambda function using Java.",{"slug":94247,"date":94248,"published":797,"author":798,"tags":94249,"cover":94250,"video":94251,"keywords":94252},"hello-aws-lambda-java","2022-11-09T08:00:00.000Z",[79471,4109],"./hello-aws-lambda.png","https://www.youtube.com/embed/MaHxZEBRcT4","AWS, AWS Lambda, AWS Java, AWS Lambda Java, AWS Lambda Java Tutorial, AWS Lambda Java Example, AWS Lambda Java Maven",{"title":375,"description":94245},"blog/2022/11/09/hello-aws-lambda-java","MGm5n4xThcYf9w8kVpUKnZEtd5YA-Pf2UVPV1UKlTt8",{"id":94257,"title":372,"body":94258,"description":95257,"extension":793,"meta":95258,"navigation":797,"path":373,"seo":95264,"stem":95265,"__hash__":95266},"content/blog/2022/11/10/aws-lambda-java-core.md",{"type":648,"value":94259,"toc":95249},[94260,94268,94275,94278,94282,94299,94305,94381,94392,94396,94410,94500,94510,94516,94536,94543,94547,94556,94635,94638,94642,94652,94657,94689,94694,94913,94921,95066,95069,95132,95135,95139,95142,95226,95232,95235,95237,95240,95243,95246],[651,94261,94262,94263,664],{},"In this tutorial, we are continuing our journey to build AWS Lambda functions in Java. Previously, we looked at the quintessential \"Hello World\" example - how to create our first serverless function using Java. If you missed it, you can find the ",[812,94264,94267],{"href":94265,"rel":94266},"https://www.danvega.dev/blog/2022/11/09/hello-aws-lambda-java/",[816],"previous tutorial here",[651,94269,94270,94271,94274],{},"Today, we will make progress by introducing a new library from AWS - the ",[2939,94272,94273],{},"AWS Lambda Core Java Library",". This library is one of the few AWS offers to assist in building serverless functions.",[651,94276,94277],{},"Let's build a new project, pull in the dependency, discuss what this new dependency provides, and build and test a new serverless function.",[4542,94279,94281],{"id":94280},"setting-up-the-project","Setting Up the Project",[651,94283,94284,94285,94288,94289,94291,94292,23212,94295,94298],{},"First, let's create a new Maven project in IntelliJ using the ",[676,94286,94287],{},"maven-archetype-quickstart"," archetype. Once the project is created, update the ",[676,94290,81517],{}," file to include the ",[7300,94293,94294],{},"AWS Lambda Core",[7300,94296,94297],{},"JUnit Jupiter"," dependencies:",[651,94300,94301],{},[660,94302],{"alt":94303,"src":94304},"Spring Initalizr","/images/blog/2022/11/10/start-spring-io.png",[669,94306,94308],{"className":9101,"code":94307,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003C!-- AWS Lambda Java Core -->\n \u003Cdependency>\n \u003CgroupId>com.amazonaws\u003C/groupId>\n \u003CartifactId>aws-lambda-java-core\u003C/artifactId>\n \u003Cversion>1.2.1\u003C/version>\n \u003C/dependency>\n \u003C!-- JUnit Jupiter -->\n \u003Cdependency>\n \u003CgroupId>org.junit.jupiter\u003C/groupId>\n \u003CartifactId>junit-jupiter\u003C/artifactId>\n \u003Cversion>5.8.2\u003C/version>\n \u003Cscope>test\u003C/scope>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,94309,94310,94314,94319,94324,94329,94334,94339,94344,94349,94353,94358,94363,94368,94373,94377],{"__ignoreMap":674},[679,94311,94312],{"class":681,"line":682},[679,94313,19199],{},[679,94315,94316],{"class":681,"line":790},[679,94317,94318],{}," \u003C!-- AWS Lambda Java Core -->\n",[679,94320,94321],{"class":681,"line":892},[679,94322,94323],{}," \u003Cdependency>\n",[679,94325,94326],{"class":681,"line":901},[679,94327,94328],{}," \u003CgroupId>com.amazonaws\u003C/groupId>\n",[679,94330,94331],{"class":681,"line":909},[679,94332,94333],{}," \u003CartifactId>aws-lambda-java-core\u003C/artifactId>\n",[679,94335,94336],{"class":681,"line":918},[679,94337,94338],{}," \u003Cversion>1.2.1\u003C/version>\n",[679,94340,94341],{"class":681,"line":935},[679,94342,94343],{}," \u003C/dependency>\n",[679,94345,94346],{"class":681,"line":944},[679,94347,94348],{}," \u003C!-- JUnit Jupiter -->\n",[679,94350,94351],{"class":681,"line":959},[679,94352,94323],{},[679,94354,94355],{"class":681,"line":964},[679,94356,94357],{}," \u003CgroupId>org.junit.jupiter\u003C/groupId>\n",[679,94359,94360],{"class":681,"line":977},[679,94361,94362],{}," \u003CartifactId>junit-jupiter\u003C/artifactId>\n",[679,94364,94365],{"class":681,"line":982},[679,94366,94367],{}," \u003Cversion>5.8.2\u003C/version>\n",[679,94369,94370],{"class":681,"line":988},[679,94371,94372],{}," \u003Cscope>test\u003C/scope>\n",[679,94374,94375],{"class":681,"line":993},[679,94376,94343],{},[679,94378,94379],{"class":681,"line":2129},[679,94380,19363],{},[651,94382,40060,94383,94386,94387,664],{},[676,94384,94385],{},"aws-lambda-java-core"," library is not required for building serverless functions with Java. This library does add some nice functionality including defining a handler method interface and the context object that the runtime passes to the handler. The library version may change, so make sure to check for the ",[812,94388,94391],{"href":94389,"rel":94390},"https://mvnrepository.com/artifact/com.amazonaws/aws-lambda-java-core",[816],"latest version here",[4542,94393,94395],{"id":94394},"creating-the-lambda-function-with-aws-lambda-core-java-library","Creating the Lambda Function with AWS Lambda Core Java Library",[651,94397,56985,94398,94401,94402,94405,94406,94409],{},[676,94399,94400],{},"src/main/java"," folder, create a new class named ",[676,94403,94404],{},"SimpleHandler",". Use the AWS Lambda Core Java Library to implement the ",[676,94407,94408],{},"RequestHandler"," interface:",[669,94411,94413],{"className":4107,"code":94412,"language":4109,"meta":674,"style":674},"import com.amazonaws.services.lambda.runtime.Context;\nimport com.amazonaws.services.lambda.runtime.RequestHandler;\n\npublic class SimpleHandler implements RequestHandler\u003CString, String> {\n\n @Override\n public String handleRequest(String input, Context context) {\n // Our code will be added here\n }\n}\n",[676,94414,94415,94422,94429,94433,94457,94461,94467,94487,94492,94496],{"__ignoreMap":674},[679,94416,94417,94419],{"class":681,"line":682},[679,94418,1999],{"class":685},[679,94420,94421],{"class":693}," com.amazonaws.services.lambda.runtime.Context;\n",[679,94423,94424,94426],{"class":681,"line":790},[679,94425,1999],{"class":685},[679,94427,94428],{"class":693}," com.amazonaws.services.lambda.runtime.RequestHandler;\n",[679,94430,94431],{"class":681,"line":892},[679,94432,889],{"emptyLinePlaceholder":797},[679,94434,94435,94437,94439,94442,94444,94447,94449,94451,94453,94455],{"class":681,"line":901},[679,94436,6073],{"class":685},[679,94438,4512],{"class":685},[679,94440,94441],{"class":880}," SimpleHandler",[679,94443,4661],{"class":685},[679,94445,94446],{"class":880}," RequestHandler",[679,94448,4505],{"class":693},[679,94450,4758],{"class":685},[679,94452,2797],{"class":693},[679,94454,4758],{"class":685},[679,94456,16397],{"class":693},[679,94458,94459],{"class":681,"line":909},[679,94460,889],{"emptyLinePlaceholder":797},[679,94462,94463,94465],{"class":681,"line":918},[679,94464,6872],{"class":693},[679,94466,10723],{"class":685},[679,94468,94469,94471,94473,94475,94477,94479,94482,94485],{"class":681,"line":935},[679,94470,6089],{"class":685},[679,94472,9289],{"class":693},[679,94474,93867],{"class":880},[679,94476,11400],{"class":693},[679,94478,27722],{"class":2099},[679,94480,94481],{"class":693},", Context ",[679,94483,94484],{"class":2099},"context",[679,94486,4390],{"class":693},[679,94488,94489],{"class":681,"line":944},[679,94490,94491],{"class":1400}," // Our code will be added here\n",[679,94493,94494],{"class":681,"line":959},[679,94495,985],{"class":693},[679,94497,94498],{"class":681,"line":964},[679,94499,996],{"class":693},[651,94501,56985,94502,94504,94505,23212,94507,94509],{},[676,94503,93867],{}," method, we can now access the ",[676,94506,27722],{},[676,94508,94484],{}," parameters provided by the AWS Lambda Core Java Library.",[651,94511,94512,94515],{},[676,94513,94514],{},"Context"," gives us access to useful information about the request, such as:",[5316,94517,94518,94521,94524,94527,94530,94533],{},[5332,94519,94520],{},"Request ID",[5332,94522,94523],{},"Log group name",[5332,94525,94526],{},"Function name",[5332,94528,94529],{},"Version",[5332,94531,94532],{},"Identity",[5332,94534,94535],{},"Other details",[651,94537,94538,94539,94542],{},"Additionally, we can access a ",[676,94540,94541],{},"LambdaLogger"," instance, which is useful for logging within our Lambda function.",[4542,94544,94546],{"id":94545},"implementing-the-lambda-function","Implementing the Lambda Function",[651,94548,94549,94550,94553,94554,72934],{},"In this example, our Lambda function will take a string input, convert it to uppercase, and return the result. To do this, we can use the ",[676,94551,94552],{},"toUpperCase()"," method provided by Java's ",[676,94555,4758],{},[669,94557,94559],{"className":4107,"code":94558,"language":4109,"meta":674,"style":674},"@Override\npublic String handleRequest(String input, Context context) {\n LambdaLogger logger = context.getLogger();\n logger.log(\"Function \" + context.getFunctionName() + \" called\");\n return input.toUpperCase();\n}\n",[676,94560,94561,94567,94578,94592,94620,94631],{"__ignoreMap":674},[679,94562,94563,94565],{"class":681,"line":682},[679,94564,4116],{"class":693},[679,94566,10723],{"class":685},[679,94568,94569,94571,94573,94575],{"class":681,"line":790},[679,94570,6073],{"class":685},[679,94572,9289],{"class":693},[679,94574,93867],{"class":880},[679,94576,94577],{"class":693},"(String input, Context context) {\n",[679,94579,94580,94583,94585,94588,94590],{"class":681,"line":892},[679,94581,94582],{"class":693}," LambdaLogger logger ",[679,94584,686],{"class":685},[679,94586,94587],{"class":693}," context.",[679,94589,9243],{"class":880},[679,94591,9317],{"class":693},[679,94593,94594,94597,94599,94601,94604,94606,94608,94611,94613,94615,94618],{"class":681,"line":901},[679,94595,94596],{"class":693}," logger.",[679,94598,21374],{"class":880},[679,94600,745],{"class":693},[679,94602,94603],{"class":689},"\"Function \"",[679,94605,3059],{"class":685},[679,94607,94587],{"class":693},[679,94609,94610],{"class":880},"getFunctionName",[679,94612,6700],{"class":693},[679,94614,3065],{"class":685},[679,94616,94617],{"class":689}," \" called\"",[679,94619,1208],{"class":693},[679,94621,94622,94624,94626,94629],{"class":681,"line":909},[679,94623,21478],{"class":685},[679,94625,15632],{"class":693},[679,94627,94628],{"class":880},"toUpperCase",[679,94630,9317],{"class":693},[679,94632,94633],{"class":681,"line":918},[679,94634,996],{"class":693},[651,94636,94637],{},"Now that we have our Lambda function implemented, we need to write tests for it.",[4542,94639,94641],{"id":94640},"writing-tests-for-the-lambda-function","Writing Tests for the Lambda Function",[651,94643,94644,94645,94647,94648,23212,94650,23545],{},"Since our Lambda function depends on the ",[676,94646,94514],{}," parameter provided by the AWS Core Java Library, we need to find a way to provide this context during testing. One approach is to use mocking libraries, such as Mockito, to create mock instances of the ",[676,94649,94514],{},[676,94651,94541],{},[651,94653,94654,94655,2391],{},"Let's add Mockito to our project by updating our ",[676,94656,81517],{},[669,94658,94660],{"className":9101,"code":94659,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.mockito\u003C/groupId>\n \u003CartifactId>mockito-junit-jupiter\u003C/artifactId>\n \u003Cversion>4.5.1\u003C/version>\n \u003Cscope>test\u003C/scope>\n\u003C/dependency>\n",[676,94661,94662,94666,94671,94676,94681,94685],{"__ignoreMap":674},[679,94663,94664],{"class":681,"line":682},[679,94665,9110],{},[679,94667,94668],{"class":681,"line":790},[679,94669,94670],{}," \u003CgroupId>org.mockito\u003C/groupId>\n",[679,94672,94673],{"class":681,"line":892},[679,94674,94675],{}," \u003CartifactId>mockito-junit-jupiter\u003C/artifactId>\n",[679,94677,94678],{"class":681,"line":901},[679,94679,94680],{}," \u003Cversion>4.5.1\u003C/version>\n",[679,94682,94683],{"class":681,"line":909},[679,94684,94372],{},[679,94686,94687],{"class":681,"line":918},[679,94688,9125],{},[651,94690,94691,94692,2391],{},"Reload the Maven changes and create a new test class for our ",[676,94693,94404],{},[669,94695,94697],{"className":4107,"code":94696,"language":4109,"meta":674,"style":674},"import com.amazonaws.services.lambda.runtime.Context;\nimport com.amazonaws.services.lambda.runtime.LambdaLogger;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(MockitoExtension.class)\nclass SimpleHandlerTest {\n\n private SimpleHandler simpleHandler;\n\n @Mock\n private Context context;\n\n @Mock\n private LambdaLogger logger;\n\n @BeforeEach\n void setUp() {\n // Set up your mocks\n }\n\n @Test\n void shouldReturnUppercaseOfInput() {\n // Write your test\n }\n}\n\n",[676,94698,94699,94705,94712,94719,94725,94731,94738,94745,94749,94757,94766,94775,94784,94788,94797,94806,94810,94817,94821,94828,94835,94839,94845,94852,94856,94863,94872,94877,94881,94885,94891,94900,94905,94909],{"__ignoreMap":674},[679,94700,94701,94703],{"class":681,"line":682},[679,94702,1999],{"class":685},[679,94704,94421],{"class":693},[679,94706,94707,94709],{"class":681,"line":790},[679,94708,1999],{"class":685},[679,94710,94711],{"class":693}," com.amazonaws.services.lambda.runtime.LambdaLogger;\n",[679,94713,94714,94716],{"class":681,"line":892},[679,94715,1999],{"class":685},[679,94717,94718],{"class":693}," org.junit.jupiter.api.BeforeEach;\n",[679,94720,94721,94723],{"class":681,"line":901},[679,94722,1999],{"class":685},[679,94724,73054],{"class":693},[679,94726,94727,94729],{"class":681,"line":909},[679,94728,1999],{"class":685},[679,94730,73359],{"class":693},[679,94732,94733,94735],{"class":681,"line":918},[679,94734,1999],{"class":685},[679,94736,94737],{"class":693}," org.mockito.Mock;\n",[679,94739,94740,94742],{"class":681,"line":935},[679,94741,1999],{"class":685},[679,94743,94744],{"class":693}," org.mockito.junit.jupiter.MockitoExtension;\n",[679,94746,94747],{"class":681,"line":944},[679,94748,889],{"emptyLinePlaceholder":797},[679,94750,94751,94753,94755],{"class":681,"line":959},[679,94752,1999],{"class":685},[679,94754,6092],{"class":685},[679,94756,73063],{"class":693},[679,94758,94759,94761,94763],{"class":681,"line":964},[679,94760,1999],{"class":685},[679,94762,6092],{"class":685},[679,94764,94765],{"class":693}," org.mockito.ArgumentMatchers.anyString;\n",[679,94767,94768,94770,94772],{"class":681,"line":977},[679,94769,1999],{"class":685},[679,94771,6092],{"class":685},[679,94773,94774],{"class":693}," org.mockito.Mockito.doAnswer;\n",[679,94776,94777,94779,94781],{"class":681,"line":982},[679,94778,1999],{"class":685},[679,94780,6092],{"class":685},[679,94782,94783],{"class":693}," org.mockito.Mockito.when;\n",[679,94785,94786],{"class":681,"line":988},[679,94787,889],{"emptyLinePlaceholder":797},[679,94789,94790,94792,94794],{"class":681,"line":993},[679,94791,4116],{"class":693},[679,94793,73408],{"class":685},[679,94795,94796],{"class":693},"(MockitoExtension.class)\n",[679,94798,94799,94801,94804],{"class":681,"line":2129},[679,94800,877],{"class":685},[679,94802,94803],{"class":880}," SimpleHandlerTest",[679,94805,884],{"class":693},[679,94807,94808],{"class":681,"line":2140},[679,94809,889],{"emptyLinePlaceholder":797},[679,94811,94812,94814],{"class":681,"line":2145},[679,94813,9232],{"class":685},[679,94815,94816],{"class":693}," SimpleHandler simpleHandler;\n",[679,94818,94819],{"class":681,"line":2154},[679,94820,889],{"emptyLinePlaceholder":797},[679,94822,94823,94825],{"class":681,"line":2159},[679,94824,6872],{"class":693},[679,94826,94827],{"class":685},"Mock\n",[679,94829,94830,94832],{"class":681,"line":2164},[679,94831,9232],{"class":685},[679,94833,94834],{"class":693}," Context context;\n",[679,94836,94837],{"class":681,"line":3134},[679,94838,889],{"emptyLinePlaceholder":797},[679,94840,94841,94843],{"class":681,"line":3139},[679,94842,6872],{"class":693},[679,94844,94827],{"class":685},[679,94846,94847,94849],{"class":681,"line":3144},[679,94848,9232],{"class":685},[679,94850,94851],{"class":693}," LambdaLogger logger;\n",[679,94853,94854],{"class":681,"line":3149},[679,94855,889],{"emptyLinePlaceholder":797},[679,94857,94858,94860],{"class":681,"line":3169},[679,94859,6872],{"class":693},[679,94861,94862],{"class":685},"BeforeEach\n",[679,94864,94865,94867,94870],{"class":681,"line":3185},[679,94866,3314],{"class":685},[679,94868,94869],{"class":880}," setUp",[679,94871,2667],{"class":693},[679,94873,94874],{"class":681,"line":3194},[679,94875,94876],{"class":1400}," // Set up your mocks\n",[679,94878,94879],{"class":681,"line":3199},[679,94880,985],{"class":693},[679,94882,94883],{"class":681,"line":3212},[679,94884,889],{"emptyLinePlaceholder":797},[679,94886,94887,94889],{"class":681,"line":3217},[679,94888,6872],{"class":693},[679,94890,73087],{"class":685},[679,94892,94893,94895,94898],{"class":681,"line":3222},[679,94894,3314],{"class":685},[679,94896,94897],{"class":880}," shouldReturnUppercaseOfInput",[679,94899,2667],{"class":693},[679,94901,94902],{"class":681,"line":3227},[679,94903,94904],{"class":1400}," // Write your test\n",[679,94906,94907],{"class":681,"line":3232},[679,94908,985],{"class":693},[679,94910,94911],{"class":681,"line":3499},[679,94912,996],{"class":693},[651,94914,56985,94915,94918,94919,2391],{},[676,94916,94917],{},"setUp()"," method, we need to configure our mocks and create a new instance of ",[676,94920,94404],{},[669,94922,94924],{"className":4107,"code":94923,"language":4109,"meta":674,"style":674},"@BeforeEach\nvoid setUp() {\n when(context.getLogger()).thenReturn(logger);\n\n doAnswer(invocation -> {\n System.out.println((String) invocation.getArguments()[0]);\n return null;\n }).when(logger).log(anyString());\n\n when(context.getFunctionName()).thenReturn(\"handleRequest\");\n\n simpleHandler = new SimpleHandler();\n}\n",[676,94925,94926,94932,94941,94959,94963,94975,94994,95002,95022,95026,95045,95049,95062],{"__ignoreMap":674},[679,94927,94928,94930],{"class":681,"line":682},[679,94929,4116],{"class":693},[679,94931,94862],{"class":685},[679,94933,94934,94937,94939],{"class":681,"line":790},[679,94935,94936],{"class":685},"void",[679,94938,94869],{"class":880},[679,94940,2667],{"class":693},[679,94942,94943,94946,94949,94951,94953,94956],{"class":681,"line":892},[679,94944,94945],{"class":880}," when",[679,94947,94948],{"class":693},"(context.",[679,94950,9243],{"class":880},[679,94952,90621],{"class":693},[679,94954,94955],{"class":880},"thenReturn",[679,94957,94958],{"class":693},"(logger);\n",[679,94960,94961],{"class":681,"line":901},[679,94962,889],{"emptyLinePlaceholder":797},[679,94964,94965,94968,94971,94973],{"class":681,"line":909},[679,94966,94967],{"class":880}," doAnswer",[679,94969,94970],{"class":693},"(invocation ",[679,94972,16955],{"class":685},[679,94974,884],{"class":693},[679,94976,94977,94979,94981,94984,94987,94990,94992],{"class":681,"line":918},[679,94978,9592],{"class":693},[679,94980,1729],{"class":880},[679,94982,94983],{"class":693},"((String) invocation.",[679,94985,94986],{"class":880},"getArguments",[679,94988,94989],{"class":693},"()[",[679,94991,1060],{"class":931},[679,94993,67196],{"class":693},[679,94995,94996,94998,95000],{"class":681,"line":935},[679,94997,9444],{"class":685},[679,94999,2307],{"class":931},[679,95001,1186],{"class":693},[679,95003,95004,95007,95010,95013,95015,95017,95020],{"class":681,"line":944},[679,95005,95006],{"class":693}," }).",[679,95008,95009],{"class":880},"when",[679,95011,95012],{"class":693},"(logger).",[679,95014,21374],{"class":880},[679,95016,745],{"class":693},[679,95018,95019],{"class":880},"anyString",[679,95021,9431],{"class":693},[679,95023,95024],{"class":681,"line":959},[679,95025,889],{"emptyLinePlaceholder":797},[679,95027,95028,95030,95032,95034,95036,95038,95040,95043],{"class":681,"line":964},[679,95029,94945],{"class":880},[679,95031,94948],{"class":693},[679,95033,94610],{"class":880},[679,95035,90621],{"class":693},[679,95037,94955],{"class":880},[679,95039,745],{"class":693},[679,95041,95042],{"class":689},"\"handleRequest\"",[679,95044,1208],{"class":693},[679,95046,95047],{"class":681,"line":977},[679,95048,889],{"emptyLinePlaceholder":797},[679,95050,95051,95054,95056,95058,95060],{"class":681,"line":982},[679,95052,95053],{"class":693}," simpleHandler ",[679,95055,686],{"class":685},[679,95057,2054],{"class":685},[679,95059,94441],{"class":880},[679,95061,9317],{"class":693},[679,95063,95064],{"class":681,"line":988},[679,95065,996],{"class":693},[651,95067,95068],{},"Now we can write our test method:",[669,95070,95072],{"className":4107,"code":95071,"language":4109,"meta":674,"style":674},"@Test\nvoid shouldReturnUppercaseOfInput() {\n when(context.getFunctionName()).thenReturn(\"handleRequest\");\n assertEquals(\"HELLO, WORLD!\",handler.handleRequest(\"Hello, World!\",context));\n}\n",[676,95073,95074,95080,95088,95106,95128],{"__ignoreMap":674},[679,95075,95076,95078],{"class":681,"line":682},[679,95077,4116],{"class":693},[679,95079,73087],{"class":685},[679,95081,95082,95084,95086],{"class":681,"line":790},[679,95083,94936],{"class":685},[679,95085,94897],{"class":880},[679,95087,2667],{"class":693},[679,95089,95090,95092,95094,95096,95098,95100,95102,95104],{"class":681,"line":892},[679,95091,94945],{"class":880},[679,95093,94948],{"class":693},[679,95095,94610],{"class":880},[679,95097,90621],{"class":693},[679,95099,94955],{"class":880},[679,95101,745],{"class":693},[679,95103,95042],{"class":689},[679,95105,1208],{"class":693},[679,95107,95108,95111,95113,95116,95119,95121,95123,95125],{"class":681,"line":901},[679,95109,95110],{"class":880}," assertEquals",[679,95112,745],{"class":693},[679,95114,95115],{"class":689},"\"HELLO, WORLD!\"",[679,95117,95118],{"class":693},",handler.",[679,95120,93867],{"class":880},[679,95122,745],{"class":693},[679,95124,71855],{"class":689},[679,95126,95127],{"class":693},",context));\n",[679,95129,95130],{"class":681,"line":909},[679,95131,996],{"class":693},[651,95133,95134],{},"With the test in place, we can run it to ensure our Lambda function works as expected.",[4542,95136,95138],{"id":95137},"building-and-deploying-the-lambda-function","Building and Deploying the Lambda Function",[651,95140,95141],{},"Finally, we can use the Maven Shade Plugin to package our Lambda function into an Uber JAR:",[669,95143,95145],{"className":9101,"code":95144,"language":9103,"meta":674,"style":674},"\u003Cbuild>\n \u003Cplugins>\n \u003Cplugin>\n \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n \u003CartifactId>maven-shade-plugin\u003C/artifactId>\n \u003Cversion>3.2.4\u003C/version>\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cphase>package\u003C/phase>\n \u003Cgoals>\n \u003Cgoal>shade\u003C/goal>\n \u003C/goals>\n \u003C/execution>\n \u003C/executions>\n \u003C/plugin>\n \u003C/plugins>\n\u003C/build>\n",[676,95146,95147,95151,95156,95160,95165,95170,95175,95180,95184,95189,95194,95199,95204,95208,95213,95217,95222],{"__ignoreMap":674},[679,95148,95149],{"class":681,"line":682},[679,95150,40689],{},[679,95152,95153],{"class":681,"line":790},[679,95154,95155],{}," \u003Cplugins>\n",[679,95157,95158],{"class":681,"line":892},[679,95159,40699],{},[679,95161,95162],{"class":681,"line":901},[679,95163,95164],{}," \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n",[679,95166,95167],{"class":681,"line":909},[679,95168,95169],{}," \u003CartifactId>maven-shade-plugin\u003C/artifactId>\n",[679,95171,95172],{"class":681,"line":918},[679,95173,95174],{}," \u003Cversion>3.2.4\u003C/version>\n",[679,95176,95177],{"class":681,"line":935},[679,95178,95179],{}," \u003Cexecutions>\n",[679,95181,95182],{"class":681,"line":944},[679,95183,81610],{},[679,95185,95186],{"class":681,"line":959},[679,95187,95188],{}," \u003Cphase>package\u003C/phase>\n",[679,95190,95191],{"class":681,"line":964},[679,95192,95193],{}," \u003Cgoals>\n",[679,95195,95196],{"class":681,"line":977},[679,95197,95198],{}," \u003Cgoal>shade\u003C/goal>\n",[679,95200,95201],{"class":681,"line":982},[679,95202,95203],{}," \u003C/goals>\n",[679,95205,95206],{"class":681,"line":988},[679,95207,81660],{},[679,95209,95210],{"class":681,"line":993},[679,95211,95212],{}," \u003C/executions>\n",[679,95214,95215],{"class":681,"line":2129},[679,95216,40713],{},[679,95218,95219],{"class":681,"line":2140},[679,95220,95221],{}," \u003C/plugins>\n",[679,95223,95224],{"class":681,"line":2145},[679,95225,40776],{},[651,95227,77982,95228,95231],{},[676,95229,95230],{},"mvn clean package"," to build the JAR file, and then upload it to AWS Lambda through the AWS Management Console.",[651,95233,95234],{},"When testing the Lambda function within the console, you should now see the input string being converted to uppercase, and the Lambda function name being logged.",[4542,95236,9042],{"id":9041},[651,95238,95239],{},"And that's it! We've successfully built and deployed our serverless Lambda function using the AWS Lambda Core Java Library. With this foundation in place, we can now explore more complex use cases and even incorporate other AWS services and technologies, such as Amazon API Gateway and the AWS Lambda Java Events library.",[651,95241,95242],{},"Stay tuned for more tutorials on building serverless functions with Java, as well as an upcoming video on creating serverless functions using Spring Boot and Spring Cloud Functions. As always...",[651,95244,95245],{},"Happy Coding 😎🧑💻👩💻",[786,95247,95248],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":95250},[95251,95252,95253,95254,95255,95256],{"id":94280,"depth":790,"text":94281},{"id":94394,"depth":790,"text":94395},{"id":94545,"depth":790,"text":94546},{"id":94640,"depth":790,"text":94641},{"id":95137,"depth":790,"text":95138},{"id":9041,"depth":790,"text":9042},"In this tutorial you will learn how to build AWS Lambda functions with Java using the AWS Lambda Core Java Library.",{"slug":94385,"date":95259,"published":797,"author":798,"tags":95260,"cover":95261,"video":95262,"keywords":95263},"2022-11-10T08:00:00.000Z",[79471,4109],"./aws-lambda-java-core.png","https://www.youtube.com/embed/kyWllXOGMWQ","AWS Lambda, AWS Lambda Java, AWS Lambda Java Core Library, Java",{"title":372,"description":95257},"blog/2022/11/10/aws-lambda-java-core","0mst4cMpBzzF8sYMjkzH1Linp4DKIPtiIZnZv1PNTjs",{"id":95268,"title":369,"body":95269,"description":95721,"extension":793,"meta":95722,"navigation":797,"path":370,"seo":95729,"stem":95730,"__hash__":95731},"content/blog/2022/12/01/spring-security-6.md",{"type":648,"value":95270,"toc":95714},[95271,95274,95276,95288,95293,95297,95307,95380,95384,95400,95530,95537,95541,95560,95567,95689,95695,95697,95706,95711],[651,95272,95273],{},"This article discusses the new features of Spring Security 6. If you haven't heard yet, Spring Boot 3.0 has been released, and if you include Spring Security as a dependency, you will now get Spring Security Version 6. In this blog post, we will delve into three important changes in Spring Security 6 and create a new Spring Boot 3 project together.",[4542,95275,44199],{"id":44198},[5316,95277,95278,95281],{},[5332,95279,95280],{},"Make sure you're using Java 17, as the baseline for Spring Boot 3 and Spring Security 6 is Java 17.",[5332,95282,95283,95284,95287],{},"Head over to ",[812,95285,77478],{"href":7115,"rel":95286},[816]," to create a new Spring Boot 3 project with the Spring Security dependency.",[651,95289,95290],{},[660,95291],{"alt":94303,"src":95292},"/images/blog/2022/12/01/spring-init.png",[4542,95294,95296],{"id":95295},"websecurityconfigureradapter-removed","WebSecurityConfigurerAdapter Removed",[651,95298,95299,95300,95302,95303,95306],{},"In previous versions of Spring Security, you had to extend the ",[676,95301,40080],{}," class to configure security settings. This class has been deprecated and removed in Spring Security 6. Instead, you should now take a more component-based approach and create a bean of type ",[676,95304,95305],{},"SecurityFilterChain",". Here's an example:",[669,95308,95310],{"className":4107,"code":95309,"language":4109,"meta":674,"style":674},"@EnableWebSecurity\n@Configuration\npublic class SecurityConfig {\n\n @Bean\n public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http.build();\n }\n}\n",[676,95311,95312,95318,95324,95334,95338,95344,95362,95372,95376],{"__ignoreMap":674},[679,95313,95314,95316],{"class":681,"line":682},[679,95315,4116],{"class":693},[679,95317,89474],{"class":685},[679,95319,95320,95322],{"class":681,"line":790},[679,95321,4116],{"class":693},[679,95323,6212],{"class":685},[679,95325,95326,95328,95330,95332],{"class":681,"line":892},[679,95327,6073],{"class":685},[679,95329,4512],{"class":685},[679,95331,89483],{"class":880},[679,95333,884],{"class":693},[679,95335,95336],{"class":681,"line":901},[679,95337,889],{"emptyLinePlaceholder":797},[679,95339,95340,95342],{"class":681,"line":909},[679,95341,6872],{"class":693},[679,95343,16929],{"class":685},[679,95345,95346,95348,95350,95352,95354,95356,95358,95360],{"class":681,"line":918},[679,95347,6089],{"class":685},[679,95349,89502],{"class":693},[679,95351,89505],{"class":880},[679,95353,89508],{"class":693},[679,95355,89511],{"class":2099},[679,95357,2378],{"class":693},[679,95359,9580],{"class":685},[679,95361,10466],{"class":693},[679,95363,95364,95366,95368,95370],{"class":681,"line":935},[679,95365,9444],{"class":685},[679,95367,92844],{"class":693},[679,95369,23612],{"class":880},[679,95371,9317],{"class":693},[679,95373,95374],{"class":681,"line":944},[679,95375,985],{"class":693},[679,95377,95378],{"class":681,"line":959},[679,95379,996],{"class":693},[4542,95381,95383],{"id":95382},"authorize-http-requests","Authorize Http Requests",[651,95385,95386,95387,95389,95390,95393,95394,95397,95398,2391],{},"Instead of using ",[676,95388,40110],{},", which has been deprecated, you should now use ",[676,95391,95392],{},"authorizeHttpRequests",". This method is part of the ",[676,95395,95396],{},"HttpSecurity"," configuration and allows you to configure fine-grained request matching for access control. Here's an example of how to use ",[676,95399,95392],{},[669,95401,95403],{"className":4107,"code":95402,"language":4109,"meta":674,"style":674},"@EnableWebSecurity\n@Configuration\npublic class SecurityConfig {\n\n @Bean\n public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http.authorizeHttpRequests(auth -> auth\n .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()\n .anyRequest().authenticated())\n .formLogin();\n .build();\n }\n}\n",[676,95404,95405,95411,95417,95427,95431,95437,95455,95470,95493,95505,95514,95522,95526],{"__ignoreMap":674},[679,95406,95407,95409],{"class":681,"line":682},[679,95408,4116],{"class":693},[679,95410,89474],{"class":685},[679,95412,95413,95415],{"class":681,"line":790},[679,95414,4116],{"class":693},[679,95416,6212],{"class":685},[679,95418,95419,95421,95423,95425],{"class":681,"line":892},[679,95420,6073],{"class":685},[679,95422,4512],{"class":685},[679,95424,89483],{"class":880},[679,95426,884],{"class":693},[679,95428,95429],{"class":681,"line":901},[679,95430,889],{"emptyLinePlaceholder":797},[679,95432,95433,95435],{"class":681,"line":909},[679,95434,6872],{"class":693},[679,95436,16929],{"class":685},[679,95438,95439,95441,95443,95445,95447,95449,95451,95453],{"class":681,"line":918},[679,95440,6089],{"class":685},[679,95442,89502],{"class":693},[679,95444,89505],{"class":880},[679,95446,89508],{"class":693},[679,95448,89511],{"class":2099},[679,95450,2378],{"class":693},[679,95452,9580],{"class":685},[679,95454,10466],{"class":693},[679,95456,95457,95459,95461,95463,95466,95468],{"class":681,"line":935},[679,95458,9444],{"class":685},[679,95460,92844],{"class":693},[679,95462,95392],{"class":880},[679,95464,95465],{"class":693},"(auth ",[679,95467,16955],{"class":685},[679,95469,89561],{"class":693},[679,95471,95472,95474,95476,95479,95482,95484,95487,95489,95491],{"class":681,"line":944},[679,95473,40148],{"class":693},[679,95475,40124],{"class":880},[679,95477,95478],{"class":693},"(PathRequest.",[679,95480,95481],{"class":880},"toStaticResources",[679,95483,10541],{"class":693},[679,95485,95486],{"class":880},"atCommonLocations",[679,95488,90621],{"class":693},[679,95490,40151],{"class":880},[679,95492,17545],{"class":693},[679,95494,95495,95497,95499,95501,95503],{"class":681,"line":959},[679,95496,40148],{"class":693},[679,95498,89569],{"class":880},[679,95500,10541],{"class":693},[679,95502,89574],{"class":880},[679,95504,40172],{"class":693},[679,95506,95507,95509,95512],{"class":681,"line":964},[679,95508,40148],{"class":693},[679,95510,95511],{"class":880},"formLogin",[679,95513,9317],{"class":693},[679,95515,95516,95518,95520],{"class":681,"line":977},[679,95517,40148],{"class":693},[679,95519,23612],{"class":880},[679,95521,9317],{"class":693},[679,95523,95524],{"class":681,"line":982},[679,95525,985],{"class":693},[679,95527,95528],{"class":681,"line":988},[679,95529,996],{"class":693},[651,95531,95532,95533,664],{},"In this example, we allow access to static resources, while requiring authentication for all other requests. If the syntax looks unfamiliar, this is because it is the Lambda DSL configuration. I have also written a tutorial on this, which can be found at ",[812,95534,18263],{"href":95535,"rel":95536},"https://www.danvega.dev/blog/2023/03/15/spring-security-lambda-dsl/",[816],[4542,95538,95540],{"id":95539},"requestmatchers-replacing-antmatcher-mvcmatcher-and-regexmatcher","RequestMatchers Replacing AntMatcher, MvcMatcher, and RegexMatcher",[651,95542,95543,95544,2797,95547,48406,95550,95553,95554,51393,95556,95559],{},"In Spring Security 6, ",[676,95545,95546],{},"AntMatcher",[676,95548,95549],{},"MvcMatcher",[676,95551,95552],{},"RegexMatcher"," have been deprecated and replaced by ",[676,95555,40124],{},[676,95557,95558],{},"securityMatchers"," for path-based access control. This allows you to match requests based on patterns or other criteria without relying on specific matchers.",[651,95561,95562,95563,95566],{},"Here's an example that permits access to the ",[676,95564,95565],{},"/greet"," endpoint without authentication while requiring authentication for all other requests:",[669,95568,95570],{"className":4107,"code":95569,"language":4109,"meta":674,"style":674},"@EnableWebSecurity\n@Configuration\npublic class SecurityConfig {\n\n @Bean\n public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http.authorizeHttpRequests(auth -> auth\n .requestMatchers(\"/greet\").permitAll()\n .anyRequest().authenticated())\n .formLogin()\n .build();\n }\n}\n",[676,95571,95572,95578,95584,95594,95598,95604,95622,95636,95653,95665,95673,95681,95685],{"__ignoreMap":674},[679,95573,95574,95576],{"class":681,"line":682},[679,95575,4116],{"class":693},[679,95577,89474],{"class":685},[679,95579,95580,95582],{"class":681,"line":790},[679,95581,4116],{"class":693},[679,95583,6212],{"class":685},[679,95585,95586,95588,95590,95592],{"class":681,"line":892},[679,95587,6073],{"class":685},[679,95589,4512],{"class":685},[679,95591,89483],{"class":880},[679,95593,884],{"class":693},[679,95595,95596],{"class":681,"line":901},[679,95597,889],{"emptyLinePlaceholder":797},[679,95599,95600,95602],{"class":681,"line":909},[679,95601,6872],{"class":693},[679,95603,16929],{"class":685},[679,95605,95606,95608,95610,95612,95614,95616,95618,95620],{"class":681,"line":918},[679,95607,6089],{"class":685},[679,95609,89502],{"class":693},[679,95611,89505],{"class":880},[679,95613,89508],{"class":693},[679,95615,89511],{"class":2099},[679,95617,2378],{"class":693},[679,95619,9580],{"class":685},[679,95621,10466],{"class":693},[679,95623,95624,95626,95628,95630,95632,95634],{"class":681,"line":935},[679,95625,9444],{"class":685},[679,95627,92844],{"class":693},[679,95629,95392],{"class":880},[679,95631,95465],{"class":693},[679,95633,16955],{"class":685},[679,95635,89561],{"class":693},[679,95637,95638,95640,95642,95644,95647,95649,95651],{"class":681,"line":944},[679,95639,40148],{"class":693},[679,95641,40124],{"class":880},[679,95643,745],{"class":693},[679,95645,95646],{"class":689},"\"/greet\"",[679,95648,47213],{"class":693},[679,95650,40151],{"class":880},[679,95652,17545],{"class":693},[679,95654,95655,95657,95659,95661,95663],{"class":681,"line":959},[679,95656,40148],{"class":693},[679,95658,89569],{"class":880},[679,95660,10541],{"class":693},[679,95662,89574],{"class":880},[679,95664,40172],{"class":693},[679,95666,95667,95669,95671],{"class":681,"line":964},[679,95668,40148],{"class":693},[679,95670,95511],{"class":880},[679,95672,17545],{"class":693},[679,95674,95675,95677,95679],{"class":681,"line":977},[679,95676,40148],{"class":693},[679,95678,23612],{"class":880},[679,95680,9317],{"class":693},[679,95682,95683],{"class":681,"line":982},[679,95684,985],{"class":693},[679,95686,95687],{"class":681,"line":988},[679,95688,996],{"class":693},[651,95690,95691,95692,95694],{},"Now, if you run your application and try to access the ",[676,95693,95565],{}," endpoint, you should see that you can access it without needing to log in. However, if you try to access any other endpoint, you'll be prompted to log in.",[4542,95696,78006],{"id":78005},[651,95698,95699,95700,95705],{},"I hope you found this overview of the important changes in Spring Security 6 helpful. Remember to go through the ",[812,95701,95704],{"href":95702,"rel":95703},"https://docs.spring.io/spring-security/site/docs/6.0.x/reference/html5/#new",[816],"release notes"," for Spring Security 6, as there are many breaking changes and new features that you should be aware of. If you have questions about any of these changes or need help with your application, feel free to reach out.",[651,95707,95708,95709,41109],{},"As always, happy coding, friends!",[41107,95710],{},[786,95712,95713],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":95715},[95716,95717,95718,95719,95720],{"id":44198,"depth":790,"text":44199},{"id":95295,"depth":790,"text":95296},{"id":95382,"depth":790,"text":95383},{"id":95539,"depth":790,"text":95540},{"id":78005,"depth":790,"text":78006},"In this article we will discuss the new features of Spring Security 6 and create a new Spring Boot 3 project together.",{"slug":95723,"date":95724,"published":797,"author":798,"tags":95725,"cover":95726,"video":95727,"keywords":95728},"spring-security-6","2022-12-01T16:00:00.000Z",[7077,23988],"./spring_security_6.png","https://www.youtube.com/embed/TDOHbK39Oxg","Spring Security, Spring Security 6, WebSecurityConfigurerAdapter, authorizeHttpRequests, requestMatchers, securityMatchers, Spring Security Lambda DSL",{"title":369,"description":95721},"blog/2022/12/01/spring-security-6","Pueh-_4KjJzObV1Jk_u2sSpLjTGTsyJlM-xOllBa2dc",{"id":95733,"title":366,"body":95734,"description":95939,"extension":793,"meta":95940,"navigation":797,"path":367,"seo":95947,"stem":95948,"__hash__":95949},"content/blog/2022/12/02/aws-lambda-snapstart-spring.md",{"type":648,"value":95735,"toc":95932},[95736,95744,95747,95750,95763,95768,95771,95775,95778,95837,95840,95844,95847,95895,95899,95902,95909,95916,95919,95921,95924,95927,95929],[651,95737,95738,95743],{},[812,95739,95742],{"href":95740,"rel":95741},"https://aws.amazon.com/blogs/compute/starting-up-faster-with-aws-lambda-snapstart/",[816],"AWS Lambda SnapStart"," is a new performance optimization developed by AWS that can significantly improve the startup time for your applications. Currently, it is only available for Java in certain regions, but Java is usually the runtime affected by cold start times. This feature delivers up to 10x faster function startup times for latency-sensitive Java applications at no extra cost and with no or minimal code changes. That's really nice!",[651,95745,95746],{},"In this blog post, we'll create a new project, include Spring Boot, and Spring Cloud Function. We'll talk about writing this function, pushing it to AWS Lambda, and then enabling SnapStart. Finally, we'll compare the init time before and after SnapStart.",[4542,95748,95749],{"id":54715},"Creating a New Project",[651,95751,95752,95753,95756,95757,23212,95759,95762],{},"To create a new project, head over to ",[812,95754,77478],{"href":7115,"rel":95755},[816]," and choose Maven as your build tool, Java as your language, and Spring Boot 2.7.6 because AWS doesn't support Java 17 yet. Make sure to add ",[676,95758,92944],{},[676,95760,95761],{},"Spring Cloud Function"," as your project dependencies.",[651,95764,95765],{},[660,95766],{"alt":7117,"src":95767},"/images/blog/2022/12/02/start-spring-io.png",[651,95769,95770],{},"After generating the project, open it up in your favorite IDE.",[4542,95772,95774],{"id":95773},"writing-the-function","Writing the Function",[651,95776,95777],{},"In your main application class, create a new Bean with a Function that takes in a string and reverses it. Here's an example implementation:",[669,95779,95781],{"className":4107,"code":95780,"language":4109,"meta":674,"style":674},"@Bean\npublic Function\u003CString, String> reverse() {\n return string -> new StringBuilder(string).reverse().toString();\n}\n",[676,95782,95783,95789,95808,95833],{"__ignoreMap":674},[679,95784,95785,95787],{"class":681,"line":682},[679,95786,4116],{"class":693},[679,95788,16929],{"class":685},[679,95790,95791,95793,95796,95798,95801,95803,95806],{"class":681,"line":790},[679,95792,6073],{"class":685},[679,95794,95795],{"class":693}," Function",[679,95797,4505],{"class":685},[679,95799,95800],{"class":693},"String, String",[679,95802,5860],{"class":685},[679,95804,95805],{"class":880}," reverse",[679,95807,2667],{"class":693},[679,95809,95810,95812,95814,95816,95818,95821,95824,95827,95829,95831],{"class":681,"line":892},[679,95811,21478],{"class":685},[679,95813,21450],{"class":693},[679,95815,16955],{"class":685},[679,95817,2054],{"class":685},[679,95819,95820],{"class":880}," StringBuilder",[679,95822,95823],{"class":693},"(string).",[679,95825,95826],{"class":880},"reverse",[679,95828,10541],{"class":693},[679,95830,14391],{"class":880},[679,95832,9317],{"class":693},[679,95834,95835],{"class":681,"line":901},[679,95836,996],{"class":693},[651,95838,95839],{},"Now you can test your application locally to make sure the function works. Once you know the function works it’s time to deploy it to AWS Lambda.",[4542,95841,95843],{"id":95842},"building-and-deploying-to-aws-lambda","Building and Deploying to AWS Lambda",[651,95845,95846],{},"To build and deploy your function to AWS Lambda, follow these steps:",[27665,95848,95849,95860,95866,95871,95879,95889],{},[5332,95850,95851,95852,95854,95855,664],{},"Add the Spring Cloud Function Adapter for AWS dependency to your ",[676,95853,81517],{},". Find more details in this article: ",[812,95856,95859],{"href":95857,"rel":95858},"https://tanzu.vmware.com/developer/guides/spring/spring-cloud-function/#installation",[816],"Serverless Spring",[5332,95861,95862,95863,95865],{},"Modify your build plugins section in ",[676,95864,81517],{}," as shown in the Serverless Spring article.",[5332,95867,77982,95868,95870],{},[676,95869,95230],{}," in your terminal to build your project.",[5332,95872,95873,95874,95878],{},"Head over to the ",[812,95875,94202],{"href":95876,"rel":95877},"https://aws.amazon.com/console/",[816]," and create a new function, authoring it from scratch. Choose the Coretto 11 runtime while creating the new function.",[5332,95880,95881,95882,95885,95886,95888],{},"Upload the built ",[676,95883,95884],{},".jar"," file from the ",[676,95887,77995],{}," directory to your function.",[5332,95890,95891,95892,664],{},"Set the handler for your function to ",[676,95893,95894],{},"org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest",[4542,95896,95898],{"id":95897},"testing-your-function-and-enabling-snapstart","Testing Your Function and Enabling SnapStart",[651,95900,95901],{},"Now that your function is deployed to AWS Lambda, test it and observe the init duration. After the first run, the init duration should improve significantly.",[651,95903,95904,95905,95908],{},"To enable SnapStart, go to your function's configuration and edit the ",[676,95906,95907],{},"Runtime Settings",". Set the supported runtime to Java 11 and turn on the SnapStart option. Save your changes and publish a new version of your function.",[651,95910,95911,95912,95915],{},"After enabling SnapStart, test your function again and observe the ",[676,95913,95914],{},"Restore duration",". You should notice a significant decrease compared to the original init duration.",[651,95917,95918],{},"For example, your init duration may have gone from around 5 seconds to just 351 milliseconds. This is a huge improvement, and it's an exciting new feature for serverless Java developers.",[4542,95920,9042],{"id":9041},[651,95922,95923],{},"AWS Lambda SnapStart offers a great performance improvement for Java applications with minimal to no code changes required. This new feature will make serverless Java development more accessible and efficient, especially for latency-sensitive applications.",[651,95925,95926],{},"If you found value in this blog post, please consider signing up for my FREE weekly newsletter below. Thank you and as always friends…",[651,95928,44143],{},[786,95930,95931],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":95933},[95934,95935,95936,95937,95938],{"id":54715,"depth":790,"text":95749},{"id":95773,"depth":790,"text":95774},{"id":95842,"depth":790,"text":95843},{"id":95897,"depth":790,"text":95898},{"id":9041,"depth":790,"text":9042},"AWS Lambda SnapStart is a new performance optimization developed by AWS that can significantly improve the startup time for your applications.",{"slug":95941,"date":95942,"published":797,"author":798,"tags":95943,"cover":95944,"video":95945,"keywords":95946},"aws-lambda-snapstart-spring","2022-12-02T08:00:00.000Z",[79471,7077],"./aws_lambda_snapstart.png","https://www.youtube.com/embed/isS6m6aj_Ak","AWS, AWS Lambda, Spring Boot, Spring Framework, Java, Spring Cloud, Spring Cloud Function, Spring Cloud AWS",{"title":366,"description":95939},"blog/2022/12/02/aws-lambda-snapstart-spring","qZR3Bewn5Ho4PoQkIMs2y7b-QfuD6tZI3RwDINEcXPA",{"id":95951,"title":363,"body":95952,"description":96321,"extension":793,"meta":96322,"navigation":797,"path":364,"seo":96329,"stem":96330,"__hash__":96331},"content/blog/2022/12/16/spring-response-entity.md",{"type":648,"value":95953,"toc":96312},[95954,95963,95967,95970,95975,95981,95984,95988,96000,96003,96007,96010,96027,96030,96079,96082,96140,96143,96147,96150,96232,96235,96239,96246,96301,96304,96306,96309],[651,95955,95956,95957,95962],{},"Recently, I sent out a ",[812,95958,95961],{"href":95959,"rel":95960},"https://twitter.com/therealdanvega/status/1599814600463355906",[816],"tweet"," that received a lot of attention. My tweet discussed the use of the ResponseEntity class in Spring framework and when to use it in your projects. In this blog post, I'll go through the ResponseEntity class in detail, and show you an example of how to use it in your next application.",[4542,95964,95966],{"id":95965},"the-tweet","The Tweet",[651,95968,95969],{},"Here's the tweet I sent out:",[1004,95971,95972],{},[651,95973,95974],{},"I often see spring developers returning a ResponseEntity and not manipulating the response. If all you're going to do is this and just return the body, just return that as the return type. If you need to customize the response and you need to add headers or change the status code, you can use a ResponseEntity as the return type.",[651,95976,95977],{},[660,95978],{"alt":95979,"src":95980},"Spring Response Entity Tweet","/images/blog/2022/12/16/response-entity-tweet.png",[651,95982,95983],{},"This tweet got more than 600 likes, 57 retweets, and many comments. Let's get into the ResponseEntity class and see what it's all about.",[4542,95985,95987],{"id":95986},"what-is-responseentity","What is ResponseEntity?",[651,95989,95990,95991,95996,95997,95999],{},"According to the ",[812,95992,95995],{"href":95993,"rel":95994},"https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-chapter.view-based-rendering",[816],"Spring framework API docs",", ResponseEntity is an extension of the HttpEntity class that adds an HTTP status code. It is used in the RestTemplate as well as in ",[676,95998,12300],{}," methods. You can also use it in Spring MVC as the return value from a controller method. RestController, in fact, is a combination of @Controller and another annotation, so ResponseEntity is useful when you're building a REST API.",[651,96001,96002],{},"Let's understand ResponseEntity better with an example.",[4542,96004,96006],{"id":96005},"activity-tracker-example","Activity Tracker Example",[651,96008,96009],{},"I'll use IntelliJ IDEA for this demonstration. Suppose we have a simple project for an activity tracker that records running activities. Each activity has a title, start time, and finish time. I've set up a simple Spring boot project with an ActivityController and an ActivityRepository using Spring Data.",[669,96011,96013],{"className":4107,"code":96012,"language":4109,"meta":674,"style":674},"public record Activity(Integer id, String title, LocalDateTime started, LocalDateTime completed) { }\n",[676,96014,96015],{"__ignoreMap":674},[679,96016,96017,96019,96021,96024],{"class":681,"line":682},[679,96018,6073],{"class":685},[679,96020,86928],{"class":685},[679,96022,96023],{"class":880}," Activity",[679,96025,96026],{"class":693},"(Integer id, String title, LocalDateTime started, LocalDateTime completed) { }\n",[651,96028,96029],{},"Let's create a method that allows us to get all the activities in the database.",[669,96031,96033],{"className":4107,"code":96032,"language":4109,"meta":674,"style":674},"@GetMapping\npublic List\u003CActivity> findAll() {\n return activityRepository.findAll(); //returns all activities from the repository\n}\n",[676,96034,96035,96041,96060,96075],{"__ignoreMap":674},[679,96036,96037,96039],{"class":681,"line":682},[679,96038,4116],{"class":693},[679,96040,80415],{"class":685},[679,96042,96043,96045,96048,96050,96053,96055,96058],{"class":681,"line":790},[679,96044,6073],{"class":685},[679,96046,96047],{"class":693}," List",[679,96049,4505],{"class":685},[679,96051,96052],{"class":693},"Activity",[679,96054,5860],{"class":685},[679,96056,96057],{"class":880}," findAll",[679,96059,2667],{"class":693},[679,96061,96062,96064,96067,96069,96072],{"class":681,"line":892},[679,96063,21478],{"class":685},[679,96065,96066],{"class":693}," activityRepository.",[679,96068,34142],{"class":880},[679,96070,96071],{"class":693},"(); ",[679,96073,96074],{"class":1400},"//returns all activities from the repository\n",[679,96076,96077],{"class":681,"line":901},[679,96078,996],{"class":693},[651,96080,96081],{},"This method returns a list of activities. A common pattern I see is to change the return type of this method to ResponseEntity without manipulating the response.",[669,96083,96085],{"className":4107,"code":96084,"language":4109,"meta":674,"style":674},"@GetMapping\npublic ResponseEntity\u003CList\u003CActivity>> findAll() {\n return ResponseEntity.ok(activityRepository.findAll()); // do NOT do this\n}\n",[676,96086,96087,96093,96115,96136],{"__ignoreMap":674},[679,96088,96089,96091],{"class":681,"line":682},[679,96090,4116],{"class":693},[679,96092,80415],{"class":685},[679,96094,96095,96097,96100,96102,96104,96106,96108,96111,96113],{"class":681,"line":790},[679,96096,6073],{"class":685},[679,96098,96099],{"class":693}," ResponseEntity",[679,96101,4505],{"class":685},[679,96103,19754],{"class":693},[679,96105,4505],{"class":685},[679,96107,96052],{"class":693},[679,96109,96110],{"class":685},">>",[679,96112,96057],{"class":880},[679,96114,2667],{"class":693},[679,96116,96117,96119,96122,96125,96128,96130,96133],{"class":681,"line":892},[679,96118,21478],{"class":685},[679,96120,96121],{"class":693}," ResponseEntity.",[679,96123,96124],{"class":880},"ok",[679,96126,96127],{"class":693},"(activityRepository.",[679,96129,34142],{"class":880},[679,96131,96132],{"class":693},"()); ",[679,96134,96135],{"class":1400},"// do NOT do this\n",[679,96137,96138],{"class":681,"line":901},[679,96139,996],{"class":693},[651,96141,96142],{},"Returning either a list of activities or a ResponseEntity list of activities generates the same output, so using ResponseEntity in this case is redundant. I also need to make sure I point out there there is absolutely nothing wrong with the code above.",[5909,96144,96146],{"id":96145},"when-to-use-responseentity","When to Use ResponseEntity",[651,96148,96149],{},"A ResponseEntity becomes useful when you want to manipulate the status code or add headers to the response. In this case, it makes sense to use ResponseEntity, as you cannot add new headers if you're just returning the list of activities:",[669,96151,96153],{"className":4107,"code":96152,"language":4109,"meta":674,"style":674},"@GetMapping\npublic ResponseEntity\u003CList\u003CActivity>> findAll() {\n HttpHeaders responseHeaders = new HttpHeaders();\n responseHeaders.set(\"My-Custom-Header\", \"My-Custom-Value\");\n return new ResponseEntity\u003C>(activityRepository.findAll(), responseHeaders, HttpStatus.OK);\n}\n",[676,96154,96155,96161,96181,96195,96214,96228],{"__ignoreMap":674},[679,96156,96157,96159],{"class":681,"line":682},[679,96158,4116],{"class":693},[679,96160,80415],{"class":685},[679,96162,96163,96165,96167,96169,96171,96173,96175,96177,96179],{"class":681,"line":790},[679,96164,6073],{"class":685},[679,96166,96099],{"class":693},[679,96168,4505],{"class":685},[679,96170,19754],{"class":693},[679,96172,4505],{"class":685},[679,96174,96052],{"class":693},[679,96176,96110],{"class":685},[679,96178,96057],{"class":880},[679,96180,2667],{"class":693},[679,96182,96183,96186,96188,96190,96193],{"class":681,"line":892},[679,96184,96185],{"class":693}," HttpHeaders responseHeaders ",[679,96187,686],{"class":685},[679,96189,2054],{"class":685},[679,96191,96192],{"class":880}," HttpHeaders",[679,96194,9317],{"class":693},[679,96196,96197,96200,96202,96204,96207,96209,96212],{"class":681,"line":901},[679,96198,96199],{"class":693}," responseHeaders.",[679,96201,64453],{"class":880},[679,96203,745],{"class":693},[679,96205,96206],{"class":689},"\"My-Custom-Header\"",[679,96208,2797],{"class":693},[679,96210,96211],{"class":689},"\"My-Custom-Value\"",[679,96213,1208],{"class":693},[679,96215,96216,96218,96220,96223,96225],{"class":681,"line":909},[679,96217,21478],{"class":685},[679,96219,2054],{"class":685},[679,96221,96222],{"class":693}," ResponseEntity\u003C>(activityRepository.",[679,96224,34142],{"class":880},[679,96226,96227],{"class":693},"(), responseHeaders, HttpStatus.OK);\n",[679,96229,96230],{"class":681,"line":918},[679,96231,996],{"class":693},[651,96233,96234],{},"Now, when you make a request to this method, you'll see that the custom header is set in the response.",[5909,96236,96238],{"id":96237},"changing-the-status-code","Changing the Status Code",[651,96240,96241,96242,96245],{},"You might want to change the status code when you create a new activity. In this case, you can use the ",[676,96243,96244],{},"@ResponseStatus"," annotation to set a different status code. For example, if you want to return a 201 (Created) status code, you can do this:",[669,96247,96249],{"className":4107,"code":96248,"language":4109,"meta":674,"style":674},"@PostMapping\n@ResponseStatus(HttpStatus.CREATED)\npublic Activity createActivity(@RequestBody Activity activity) {\n return activityRepository.save(activity);\n}\n",[676,96250,96251,96258,96268,96286,96297],{"__ignoreMap":674},[679,96252,96253,96255],{"class":681,"line":682},[679,96254,4116],{"class":693},[679,96256,96257],{"class":685},"PostMapping\n",[679,96259,96260,96262,96265],{"class":681,"line":790},[679,96261,4116],{"class":693},[679,96263,96264],{"class":685},"ResponseStatus",[679,96266,96267],{"class":693},"(HttpStatus.CREATED)\n",[679,96269,96270,96272,96275,96278,96280,96283],{"class":681,"line":892},[679,96271,6073],{"class":685},[679,96273,96274],{"class":693}," Activity ",[679,96276,96277],{"class":880},"createActivity",[679,96279,73246],{"class":693},[679,96281,96282],{"class":685},"RequestBody",[679,96284,96285],{"class":693}," Activity activity) {\n",[679,96287,96288,96290,96292,96294],{"class":681,"line":901},[679,96289,21478],{"class":685},[679,96291,96066],{"class":693},[679,96293,7629],{"class":880},[679,96295,96296],{"class":693},"(activity);\n",[679,96298,96299],{"class":681,"line":909},[679,96300,996],{"class":693},[651,96302,96303],{},"However, if the status code you want to set is conditional on some logic, then using ResponseEntity is the right choice.",[4542,96305,78006],{"id":78005},[651,96307,96308],{},"In summary, ResponseEntity is useful when you need to manipulate the status code or add custom headers to your response. It's not necessary to use ResponseEntity in other cases, such as when you're simply returning the data. A common argument I hear for using ResponseEntity is to be consistent with the return type for all methods. I’m not a big proponent of being consistent for consistent sake. This discussion on when to use ResponseEntity has been a valuable learning experience for me, and I hope it's been helpful for you too.",[786,96310,96311],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":96313},[96314,96315,96316,96320],{"id":95965,"depth":790,"text":95966},{"id":95986,"depth":790,"text":95987},{"id":96005,"depth":790,"text":96006,"children":96317},[96318,96319],{"id":96145,"depth":892,"text":96146},{"id":96237,"depth":892,"text":96238},{"id":78005,"depth":790,"text":78006},"In this tutorial, you will learn what the ResponseEntity class is and how it can be used to customize the response in your Spring Boot application.",{"slug":96323,"date":96324,"published":797,"author":798,"tags":96325,"cover":96326,"video":96327,"keywords":96328},"spring-response-entity","2022-12-16T12:00:00.000Z",[7077],"./spring-response-entity.png","https://www.youtube.com/embed/B5Zrn1Tzyqw","Spring Boot, Spring Framework, REST API, ResponseEntity, Spring Boot ResponseEntity, Spring Boot Response",{"title":363,"description":96321},"blog/2022/12/16/spring-response-entity","zi_oT6CV9jR-cxgxwtzzmGGcjpf8tX-ESbLsog0-TO0",{"id":96333,"title":360,"body":96334,"description":849,"extension":793,"meta":96964,"navigation":797,"path":361,"seo":96971,"stem":96972,"__hash__":96973},"content/blog/2022/12/19/spring-proxy-bean-methods.md",{"type":648,"value":96335,"toc":96957},[96336,96349,96353,96358,96365,96371,96375,96378,96489,96500,96504,96511,96571,96581,96720,96723,96728,96732,96737,96759,96762,96765,96869,96872,96946,96949,96951,96954],[651,96337,96338,96339,96341,96342,96345,96346,96348],{},"In this blog post, we will explore the configuration classes in Spring and explain how setting a property on the ",[676,96340,6139],{}," annotation of ",[676,96343,96344],{},"proxyBeanMethods=false"," can resolve certain issues. We will guide you through creating a few services that depend on a RestTemplate using the ",[676,96347,6139],{}," annotation. Along the way, we will examine a problem and how to address it.",[4542,96350,96352],{"id":96351},"create-a-new-project-using-the-spring-initializr","Create a new project using the Spring Initializr",[651,96354,96355],{},[660,96356],{"alt":7117,"src":96357},"/images/blog/2022/12/19/spring-init.png",[651,96359,96360,96361,96364],{},"Let's start by creating a new project at ",[812,96362,77478],{"href":20748,"rel":96363},[816],". I have bookmarked this site, and I recommend you do the same. I am currently creating a Runners application for an upcoming Spring Workshop, and we'll use this to start the tutorial.",[651,96366,96367,96368,96370],{},"We'll choose Maven and Java, using the latest version of Spring Boot 3.0. We will create a group called ",[676,96369,92925],{}," with this project's artifact, Runners. The only dependency we need right now is the web, and we'll use a rest template underneath the hood.",[4542,96372,96374],{"id":96373},"setting-up-the-services","Setting up the Services",[651,96376,96377],{},"Let's create a couple of classes that will be the services that we'll use in our application. These classes will require the use of a rest template. We'll create a Run Service and a Track Service in our application, each with a rest template as a dependency.",[669,96379,96381],{"className":4107,"code":96380,"language":4109,"meta":674,"style":674},"public class RunService {\n private final RestTemplate restTemplate;\n public RunService(RestTemplate restTemplate) {\n this.restTemplate = restTemplate;\n }\n}\n\npublic class TrackService {\n private final RestTemplate restTemplate;\n public TrackService(RestTemplate restTemplate) {\n this.restTemplate = restTemplate;\n }\n}\n",[676,96382,96383,96394,96403,96416,96428,96432,96436,96440,96451,96459,96471,96481,96485],{"__ignoreMap":674},[679,96384,96385,96387,96389,96392],{"class":681,"line":682},[679,96386,6073],{"class":685},[679,96388,4512],{"class":685},[679,96390,96391],{"class":880}," RunService",[679,96393,884],{"class":693},[679,96395,96396,96398,96400],{"class":681,"line":790},[679,96397,9232],{"class":685},[679,96399,12768],{"class":685},[679,96401,96402],{"class":693}," RestTemplate restTemplate;\n",[679,96404,96405,96407,96409,96412,96414],{"class":681,"line":892},[679,96406,6089],{"class":685},[679,96408,96391],{"class":880},[679,96410,96411],{"class":693},"(RestTemplate ",[679,96413,23596],{"class":2099},[679,96415,4390],{"class":693},[679,96417,96418,96420,96423,96425],{"class":681,"line":901},[679,96419,7862],{"class":931},[679,96421,96422],{"class":693},".restTemplate ",[679,96424,686],{"class":685},[679,96426,96427],{"class":693}," restTemplate;\n",[679,96429,96430],{"class":681,"line":909},[679,96431,985],{"class":693},[679,96433,96434],{"class":681,"line":918},[679,96435,996],{"class":693},[679,96437,96438],{"class":681,"line":935},[679,96439,889],{"emptyLinePlaceholder":797},[679,96441,96442,96444,96446,96449],{"class":681,"line":944},[679,96443,6073],{"class":685},[679,96445,4512],{"class":685},[679,96447,96448],{"class":880}," TrackService",[679,96450,884],{"class":693},[679,96452,96453,96455,96457],{"class":681,"line":959},[679,96454,9232],{"class":685},[679,96456,12768],{"class":685},[679,96458,96402],{"class":693},[679,96460,96461,96463,96465,96467,96469],{"class":681,"line":964},[679,96462,6089],{"class":685},[679,96464,96448],{"class":880},[679,96466,96411],{"class":693},[679,96468,23596],{"class":2099},[679,96470,4390],{"class":693},[679,96472,96473,96475,96477,96479],{"class":681,"line":977},[679,96474,7862],{"class":931},[679,96476,96422],{"class":693},[679,96478,686],{"class":685},[679,96480,96427],{"class":693},[679,96482,96483],{"class":681,"line":982},[679,96484,985],{"class":693},[679,96486,96487],{"class":681,"line":988},[679,96488,996],{"class":693},[651,96490,96491,96492,96494,96495,664],{},"We're not annotating these with \"@Service\" because we'll create these using the \"app Bean\" annotation. If you’re new to Spring Beans and what the ",[676,96493,16857],{}," annotation does please check out my post on ",[812,96496,96499],{"href":96497,"rel":96498},"https://www.danvega.dev/blog/2017/05/17/spring-component-vs-bean/",[816],"Component vs Bean",[4542,96501,96503],{"id":96502},"creating-a-configuration-class","Creating a Configuration Class",[651,96505,96506,96507,96510],{},"Let's create a configuration class called ",[676,96508,96509],{},"MyAppConfig"," We'll start by creating a bean of type RestTemplate, which will be used in both of our services.",[669,96512,96514],{"className":4107,"code":96513,"language":4109,"meta":674,"style":674},"@Configuration\npublic class MyAppConfig {\n @Bean\n public RestTemplate restTemplate() {\n return new RestTemplateBuilder().build();\n }\n}\n",[676,96515,96516,96522,96533,96539,96549,96563,96567],{"__ignoreMap":674},[679,96517,96518,96520],{"class":681,"line":682},[679,96519,4116],{"class":693},[679,96521,6212],{"class":685},[679,96523,96524,96526,96528,96531],{"class":681,"line":790},[679,96525,6073],{"class":685},[679,96527,4512],{"class":685},[679,96529,96530],{"class":880}," MyAppConfig",[679,96532,884],{"class":693},[679,96534,96535,96537],{"class":681,"line":892},[679,96536,6872],{"class":693},[679,96538,16929],{"class":685},[679,96540,96541,96543,96545,96547],{"class":681,"line":901},[679,96542,6089],{"class":685},[679,96544,23593],{"class":693},[679,96546,23596],{"class":880},[679,96548,2667],{"class":693},[679,96550,96551,96553,96555,96557,96559,96561],{"class":681,"line":909},[679,96552,9444],{"class":685},[679,96554,2054],{"class":685},[679,96556,23607],{"class":880},[679,96558,10541],{"class":693},[679,96560,23612],{"class":880},[679,96562,9317],{"class":693},[679,96564,96565],{"class":681,"line":918},[679,96566,985],{"class":693},[679,96568,96569],{"class":681,"line":935},[679,96570,996],{"class":693},[651,96572,96573,96574,23212,96577,96580],{},"We have created a Singleton of RestTemplate, which we can now use in both of our services. We can create two more beans called ",[676,96575,96576],{},"RunService",[676,96578,96579],{},"TrackService"," . We'll return a new instance of both of these services. The constructor in both of these services needs an instance of rest template, so we'll call that corresponding method.",[669,96582,96584],{"className":4107,"code":96583,"language":4109,"meta":674,"style":674},"@Configuration\npublic class MyAppConfig {\n @Bean\n public RestTemplate restTemplate() {\n return new RestTemplateBuilder().build();\n }\n\n @Bean\n public RunService runService() {\n return new RunService(restTemplate());\n }\n\n @Bean\n public TrackService trackService() {\n return new TrackService(restTemplate());\n }\n}\n",[676,96585,96586,96592,96602,96608,96618,96632,96636,96640,96646,96658,96672,96676,96680,96686,96698,96712,96716],{"__ignoreMap":674},[679,96587,96588,96590],{"class":681,"line":682},[679,96589,4116],{"class":693},[679,96591,6212],{"class":685},[679,96593,96594,96596,96598,96600],{"class":681,"line":790},[679,96595,6073],{"class":685},[679,96597,4512],{"class":685},[679,96599,96530],{"class":880},[679,96601,884],{"class":693},[679,96603,96604,96606],{"class":681,"line":892},[679,96605,6872],{"class":693},[679,96607,16929],{"class":685},[679,96609,96610,96612,96614,96616],{"class":681,"line":901},[679,96611,6089],{"class":685},[679,96613,23593],{"class":693},[679,96615,23596],{"class":880},[679,96617,2667],{"class":693},[679,96619,96620,96622,96624,96626,96628,96630],{"class":681,"line":909},[679,96621,9444],{"class":685},[679,96623,2054],{"class":685},[679,96625,23607],{"class":880},[679,96627,10541],{"class":693},[679,96629,23612],{"class":880},[679,96631,9317],{"class":693},[679,96633,96634],{"class":681,"line":918},[679,96635,985],{"class":693},[679,96637,96638],{"class":681,"line":935},[679,96639,889],{"emptyLinePlaceholder":797},[679,96641,96642,96644],{"class":681,"line":944},[679,96643,6872],{"class":693},[679,96645,16929],{"class":685},[679,96647,96648,96650,96653,96656],{"class":681,"line":959},[679,96649,6089],{"class":685},[679,96651,96652],{"class":693}," RunService ",[679,96654,96655],{"class":880},"runService",[679,96657,2667],{"class":693},[679,96659,96660,96662,96664,96666,96668,96670],{"class":681,"line":964},[679,96661,9444],{"class":685},[679,96663,2054],{"class":685},[679,96665,96391],{"class":880},[679,96667,745],{"class":693},[679,96669,23596],{"class":880},[679,96671,9431],{"class":693},[679,96673,96674],{"class":681,"line":977},[679,96675,985],{"class":693},[679,96677,96678],{"class":681,"line":982},[679,96679,889],{"emptyLinePlaceholder":797},[679,96681,96682,96684],{"class":681,"line":988},[679,96683,6872],{"class":693},[679,96685,16929],{"class":685},[679,96687,96688,96690,96693,96696],{"class":681,"line":993},[679,96689,6089],{"class":685},[679,96691,96692],{"class":693}," TrackService ",[679,96694,96695],{"class":880},"trackService",[679,96697,2667],{"class":693},[679,96699,96700,96702,96704,96706,96708,96710],{"class":681,"line":2129},[679,96701,9444],{"class":685},[679,96703,2054],{"class":685},[679,96705,96448],{"class":880},[679,96707,745],{"class":693},[679,96709,23596],{"class":880},[679,96711,9431],{"class":693},[679,96713,96714],{"class":681,"line":2140},[679,96715,985],{"class":693},[679,96717,96718],{"class":681,"line":2145},[679,96719,996],{"class":693},[651,96721,96722],{},"When we run our application, we have no errors, and everything works fine. However, Spring creates a proxy class using the CGlib code generation library. The reason behind this is Spring doesn't want to call the method that returns a new RestTemplate every time a bean is created. It only wants one instance of RestTemplate. When we create a new service class, it retrieves the already-created instance of RestTemplate using the proxy class.",[651,96724,96725],{},[660,96726],{"alt":70954,"src":96727},"/images/blog/2022/12/19/proxy.png",[4542,96729,96731],{"id":96730},"fixing-the-problem","Fixing the Problem",[651,96733,96734,96735,88808],{},"Using a proxy class most of the time isn’t an issue, but we want to get rid of it because it's using the CGlib library, which won’t work for native executables. I’m also a big fan of avoiding proxies in any situation I can so even If you’re not building a native executable I find this to be a best practice. We can disable this by setting a property proxy bean methods equal to false on the ",[676,96736,6139],{},[669,96738,96740],{"className":4107,"code":96739,"language":4109,"meta":674,"style":674},"@Configuration(proxyBeanMethods = false)\n",[676,96741,96742],{"__ignoreMap":674},[679,96743,96744,96746,96748,96750,96753,96755,96757],{"class":681,"line":682},[679,96745,4116],{"class":693},[679,96747,6478],{"class":685},[679,96749,745],{"class":693},[679,96751,96752],{"class":931},"proxyBeanMethods",[679,96754,6883],{"class":685},[679,96756,14559],{"class":931},[679,96758,1339],{"class":693},[651,96760,96761],{},"Now we have a new problem. If we set proxyBeanMethods to false, every time we create a service, it calls the method that returns a new RestTemplate instance. This eliminates the benefit we previously had of only creating one instance of RestTemplate.",[651,96763,96764],{},"The solution to this problem is to make RestTemplate an argument to our service classes. Spring already has an instance of RestTemplate since we've created it using the MyAppConfig class. Therefore, we can just pass an instance of RestTemplate to the constructor of our service classes.",[669,96766,96767],{"className":4107,"code":96380,"language":4109,"meta":674,"style":674},[676,96768,96769,96779,96787,96799,96809,96813,96817,96821,96831,96839,96851,96861,96865],{"__ignoreMap":674},[679,96770,96771,96773,96775,96777],{"class":681,"line":682},[679,96772,6073],{"class":685},[679,96774,4512],{"class":685},[679,96776,96391],{"class":880},[679,96778,884],{"class":693},[679,96780,96781,96783,96785],{"class":681,"line":790},[679,96782,9232],{"class":685},[679,96784,12768],{"class":685},[679,96786,96402],{"class":693},[679,96788,96789,96791,96793,96795,96797],{"class":681,"line":892},[679,96790,6089],{"class":685},[679,96792,96391],{"class":880},[679,96794,96411],{"class":693},[679,96796,23596],{"class":2099},[679,96798,4390],{"class":693},[679,96800,96801,96803,96805,96807],{"class":681,"line":901},[679,96802,7862],{"class":931},[679,96804,96422],{"class":693},[679,96806,686],{"class":685},[679,96808,96427],{"class":693},[679,96810,96811],{"class":681,"line":909},[679,96812,985],{"class":693},[679,96814,96815],{"class":681,"line":918},[679,96816,996],{"class":693},[679,96818,96819],{"class":681,"line":935},[679,96820,889],{"emptyLinePlaceholder":797},[679,96822,96823,96825,96827,96829],{"class":681,"line":944},[679,96824,6073],{"class":685},[679,96826,4512],{"class":685},[679,96828,96448],{"class":880},[679,96830,884],{"class":693},[679,96832,96833,96835,96837],{"class":681,"line":959},[679,96834,9232],{"class":685},[679,96836,12768],{"class":685},[679,96838,96402],{"class":693},[679,96840,96841,96843,96845,96847,96849],{"class":681,"line":964},[679,96842,6089],{"class":685},[679,96844,96448],{"class":880},[679,96846,96411],{"class":693},[679,96848,23596],{"class":2099},[679,96850,4390],{"class":693},[679,96852,96853,96855,96857,96859],{"class":681,"line":977},[679,96854,7862],{"class":931},[679,96856,96422],{"class":693},[679,96858,686],{"class":685},[679,96860,96427],{"class":693},[679,96862,96863],{"class":681,"line":982},[679,96864,985],{"class":693},[679,96866,96867],{"class":681,"line":988},[679,96868,996],{"class":693},[651,96870,96871],{},"Lastly, we need to modify our configuration class to reflect this:",[669,96873,96875],{"className":4107,"code":96874,"language":4109,"meta":674,"style":674},"@Bean\npublic RunService runService(RestTemplate restTemplate) {\n return new RunService(restTemplate);\n}\n\n@Bean\npublic TrackService trackService(RestTemplate restTemplate) {\n return new TrackService(restTemplate);\n}31\n",[676,96876,96877,96883,96894,96905,96909,96913,96919,96929,96939],{"__ignoreMap":674},[679,96878,96879,96881],{"class":681,"line":682},[679,96880,4116],{"class":693},[679,96882,16929],{"class":685},[679,96884,96885,96887,96889,96891],{"class":681,"line":790},[679,96886,6073],{"class":685},[679,96888,96652],{"class":693},[679,96890,96655],{"class":880},[679,96892,96893],{"class":693},"(RestTemplate restTemplate) {\n",[679,96895,96896,96898,96900,96902],{"class":681,"line":892},[679,96897,21478],{"class":685},[679,96899,2054],{"class":685},[679,96901,96391],{"class":880},[679,96903,96904],{"class":693},"(restTemplate);\n",[679,96906,96907],{"class":681,"line":901},[679,96908,996],{"class":693},[679,96910,96911],{"class":681,"line":909},[679,96912,889],{"emptyLinePlaceholder":797},[679,96914,96915,96917],{"class":681,"line":918},[679,96916,4116],{"class":693},[679,96918,16929],{"class":685},[679,96920,96921,96923,96925,96927],{"class":681,"line":935},[679,96922,6073],{"class":685},[679,96924,96692],{"class":693},[679,96926,96695],{"class":880},[679,96928,96893],{"class":693},[679,96930,96931,96933,96935,96937],{"class":681,"line":944},[679,96932,21478],{"class":685},[679,96934,2054],{"class":685},[679,96936,96448],{"class":880},[679,96938,96904],{"class":693},[679,96940,96941,96943],{"class":681,"line":959},[679,96942,56311],{"class":693},[679,96944,96945],{"class":931},"31\n",[651,96947,96948],{},"Now when we run our application, the CGlib proxy class is not used, and we only create one instance of RestTemplate.",[4542,96950,9042],{"id":9041},[651,96952,96953],{},"By setting the \"proxy Bean methods\" property to \"false,\" we can eliminate the proxy class. This is something you must do if you’re creating a native executable but I also find it a best practice if it is something you can do. Removing overhead such as the proxy class can help improve your application's performance. I hope this blog post has helped you gain a better understanding of Spring's configuration classes.",[786,96955,96956],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":96958},[96959,96960,96961,96962,96963],{"id":96351,"depth":790,"text":96352},{"id":96373,"depth":790,"text":96374},{"id":96502,"depth":790,"text":96503},{"id":96730,"depth":790,"text":96731},{"id":9041,"depth":790,"text":9042},{"slug":96965,"date":96966,"published":797,"author":798,"tags":96967,"cover":96968,"video":96969,"keywords":96970},"spring-proxy-bean-methods","2022-12-19T10:00:00.000Z",[7077],"./proxy-bean-methods.png","https://www.youtube.com/embed/VoK6-OiSPu4","Spring Boot, Spring Boot Configuration, proxy bean methods",{"title":360,"description":849},"blog/2022/12/19/spring-proxy-bean-methods","6peNrCXdnWGuq18cTjYiuXlqgve4W5wfTufRO_nlCsI",{"id":96975,"title":357,"body":96976,"description":97250,"extension":793,"meta":97251,"navigation":797,"path":358,"seo":97257,"stem":97258,"__hash__":97259},"content/blog/2022/12/29/2022-reflections.md",{"type":648,"value":96977,"toc":97236},[96978,96981,96983,96992,96995,97004,97007,97051,97057,97059,97062,97065,97068,97071,97108,97112,97121,97125,97128,97131,97134,97137,97141,97144,97147,97150,97154,97169,97172,97175,97179,97199,97203,97206,97210,97224,97228,97231,97233],[651,96979,96980],{},"It’s hard to believe that another year has passed but I’m guessing this is something we say at the end of every year. I like to sit down over Christmas break and spend some time reflecting because I believe it's a good reminder of the good and the bad as I began to set some goals for next year.",[4542,96982,82850],{"id":83080},[651,96984,96985,96986,96991],{},"This was one of my best years professionally and it began in January when ",[812,96987,96990],{"href":96988,"rel":96989},"https://www.danvega.dev/blog/2022/01/24/im-joining-vmware/",[816],"I started my dream job"," as a Spring Developer Advocate at VMware. I joined an amazing company and a team that I absolutely love working with every single day. I often joke that I was an advocate for Spring long before I joined VMware but this has literally been a dream come true.",[651,96993,96994],{},"I remember that feeling on Sunday evening of dreading the new work week and the stress that would follow. Now I sit down on Sunday with a smile on my face and plan out what I get to work on during the upcoming week. I absolutely love creating content and helping developers solve their problems.",[651,96996,96997,96998,97003],{},"When I joined DaShaun and I started talking about creating a place for Spring Developers to ask their questions. After some brainstorming sessions, we created Spring Office Hours, and honestly, it’s been one of the highlights of my year. This is a weekly live stream where we discuss what’s new in the Spring community and answer any questions you might have. If you want to learn more about the show head over to the ",[812,96999,97002],{"href":97000,"rel":97001},"https://tanzu.vmware.com/developer/tv/spring-office-hours/",[816],"Tanzu Developer Center"," and watch previous episodes.",[651,97005,97006],{},"I spent a lot of this year learning as much as I could and teaching what I learned back to the community. My plan is to continue that in 2023 because the Spring ecosystem is vast and there is so much more I hope to learn. It was an amazing year of traveling and giving presentations on topics I am passionate about and it’s something I hope to continue in the new year. Here is a short list of some of the presentations I gave in 2022:",[5316,97008,97009,97016,97023,97030,97037,97044],{},[5332,97010,97011],{},[812,97012,97015],{"href":97013,"rel":97014},"https://tanzu.vmware.com/developer/springone-tour/2022/toronto/",[816],"SpringOne Tour Toronto",[5332,97017,97018],{},[812,97019,97022],{"href":97020,"rel":97021},"https://us.vuejs.org/",[816],"VueConf US",[5332,97024,97025],{},[812,97026,97029],{"href":97027,"rel":97028},"https://tanzu.vmware.com/developer/springone-tour/2022/seattle/",[816],"SpringOne Tour Seattle",[5332,97031,97032],{},[812,97033,97036],{"href":97034,"rel":97035},"https://www.kcdc.info/",[816],"Kansas City Developer Conference (KCDC)",[5332,97038,97039],{},[812,97040,97043],{"href":97041,"rel":97042},"https://www.codeonthebeach.com/",[816],"Code On The Beach",[5332,97045,97046],{},[812,97047,97050],{"href":97048,"rel":97049},"https://tanzu.vmware.com/developer/springone-tour/2022/new-york/",[816],"SpringOne Tour NewYork",[651,97052,97053],{},[660,97054],{"alt":97055,"src":97056},"dan-nyc-talk.jpeg","/images/blog/2022/12/29/dan-nyc-talk.jpeg",[4542,97058,15432],{"id":61224},[651,97060,97061],{},"I ended up crossing a huge milestone towards the end of the year and passing 20,000 subscribers on YouTube. I think I heard somewhere that only 1% of YouTubers hit this mark and that is just amazing company to be in 🤩 It’s been an amazing journey getting to this point and I have learned so much since I uploaded my first video in October of 2013.",[651,97063,97064],{},"I think what I am most excited about is that I have barely scratched the surface of what I could do with this channel. I know I have a lot of room to grow and I have big plans for my channel in the new year. Here is a video I published when I hit 20k thanking my subscribers and looking back at some of my older videos.",[5988,97066],{"id":97067},"hsMKOirkLZU",[651,97069,97070],{},"I ended up posting 71 videos this year which exceeded my lofty goal of 1x per week for a total of 52 videos. I also ended up live streaming 24 times which was the exact number I set as a goal at the beginning of the year. I'm proud of the content I published on my channel and across YouTube and here are a few of my most popular videos:",[5316,97072,97073,97080,97087,97094,97101],{},[5332,97074,97075],{},[812,97076,97079],{"href":97077,"rel":97078},"https://www.youtube.com/watch?v=4L4LEnawcO8",[816],"What’s new in Spring Boot 2.6",[5332,97081,97082],{},[812,97083,97086],{"href":97084,"rel":97085},"https://www.youtube.com/watch?v=q_RLfOB7axQ",[816],"Building REST APIs in Spring Boot",[5332,97088,97089],{},[812,97090,97093],{"href":97091,"rel":97092},"https://studio.youtube.com/video/KYNR5js2cXE/edit",[816],"Spring Security JWT: How to secure your Spring Boot REST APIs with JSON Web Tokens",[5332,97095,97096],{},[812,97097,97100],{"href":97098,"rel":97099},"https://studio.youtube.com/video/s4X4SJv2RrU/edit",[816],"Spring Security without the WebSecurityConfigurerAdapter",[5332,97102,97103],{},[812,97104,97107],{"href":97105,"rel":97106},"https://studio.youtube.com/video/TR254zh-f3c/edit",[816],"Spring Boot 3 - What’s new in Spring Framework 6 and Spring Boot 3.0",[4542,97109,97111],{"id":97110},"_7-questions-to-successfully-reflect-on-2022","7 QUESTIONS TO SUCCESSFULLY REFLECT ON 2022",[651,97113,97114,97115,97120],{},"I’m a huge fan of Jay Shetty and his podcast. On a ",[812,97116,97119],{"href":97117,"rel":97118},"https://jayshetty.me/podcast/7-powerful-questions-to-successfully-reflect-on-2022-and-end-the-year-with-confidence/",[816],"recent episode",", he proposed answering the following 7 questions as a way to reflect on the past year. I thought this was a great idea to learn from it and take what I have learned into the new year.",[5909,97122,97124],{"id":97123},"what-is-the-challenge-that-youve-overcome-this-year","What is the challenge that you’ve overcome this year?",[651,97126,97127],{},"I have been struggling with my health and my battle with type 2 diabetes. Towards the end of last year, my numbers were not good and they didn’t get better through the first half of this year. I ended up having to go on a 3rd medicine and that really started to scare me. Suddenly this wasn’t something I was managing and could begin to have pretty serious consequences.",[651,97129,97130],{},"After asking myself some hard questions and confronting some truths I realized that a big part of my problem was alcohol. In the middle of August, I decided that I was going to stop drinking. I didn’t know for how long and If I am being honest I still don’t know the answer to that question.",[651,97132,97133],{},"What I do know is that my A1C went from 11.1 all the way down to 5.6 in November and I am getting healthier every single day. I eat better, exercise regularly and I am much more focused on a daily basis. In the month of December, I am hoping to cross 100 miles of running indoors on a treadmill which is an all-time best for me by a long shot.",[651,97135,97136],{},"While I am thankful for this decision and my progress I know it's just the beginning. I want to be off of all medicine one day and manage this through good lifestyle choices. I don’t know if that’s possible but I am working towards that goal every single day.",[5909,97138,97140],{"id":97139},"whats-a-surprise-you-dealt-with","What’s a surprise you dealt with?",[651,97142,97143],{},"A big surprise I had to deal with this year was getting Covid for the first time. We were very careful for a long time because when the pandemic hit my 2nd daughter was born. We ended up going on a family trip with our friends and their 2 kids to Hilton Head, SC.",[651,97145,97146],{},"On our way home I started feeling weak and honestly didn’t think much of it. The next day I still didn’t feel great and decided to take a test because I was supposed to travel the next day for my first in-person event in Chicago. What’s even crazier is that all of these people I spent every single hour with were just fine.",[651,97148,97149],{},"I was really bummed out that I had to miss my first SpringOne Tour event but I told myself it was just the first of many. I’m not sure what next year will bring but I hope to travel and talk code in a city near you!",[5909,97151,97153],{"id":97152},"what-is-something-you-bought-this-year","What is something you bought this year?",[651,97155,97156,97157,97162,97163,97168],{},"I bought a ",[812,97158,97161],{"href":97159,"rel":97160},"https://amzn.to/3Vq4u2M",[816],"new treadmill"," last January and it was one of the best purchases I have ever made. I’m normally not a big fan of running inside but this one changed all of that for me. This treadmill came with an LCD and you could sign up for a service called ",[812,97164,97167],{"href":97165,"rel":97166},"https://www.ifit.com/",[816],"iFit"," which has guided workouts on it.",[651,97170,97171],{},"I really enjoyed the 5k & 10k series that took me all over the world. There is something very subversive about these runs and being transported to a remote island on the other side of the planet. I also enjoy that they are all different kinds of workouts like tempo, interval, and long runs. The trainer also changes the speed for you and the incline automatically aligns with the hills they are running on.",[651,97173,97174],{},"This is a reminder to invest in anything that makes you happy and for me, this treadmill has done a lot for me this year.",[5909,97176,97178],{"id":97177},"whats-the-best-bookpodcast-you-readlistened-to","What’s the best book/podcast you read/listened to?",[651,97180,97181,97182,97187,97188,97193,97194,664],{},"I discovered ",[812,97183,97186],{"href":97184,"rel":97185},"https://www.calnewport.com/",[816],"Cal Newport"," this year and for that reason, he is going to be both the best book and podcast I listened to this year. I really enjoyed his book ",[812,97189,97192],{"href":97190,"rel":97191},"https://amzn.to/3Cadi60",[816],"Deep Work"," which I wrote a review on in a ",[812,97195,97198],{"href":97196,"rel":97197},"https://www.danvega.dev/newsletter/deep-work/",[816],"previous newsletter",[5909,97200,97202],{"id":97201},"what-are-your-blind-spots-for-next-year","What are your blind spots for next year?",[651,97204,97205],{},"VMWare was acquired by Broadcom this year and this does leave a little bit of uncertainty for me. I am not sure what will happen because of this acquisition but a lot of that is out of my hands. I love the company I work for and the team I get to work with every single day. It’s because of this I am going to keep a positive attitude and keep on doing what I love to do until I am told otherwise.",[5909,97207,97209],{"id":97208},"what-made-you-the-happiest-this-year","What made you the happiest this year?",[5316,97211,97212,97215,97218,97221],{},[5332,97213,97214],{},"My family and I got to take some really amazing trips this year. It was great to see so many beaches and the highlight was going to Disney World. My girls love to travel and I love seeing them happy.",[5332,97216,97217],{},"Seeing my girls grow up and realizing just how freaking lucky I am to be their dad #girldad",[5332,97219,97220],{},"My work makes me happy and the people I get to help every day puts a big smile on my face.",[5332,97222,97223],{},"Running has improved my physical and mental health and makes me so happy.",[5909,97225,97227],{"id":97226},"whos-the-person-you-couldnt-have-gotten-through-this-year-without","Who’s the person you couldn’t have gotten through this year without?",[651,97229,97230],{},"This is without a doubt, my wife. She is my person, my best friend, my love, and my rock. I always dreamed about the life I have now and I owe a lot of that to her ❤️",[4542,97232,9042],{"id":9041},[651,97234,97235],{},"I had a lot of fun sitting down` and reflecting on the past year. Like every year it had its up and downs but I choose to focus on the positives and learn from my mistakes as I move into the new year. Be sure to stay on the lookout as I post my goals for 2023.",{"title":674,"searchDepth":790,"depth":790,"links":97237},[97238,97239,97240,97249],{"id":83080,"depth":790,"text":82850},{"id":61224,"depth":790,"text":15432},{"id":97110,"depth":790,"text":97111,"children":97241},[97242,97243,97244,97245,97246,97247,97248],{"id":97123,"depth":892,"text":97124},{"id":97139,"depth":892,"text":97140},{"id":97152,"depth":892,"text":97153},{"id":97177,"depth":892,"text":97178},{"id":97201,"depth":892,"text":97202},{"id":97208,"depth":892,"text":97209},{"id":97226,"depth":892,"text":97227},{"id":9041,"depth":790,"text":9042},"In this article I am going to spend some time reflecting because I believe it's a good reminder of the good and the bad as I began to set some goals for next year.",{"slug":97252,"date":97253,"published":797,"author":798,"tags":97254,"cover":97255,"keywords":97256},"2022-reflections","2022-12-29T16:00:00.000Z",[11968],"./jose-m-reyes-0GBxtiFvzXE-unsplash.jpg","2022 reflections",{"title":357,"description":97250},"blog/2022/12/29/2022-reflections","KX62L1THTJMj0dtDeZbPEPChuvXAjqbtOpupmQ0-Qik",{"id":97261,"title":354,"body":97262,"description":97541,"extension":793,"meta":97542,"navigation":797,"path":355,"seo":97548,"stem":97549,"__hash__":97550},"content/blog/2023/01/01/happy-new-year-2023.md",{"type":648,"value":97263,"toc":97527},[97264,97279,97290,97294,97302,97305,97308,97322,97324,97327,97330,97336,97339,97342,97346,97353,97359,97362,97365,97368,97370,97373,97379,97383,97391,97395,97398,97401,97415,97418,97421,97425,97432,97435,97438,97442,97445,97448,97450,97453,97460,97463,97467,97470,97473,97510,97513,97519,97522,97524],[651,97265,97266,97267,97272,97273,97278],{},"It’s that time of year again. If you missed ",[812,97268,97271],{"href":97269,"rel":97270},"https://www.danvega.dev/blog/2022/12/29/2022-reflections/",[816],"my last post"," I spent some time reflecting on 2022 before setting some goals for the new year. I mentioned this in ",[812,97274,97277],{"href":97275,"rel":97276},"https://www.danvega.dev/blog/2022/01/01/happy-new-year-2022/",[816],"last year's new year's post"," but I have 2 small kids at home so I am hesitant to attach the word goal but I will give it a shot.",[651,97280,97281,97282,97289],{},"I have read Michael Hyatt’s book ",[2939,97283,97284],{},[812,97285,97288],{"href":97286,"rel":97287},"https://amzn.to/3VvC1sd",[816],"Your Best Year"," a couple of times now, so I know that by not creating SMART goals, I am setting myself up for failure. With that, I will list some things I want to focus on in the new year and try to revisit the idea of setting a more measurable goal later. Even as I am writing this it reminds me a lot of when I say I will come back and write those tests later 🤦♂️🤣",[4542,97291,97293],{"id":97292},"professional-goals","Professional Goals",[651,97295,97296,97297,97301],{},"I will be hitting my 1 year anniversary as a ",[812,97298,82850],{"href":97299,"rel":97300},"https://tanzu.vmware.com/developer/team/dan-vega/",[816]," in January and couldn’t be more excited about the year ahead. While I feel like I have some breadth across the Spring Ecosystem of projects there is still a lot I have to learn in terms of depth.",[651,97303,97304],{},"That is why a big focus this year for me is to keep doing what I did last year. I want to learn as much as I can and take what I have learned and share that with the community. I will do that by listening to our community on places like YouTube, Twitter, Facebook, Stack Overflow, and Spring Office Hours. Whatever questions are facing you the most are the subjects I will try and dive deeper into.",[651,97306,97307],{},"I love writing articles, creating videos, and live streaming but nothing quite beats an in-person event. This is why I spent some time towards the end of last year identifying which events I could submit proposals to. My goal here is to speak at 8-10 different in-person conferences and meetups. I may adjust this goal when I learn more about our plans at work but for now, here are the appearances I have scheduled:",[5316,97309,97310,97313,97316,97319],{},[5332,97311,97312],{},"CodeMash",[5332,97314,97315],{},"Cleveland Java User Group",[5332,97317,97318],{},"Spring Essentials Tour Stop (San Francisco)",[5332,97320,97321],{},"DevNexus",[4542,97323,15432],{"id":61224},[651,97325,97326],{},"I had a great year on YouTube last year and it gave me a glimpse of what could be. While I am happy with how consistent I was at times and the quality of content I produced I believe that I have a lot to learn and room to grow.",[651,97328,97329],{},"The first item I want to focus on is just being consistent and coming up with a good plan. I think consistency is a big part of why successful YouTubers are successful. I also have so many different types of videos that I work on from shorts to live streams to full-length tutorials. I think coming up with a consistent schedule of when I post what type of content is going to help me. If I can just post 1 of each per week that would be 144 uploads for the year and I think that would be a great goal.",[651,97331,97332],{},[660,97333],{"alt":97334,"src":97335},"christian-wiediger-NmGzVG5Wsg8-unsplash.jpg","/images/blog/2023/01/01/christian-wiediger-NmGzVG5Wsg8-unsplash.jpg",[651,97337,97338],{},"While I am on that subject a focus of mine last year was repurposing content and I need to get better at that on YouTube. If I prepare content to work on during a live stream I should use that across multiple shorts as well as full-length tutorials. It takes time to prepare content and If I am creating new content for each type of video I am not going to be very productive.",[651,97340,97341],{},"I know my way around video editing software but I wouldn’t call myself much of an editor. I need to get better at editing and this involves cuts, transitions, motion graphics, and more. I understand that the most important part of the video is the content but if you can tell a great story with visuals to keep the audience engaged it will only lead to great things. This is all in the name of creating better-quality videos.",[4542,97343,97345],{"id":97344},"spring-office-hours","Spring Office Hours",[651,97347,97348,97349,97352],{},"Last year DaShaun and I started a new show on Tanzu TV called ",[812,97350,97345],{"href":97000,"rel":97351},[816],". This was our chance to talk about what was new in the Spring community, show off any cool things we were working on and answer questions from the community.",[651,97354,97355],{},[660,97356],{"alt":97357,"src":97358},"spring-office-hours.png","/images/blog/2023/01/01/spring-office-hours.png",[651,97360,97361],{},"We did some shows without really telling anyone before we officially launched the show in May. During the summer we were traveling a lot for the SpringOne Tour and ended up not being as consistent as we would have liked. Towards the end of the year, we found a new home on Tuesdays at 3:30 EDT to hopefully include our friends all over and I think that really worked out well.",[651,97363,97364],{},"This is something both of us really look forward to each week and that isn’t going to change in the new year. I think some things we do want to improve on are scheduling shows in advance as well as automating that along with other tasks like show notes.",[651,97366,97367],{},"Finally, we do have a big idea for this show that we are working on but I don’t want to let the cat out of the bag just yet. We want to do our research on this particular thing before we decide to move forward with it. As soon as we can figure it out though we will be sure to let you know!",[4542,97369,53863],{"id":53862},[651,97371,97372],{},"My personal website is one of the things that I am most proud of. It has certainly evolved over the years but its purpose never has. This is a place where you can learn more about me as well as get access to all of the content that I produce.",[651,97374,97375],{},[660,97376],{"alt":97377,"src":97378},"danega_dev_homepage.png","/images/blog/2023/01/01/danega_dev_homepage.png",[5909,97380,97382],{"id":97381},"moving-to-nuxt-3","Moving to Nuxt 3",[651,97384,97385,97386,97390],{},"I mentioned this in ",[812,97387,97389],{"href":97275,"rel":97388},[816],"last year's new year’s goals"," but I would like to move my website over to Nuxt 3. This never panned out last year because Nuxt 3 didn’t get released until the end of the year. I think one of the big questions I still have to answer is how I am going to convert all of my blog posts over to the format that Nuxt 3 is looking for. I currently have all of the images in the same folder as the post and Nuxt would like them in a public folder. If you are doing the same and found a solution for this please reach out.",[5909,97392,97394],{"id":97393},"writing","Writing",[651,97396,97397],{},"The biggest thing I need to figure out what to do with my website is where I am going to focus my writing efforts. Last year I wrote a lousy 6 blog posts while sending out 32 editions of my newsletter. I enjoy writing and don’t want to stop doing it but at the same time, I have to be smart with my time.",[651,97399,97400],{},"If I am being honest when it comes to the blog I want the ability to post the occasional random blog post. What I really want on the blog are posts about the tutorials I am working on. The time I spend working on these tutorials though is for my YouTube channel. The question is how I can create a quick blog post that summarizes the YouTube video and links to it so I can get some cross-promotion between the blog and Youtube.",[651,97402,97403,97408,97409,97414],{},[812,97404,97407],{"href":97405,"rel":97406},"https://twitter.com/chris__sev",[816],"Chris Sev"," has a solution to this problem called ",[812,97410,97413],{"href":97411,"rel":97412},"https://videotapit.com/",[816],"VideoTap",". It will transcribe your video and create a blog post it. My only concern here is that my videos don’t really transcribe to a blog post all that well. It doesn’t feel like something I would write but I also haven’t spent a ton of time testing out this wonderful tool. This is something I need to spend more time playing in the new year.",[651,97416,97417],{},"It’s also been an up-and-down year with the newsletter. I haven’t really been able to grow my subscriber base and in fact, I lose around 5-10 subscribers every time I send out a newsletter. I also haven’t put a lot of effort into driving newsletter subscribers. I know a lot of people create landing pages with something to entice someone into subscribing and I just haven’t done that.",[651,97419,97420],{},"I love to write but I also don’t want to write without a specific plan. I need to figure this out 🙇♂️",[4542,97422,97424],{"id":97423},"reading","Reading",[651,97426,97427,97428,664],{},"I went on a trip this year to Ocean Isle NC for a friend's wedding and it was an amazing getaway. It was great to see my good friend so happy and the wedding was very special. On top of that, I get to spend some time with friends, run alongside the ocean, and even managed to read 2 books on that trip. If you’re interested in reading more about that I talked it about in ",[812,97429,97431],{"href":97196,"rel":97430},[816],"my newsletter",[651,97433,97434],{},"I was reminded how much I love reading and I need to make it more of a priority. I really do love learning new things but I also realized that it doesn’t need to just be related to work. I have gotten into history lately and have some great books lined up about the history of mankind.",[651,97436,97437],{},"I don’t have a goal for the number of books I want to read next year, I just need to make more time for it. If I can get the kids to sleep (on their own) at a decent time that is probably my best chance to find a regular reading cadence.",[4542,97439,97441],{"id":97440},"health-fitness","Health & Fitness",[651,97443,97444],{},"I talked a little bit about this in my 2022 reflections but Health and Fitness must take priority over most things throughout this year. I am going to continue not drinking for the foreseeable future. My goal is to get off all medicine if possible so anything I can do that will contribute to that goal will be my focus.",[651,97446,97447],{},"I really enjoy running and spent the last part of this year running a lot. I want to run a half marathon this year and would like to sign up for the Cleveland Half in May. I also want to put in a lot of miles this year so I am setting a goal of running 600 miles for the year. This will of course rely on me not getting injured so I am going to strength train 2x per week on top of running 4x week.",[4542,97449,79577],{"id":82783},[651,97451,97452],{},"I listed this in your last year's goals but this is going to continue to be a top goal of mine until it happens. Yes, I did say until it happens because I believe if you want something in life you need to believe in yourself and work hard at it.",[651,97454,40060,97455,97459],{},[812,97456,79577],{"href":97457,"rel":97458},"https://dev.java/community/jcs/",[816]," Program consists of a unique group of skilled Java technologists and community leaders sponsored by Oracle. These are leaders, influencers, and enablers who help grow the size of the Java community.",[651,97461,97462],{},"While I have been a fan of Java and Spring I spend most of my time talking about Spring. I am going to focus more this year on general Java content. If there are questions you have about Java and would like me to create a tutorial or explanation about please feel free to reach out.",[4542,97464,97466],{"id":97465},"artificial-intelligence-ai","Artificial Intelligence (AI)",[651,97468,97469],{},"There are some really amazing tools in the AI / Machine Learning space and I think we are only going to see more in the new year. When ChatGPT exploded towards the end of last year I received a lot of questions from developers wondering if this was going to take their job.",[651,97471,97472],{},"I don’t have a crystal ball and can’t see into the future but what I would say is you’re safe for now. With any industry, I think it would be a mistake to ignore AI. I think you can use AI as another tool in your tool belt. I’m excited about the future of AI and I will be particularly paying attention to the following services:",[5316,97474,97475,97482,97489,97496,97503],{},[5332,97476,97477],{},[812,97478,97481],{"href":97479,"rel":97480},"https://chat.openai.com/chat",[816],"ChatGPT",[5332,97483,97484],{},[812,97485,97488],{"href":97486,"rel":97487},"https://openai.com/dall-e-2/",[816],"DALLE",[5332,97490,97491],{},[812,97492,97495],{"href":97493,"rel":97494},"https://www.midjourney.com/home/?callbackUrl=%2Fapp%2F",[816],"Midjourney",[5332,97497,97498],{},[812,97499,97502],{"href":97500,"rel":97501},"https://github.com/features/copilot",[816],"Github Copilot",[5332,97504,97505],{},[812,97506,97509],{"href":97507,"rel":97508},"https://quillbot.com/",[816],"QuillBot",[651,97511,97512],{},"I spent some time over the break playing around with Midjourney and like ChatGPT the trick is understanding what to ask the service for. I was looking to create a new image for a landing page related to Spring Boot. After playing around with different prompts I came up with the following image which I really love.",[651,97514,97515],{},[660,97516],{"alt":97517,"src":97518},"Spring Boot AI","/images/blog/2023/01/01/spring_boot_ai.png",[651,97520,97521],{},"Going back to my writing blog posts about my YouTube videos. There has to be a solution here somewhere, just need to figure it out 😉",[4542,97523,9042],{"id":9041},[651,97525,97526],{},"Another year has come and gone and after doing some reflecting I was able to sit down and put together this list of priorities for 2023. I look forward to revisiting this list at the end of the year to see what I actually followed through on.",{"title":674,"searchDepth":790,"depth":790,"links":97528},[97529,97530,97531,97532,97536,97537,97538,97539,97540],{"id":97292,"depth":790,"text":97293},{"id":61224,"depth":790,"text":15432},{"id":97344,"depth":790,"text":97345},{"id":53862,"depth":790,"text":53863,"children":97533},[97534,97535],{"id":97381,"depth":892,"text":97382},{"id":97393,"depth":892,"text":97394},{"id":97423,"depth":790,"text":97424},{"id":97440,"depth":790,"text":97441},{"id":82783,"depth":790,"text":79577},{"id":97465,"depth":790,"text":97466},{"id":9041,"depth":790,"text":9042},"Happy New Year! I want to take a few minutes and talk about a few of my priorities as we head into the new year.",{"slug":97543,"date":97544,"published":797,"author":798,"tags":97545,"cover":97546,"keywords":97547},"happy-new-year-2023","2023-01-01T10:00:00.000Z",[11968,39139],"kostiantyn-li-pTfOKdj8whk-unsplash.jpg","Happy New Year, 2023",{"title":354,"description":97541},"blog/2023/01/01/happy-new-year-2023","Ji7RQVep4-xmSdjqnggJJoPqZLahARDU7l8VgYCvMQM",{"id":97552,"title":351,"body":97553,"description":98067,"extension":793,"meta":98068,"navigation":797,"path":352,"seo":98075,"stem":98076,"__hash__":98077},"content/blog/2023/01/27/jakarta-ee-10-uuid.md",{"type":648,"value":97554,"toc":98058},[97555,97558,97562,97565,97576,97579,97581,97588,97591,97596,97600,97616,97627,97746,97749,97753,97760,97798,97800,97806,97809,98006,98009,98044,98050,98052,98055],[651,97556,97557],{},"In this blog post, we will take a trip down memory lane and discuss the evolution of Jakarta EE (formerly known as Java EE) and its impact on enterprise development in Java. We will explore the role of the Eclipse Foundation in furthering the development of Jakarta EE and how it has led to the introduction of new APIs, fixes, and features. This article will also delve into one specific change in Jakarta EE 10, the ability to use a UUID as a primary key, and how it can be easily implemented in a Spring Boot 3 project using Spring Data JPA.",[4542,97559,97561],{"id":97560},"what-is-jakarta-ee","What is Jakarta EE",[651,97563,97564],{},"Jakarta EE is an open-source platform for developing enterprise-level applications in Java, maintained and managed by the Eclipse Foundation. It provides secure, scalable, and extensible tools for creating, deploying, and managing applications. Formerly known as Java EE (Enterprise Edition), it has been available since 1999. In recent years, Jakarta EE has been updated with new APIs, bug fixes, and features, giving developers more options for creating enterprise applications.",[651,97566,97567,97568,97571,97572,97575],{},"The real question is: why are we discussing this now, and why should Spring developers care? This is because Spring Framework 6 now has a baseline on Jakarta EE 9 and 10. The two versions are necessary because we will use 10 wherever possible, and fall back to 9 when we can't. Jakarta EE 9 was the version where all the namespace changes from ",[676,97569,97570],{},"javax.*"," to ",[676,97573,97574],{},"jakarta.*"," were made, without any changes to the APIs. In Jakarta EE 10, we start to see the evolution of the APIs, which includes new features.",[651,97577,97578],{},"Now that you have some background on Jakarta EE and why we are discussing it in the context of a Spring Application, it's time to write some code. Create a new Spring Boot project and use one of the new features from Jakarta EE 10.",[4542,97580,11162],{"id":11161},[651,97582,97583,97584,97587],{},"To create a new project, go to ",[812,97585,77478],{"href":20748,"rel":97586},[816],", select Maven as the project type, Java as the language, and the latest stable version of Spring Boot. Then, fill in your project metadata and select the Web, Spring Data JPA, and H2 dependencies.",[651,97589,97590],{},"Once you have entered the details, you can download the project as a zip file and unzip it. Then, you can import the project into your preferred Integrated Development Environment (IDE) and begin development.",[651,97592,97593],{},[660,97594],{"alt":7117,"src":97595},"/images/blog/2023/01/27/start-spring-io.png",[5909,97597,97599],{"id":97598},"spring-data-jpa-entity","Spring Data JPA Entity",[651,97601,97602,97603,97606,97607,97609,97610,73320,97612,97615],{},"With our project created, it's time to write some code. Let's start by creating a new class called ",[676,97604,97605],{},"Product",". When creating an Entity class in Spring Data JPA you need to mark it with the ",[676,97608,16256],{}," annotation. Next, you need to create a new field called ",[676,97611,11341],{},[676,97613,97614],{},"@Id",". Nothing you have done so far is new, but here is where things get interesting.",[651,97617,97618,97619,97622,97623,97626],{},"You could always annotate the ID field with ",[676,97620,97621],{},"@GeneratedValue"," . This annotation defines the primary key generation strategy that the persistence provider must use to generate the primary key. Jakarta EE 10 now adds the ",[676,97624,97625],{},"GenerationType"," for a UUID, so that you can use Universally Unique Identifiers (UUIDs) as the primary key.",[669,97628,97630],{"className":4107,"code":97629,"language":4109,"meta":674,"style":674},"@Entity\npublic class Product {\n\n @Id\n @GeneratedValue(strategy = GenerationType.UUID)\n private UUID id;\n private String title;\n\n public Product() {\n }\n\n public Product(String title) {\n this.title = title;\n }\n\n // getters and setters\n}\n",[676,97631,97632,97638,97649,97653,97659,97674,97681,97687,97691,97699,97703,97707,97719,97729,97733,97737,97742],{"__ignoreMap":674},[679,97633,97634,97636],{"class":681,"line":682},[679,97635,4116],{"class":693},[679,97637,11234],{"class":685},[679,97639,97640,97642,97644,97647],{"class":681,"line":790},[679,97641,6073],{"class":685},[679,97643,4512],{"class":685},[679,97645,97646],{"class":880}," Product",[679,97648,884],{"class":693},[679,97650,97651],{"class":681,"line":892},[679,97652,889],{"emptyLinePlaceholder":797},[679,97654,97655,97657],{"class":681,"line":901},[679,97656,6872],{"class":693},[679,97658,33530],{"class":685},[679,97660,97661,97663,97665,97667,97669,97671],{"class":681,"line":909},[679,97662,6872],{"class":693},[679,97664,33537],{"class":685},[679,97666,745],{"class":693},[679,97668,33542],{"class":931},[679,97670,6883],{"class":685},[679,97672,97673],{"class":693}," GenerationType.UUID)\n",[679,97675,97676,97678],{"class":681,"line":918},[679,97677,9232],{"class":685},[679,97679,97680],{"class":693}," UUID id;\n",[679,97682,97683,97685],{"class":681,"line":935},[679,97684,9232],{"class":685},[679,97686,93032],{"class":693},[679,97688,97689],{"class":681,"line":944},[679,97690,889],{"emptyLinePlaceholder":797},[679,97692,97693,97695,97697],{"class":681,"line":959},[679,97694,6089],{"class":685},[679,97696,97646],{"class":880},[679,97698,2667],{"class":693},[679,97700,97701],{"class":681,"line":964},[679,97702,985],{"class":693},[679,97704,97705],{"class":681,"line":977},[679,97706,889],{"emptyLinePlaceholder":797},[679,97708,97709,97711,97713,97715,97717],{"class":681,"line":982},[679,97710,6089],{"class":685},[679,97712,97646],{"class":880},[679,97714,11400],{"class":693},[679,97716,11750],{"class":2099},[679,97718,4390],{"class":693},[679,97720,97721,97723,97725,97727],{"class":681,"line":988},[679,97722,7862],{"class":931},[679,97724,93105],{"class":693},[679,97726,686],{"class":685},[679,97728,93110],{"class":693},[679,97730,97731],{"class":681,"line":993},[679,97732,985],{"class":693},[679,97734,97735],{"class":681,"line":2129},[679,97736,889],{"emptyLinePlaceholder":797},[679,97738,97739],{"class":681,"line":2140},[679,97740,97741],{"class":1400}," // getters and setters\n",[679,97743,97744],{"class":681,"line":2145},[679,97745,996],{"class":693},[651,97747,97748],{},"As I mentioned in the video I don’t want to get into the pros/cons of using a UUID as the primary key. There were a lot of really good comments in the Twitter thread I linked to above so if you’re interested in that please take a chance to read through that.",[5909,97750,97752],{"id":97751},"spring-data-repository","Spring Data Repository",[651,97754,97755,97756,97759],{},"Now that you have an Entity in place you need to create a Repository. The Spring Data ",[676,97757,97758],{},"ListCrudRepository"," is an interface that extends the Spring Data Repository interface and adds support for the CRUD (Create, Read, Update and Delete) operations on a list of objects. It provides basic methods for saving and retrieving data from a list of objects, and also includes methods for pagination, sorting and querying. This makes it an ideal choice for applications that require the ability to perform basic CRUD operations on a list of objects.",[669,97761,97763],{"className":4107,"code":97762,"language":4109,"meta":674,"style":674},"public interface ProductRepository extends ListCrudRepository\u003CProduct, UUID> {\n\n}\n",[676,97764,97765,97790,97794],{"__ignoreMap":674},[679,97766,97767,97769,97771,97774,97776,97779,97781,97783,97785,97788],{"class":681,"line":682},[679,97768,6073],{"class":685},[679,97770,6994],{"class":685},[679,97772,97773],{"class":880}," ProductRepository",[679,97775,2767],{"class":685},[679,97777,97778],{"class":880}," ListCrudRepository",[679,97780,4505],{"class":693},[679,97782,97605],{"class":685},[679,97784,2797],{"class":693},[679,97786,97787],{"class":685},"UUID",[679,97789,16397],{"class":693},[679,97791,97792],{"class":681,"line":790},[679,97793,889],{"emptyLinePlaceholder":797},[679,97795,97796],{"class":681,"line":892},[679,97797,996],{"class":693},[5909,97799,16213],{"id":16212},[651,97801,97802,97803,97805],{},"Now that you have a repository in place you can create some new products and persist them to the database. A ",[676,97804,16415],{}," is a Spring Boot interface that is used to execute code when the application starts up. It is useful for performing any setup tasks that need to be done before the application can be used. It can also be used to run simple tasks that need to be executed at startup, such as setting up a database or loading data from an external source. It provides an easy way to execute code when the application starts, making it a useful tool for developers.",[651,97807,97808],{},"In the following example you will create 3 new products, save them to the database and then print out all of the records in the db so that you can view the generated IDs.",[669,97810,97812],{"className":4107,"code":97811,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n CommandLineRunner commandLineRunner(ProductRepository productRepository) {\n return args -> {\n List\u003CProduct> products = List.of(new Product(\"Product 1\"),\n new Product(\"Product 2\"),\n new Product(\"Product 3\"));\n productRepository.saveAll(products);\n\n productRepository.findAll().stream().forEach(System.out::println);\n };\n }\n\n}\n",[676,97813,97814,97820,97830,97834,97854,97862,97866,97870,97876,97890,97900,97928,97941,97954,97964,97968,97990,97994,97998,98002],{"__ignoreMap":674},[679,97815,97816,97818],{"class":681,"line":682},[679,97817,4116],{"class":693},[679,97819,6068],{"class":685},[679,97821,97822,97824,97826,97828],{"class":681,"line":790},[679,97823,6073],{"class":685},[679,97825,4512],{"class":685},[679,97827,16878],{"class":880},[679,97829,884],{"class":693},[679,97831,97832],{"class":681,"line":892},[679,97833,889],{"emptyLinePlaceholder":797},[679,97835,97836,97838,97840,97842,97844,97846,97848,97850,97852],{"class":681,"line":901},[679,97837,6089],{"class":685},[679,97839,6092],{"class":685},[679,97841,6095],{"class":685},[679,97843,6098],{"class":880},[679,97845,745],{"class":693},[679,97847,4758],{"class":685},[679,97849,16901],{"class":693},[679,97851,6108],{"class":2099},[679,97853,4390],{"class":693},[679,97855,97856,97858,97860],{"class":681,"line":909},[679,97857,6115],{"class":693},[679,97859,6118],{"class":880},[679,97861,16914],{"class":693},[679,97863,97864],{"class":681,"line":918},[679,97865,985],{"class":693},[679,97867,97868],{"class":681,"line":935},[679,97869,889],{"emptyLinePlaceholder":797},[679,97871,97872,97874],{"class":681,"line":944},[679,97873,6872],{"class":693},[679,97875,16929],{"class":685},[679,97877,97878,97880,97882,97885,97888],{"class":681,"line":959},[679,97879,20982],{"class":693},[679,97881,23712],{"class":880},[679,97883,97884],{"class":693},"(ProductRepository ",[679,97886,97887],{"class":2099},"productRepository",[679,97889,4390],{"class":693},[679,97891,97892,97894,97896,97898],{"class":681,"line":964},[679,97893,9444],{"class":685},[679,97895,16952],{"class":693},[679,97897,16955],{"class":685},[679,97899,884],{"class":693},[679,97901,97902,97904,97906,97909,97911,97913,97915,97917,97919,97921,97923,97926],{"class":681,"line":977},[679,97903,16962],{"class":693},[679,97905,97605],{"class":685},[679,97907,97908],{"class":693},"> products ",[679,97910,686],{"class":685},[679,97912,16669],{"class":693},[679,97914,16672],{"class":880},[679,97916,745],{"class":693},[679,97918,8930],{"class":685},[679,97920,97646],{"class":880},[679,97922,745],{"class":693},[679,97924,97925],{"class":689},"\"Product 1\"",[679,97927,66689],{"class":693},[679,97929,97930,97932,97934,97936,97939],{"class":681,"line":982},[679,97931,85015],{"class":685},[679,97933,97646],{"class":880},[679,97935,745],{"class":693},[679,97937,97938],{"class":689},"\"Product 2\"",[679,97940,66689],{"class":693},[679,97942,97943,97945,97947,97949,97952],{"class":681,"line":988},[679,97944,85015],{"class":685},[679,97946,97646],{"class":880},[679,97948,745],{"class":693},[679,97950,97951],{"class":689},"\"Product 3\"",[679,97953,1669],{"class":693},[679,97955,97956,97959,97961],{"class":681,"line":993},[679,97957,97958],{"class":693}," productRepository.",[679,97960,16716],{"class":880},[679,97962,97963],{"class":693},"(products);\n",[679,97965,97966],{"class":681,"line":2129},[679,97967,889],{"emptyLinePlaceholder":797},[679,97969,97970,97972,97974,97976,97978,97980,97982,97985,97987],{"class":681,"line":2140},[679,97971,97958],{"class":693},[679,97973,34142],{"class":880},[679,97975,10541],{"class":693},[679,97977,87323],{"class":880},[679,97979,10541],{"class":693},[679,97981,46928],{"class":880},[679,97983,97984],{"class":693},"(System.out",[679,97986,90007],{"class":685},[679,97988,97989],{"class":693},"println);\n",[679,97991,97992],{"class":681,"line":2145},[679,97993,17018],{"class":693},[679,97995,97996],{"class":681,"line":2154},[679,97997,985],{"class":693},[679,97999,98000],{"class":681,"line":2159},[679,98001,889],{"emptyLinePlaceholder":797},[679,98003,98004],{"class":681,"line":2164},[679,98005,996],{"class":693},[651,98007,98008],{},"If you run the application you should see the following output:",[669,98010,98012],{"className":5851,"code":98011,"language":5853,"meta":674,"style":674},"Product{id=fe63db2b-a76e-47aa-a1cb-e613b0b987e8, title='Product 1'}\nProduct{id=2236597b-1366-4cad-9a05-77ebb283acd3, title='Product 2'}\nProduct{id=c9f0596a-cfbc-48a7-8202-1ddd058120c0, title='Product 3'}\n",[676,98013,98014,98024,98034],{"__ignoreMap":674},[679,98015,98016,98018,98021],{"class":681,"line":682},[679,98017,97605],{"class":880},[679,98019,98020],{"class":689},"{id=fe63db2b-a76e-47aa-a1cb-e613b0b987e8,",[679,98022,98023],{"class":689}," title='Product 1'}\n",[679,98025,98026,98028,98031],{"class":681,"line":790},[679,98027,97605],{"class":880},[679,98029,98030],{"class":689},"{id=2236597b-1366-4cad-9a05-77ebb283acd3,",[679,98032,98033],{"class":689}," title='Product 2'}\n",[679,98035,98036,98038,98041],{"class":681,"line":892},[679,98037,97605],{"class":880},[679,98039,98040],{"class":689},"{id=c9f0596a-cfbc-48a7-8202-1ddd058120c0,",[679,98042,98043],{"class":689}," title='Product 3'}\n",[651,98045,98046],{},[660,98047],{"alt":98048,"src":98049},"Running the Application","/images/blog/2023/01/27/run_app.png",[4542,98051,9042],{"id":9041},[651,98053,98054],{},"The transition to Jakarta EE 9/10 was not easy, but it was necessary for the Spring Framework. Going forward, the Spring Framework and its associated libraries can benefit from the evolution of the Jakarta EE APIs.",[786,98056,98057],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":98059},[98060,98061,98066],{"id":97560,"depth":790,"text":97561},{"id":11161,"depth":790,"text":11162,"children":98062},[98063,98064,98065],{"id":97598,"depth":892,"text":97599},{"id":97751,"depth":892,"text":97752},{"id":16212,"depth":892,"text":16213},{"id":9041,"depth":790,"text":9042},"In this tutorial you will learn how to use one of the new features in Jakarta EE 10 in a Spring Boot 3 application.",{"slug":98069,"date":98070,"published":797,"author":798,"tags":98071,"cover":98072,"video":98073,"keywords":98074},"jakarta-ee-10-uuid","2023-01-27T10:00:00.000Z",[7077],"./jakarta_ee_10_uuid.png","https://www.youtube.com/embed/ZWwqcH__kr4","Java EE, Jakarta EE, Jakarta EE 10, Spring Boot 3, Spring Framework 6, Java, UUID, Spring Data, Spring Data JPA, Spring Data JPA UUID",{"title":351,"description":98067},"blog/2023/01/27/jakarta-ee-10-uuid","ThJSj18onCsbf18YVVViX-vj9vNuHn0zohj17hgHOkc",{"id":98079,"title":348,"body":98080,"description":849,"extension":793,"meta":99131,"navigation":797,"path":349,"seo":99140,"stem":99141,"__hash__":99142},"content/blog/2023/01/31/graphql-custom-scalars.md",{"type":648,"value":98081,"toc":99122},[98082,98096,98102,98105,98108,98112,98115,98122,98127,98130,98134,98148,98214,98217,98252,98255,98514,98518,98521,98542,98545,98586,98590,98593,98701,98704,98708,98719,98798,98801,98804,98808,98824,98899,98902,98912,98940,98943,99025,99096,99105,99108,99110,99113,99116,99119],[651,98083,98084,98085,2797,98087,2797,98089,2797,98091,48406,98093,98095],{},"Whether you're new to GraphQL or a seasoned user, understanding the scalar types in GraphQL is important. Within the GraphQL specification, there are five defined scalar types: ",[2939,98086,1078],{},[2939,98088,1099],{},[2939,98090,73511],{},[2939,98092,1132],{},[2939,98094,22662],{},". These will work for many components of your schema definition. But what happens when you need a custom scalar? Unfortunately, you'll inevitably need to create a custom scalar at some point.",[651,98097,98098],{},[660,98099],{"alt":98100,"src":98101},"GraphQL Scalar Types","/images/blog/2023/01/31/graphql-scalar-types.png",[651,98103,98104],{},"That's what we'll be doing today. We're going to create a new Spring Boot 3 project that includes Spring for GraphQL. We'll be defining our schema and integrating built-in scalars. Then, we'll discuss how to bring in custom scalars when needed.",[651,98106,98107],{},"So, let's dive into some code.",[4542,98109,98111],{"id":98110},"setting-up-a-new-project-with-graphql","Setting Up a New Project with GraphQL",[651,98113,98114],{},"For those of you who have built a Spring Boot application, you'll be familiar with the process of how we set up a new project.",[651,98116,98117,98118,23212,98120,664],{},"We'll start by heading to start.spring.io, where we'll input our project details. I'm going to use Maven for the project type, with Java as the language. As for dependencies, we're going to include ",[2939,98119,80827],{},[2939,98121,86347],{},[651,98123,98124],{},[660,98125],{"alt":7117,"src":98126},"/images/blog/2023/01/31/scalars-spring-init.png",[651,98128,98129],{},"Once our metadata is filled in, we can hit Generate, which will download a zip file. Open this in your preferred IDE - I typically use IntelliJ Ultimate.",[4542,98131,98133],{"id":98132},"create-a-model","Create a Model",[651,98135,98136,98137,23212,98139,98141,98142,98144,98145,98147],{},"Now that our application is set up and ready to go, the first thing we're going to do is create a new model named Product. We'll give it the properties ",[2939,98138,11341],{},[2939,98140,11750],{},", with ",[2939,98143,11341],{}," being an integer and ",[2939,98146,11750],{}," a string.",[669,98149,98151],{"className":4107,"code":98150,"language":4109,"meta":674,"style":674},"@Entity\npublic class Product {\n\n @Id\n @GeneratedValue\n private Integer id;\n private String title;\n\n // constructors, getters, setters and toSring\n\n}\n",[676,98152,98153,98159,98169,98173,98179,98185,98191,98197,98201,98206,98210],{"__ignoreMap":674},[679,98154,98155,98157],{"class":681,"line":682},[679,98156,4116],{"class":693},[679,98158,11234],{"class":685},[679,98160,98161,98163,98165,98167],{"class":681,"line":790},[679,98162,6073],{"class":685},[679,98164,4512],{"class":685},[679,98166,97646],{"class":880},[679,98168,884],{"class":693},[679,98170,98171],{"class":681,"line":892},[679,98172,889],{"emptyLinePlaceholder":797},[679,98174,98175,98177],{"class":681,"line":901},[679,98176,6872],{"class":693},[679,98178,33530],{"class":685},[679,98180,98181,98183],{"class":681,"line":909},[679,98182,6872],{"class":693},[679,98184,11261],{"class":685},[679,98186,98187,98189],{"class":681,"line":918},[679,98188,9232],{"class":685},[679,98190,83952],{"class":693},[679,98192,98193,98195],{"class":681,"line":935},[679,98194,9232],{"class":685},[679,98196,93032],{"class":693},[679,98198,98199],{"class":681,"line":944},[679,98200,889],{"emptyLinePlaceholder":797},[679,98202,98203],{"class":681,"line":959},[679,98204,98205],{"class":1400}," // constructors, getters, setters and toSring\n",[679,98207,98208],{"class":681,"line":964},[679,98209,889],{"emptyLinePlaceholder":797},[679,98211,98212],{"class":681,"line":977},[679,98213,996],{"class":693},[651,98215,98216],{},"Next, we'll create a repository package to be able to fetch or persist data from the database.",[669,98218,98220],{"className":4107,"code":98219,"language":4109,"meta":674,"style":674},"public interface ProductRepository extends ListCrudRepository\u003CProduct, Integer> {\n\n}\n",[676,98221,98222,98244,98248],{"__ignoreMap":674},[679,98223,98224,98226,98228,98230,98232,98234,98236,98238,98240,98242],{"class":681,"line":682},[679,98225,6073],{"class":685},[679,98227,6994],{"class":685},[679,98229,97773],{"class":880},[679,98231,2767],{"class":685},[679,98233,97778],{"class":880},[679,98235,4505],{"class":693},[679,98237,97605],{"class":685},[679,98239,2797],{"class":693},[679,98241,1083],{"class":685},[679,98243,16397],{"class":693},[679,98245,98246],{"class":681,"line":790},[679,98247,889],{"emptyLinePlaceholder":797},[679,98249,98250],{"class":681,"line":892},[679,98251,996],{"class":693},[651,98253,98254],{},"In order to actually pull this data, we'll create a Command Line Runner which will include a few new products that we can later test.",[669,98256,98258],{"className":4107,"code":98257,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n CommandLineRunner commandLineRunner(ProductRepository repository) {\n return args -> {\n List\u003CProduct> products = List.of(\n new Product(\"Product 1\",true,1.99F,new BigDecimal(9.99),LocalDateTime.now()),\n new Product(\"Product 2\",false, 3.99F,new BigDecimal(9.99),LocalDateTime.now()),\n new Product(\"Product 3\", true, 19.99F,new BigDecimal(9.99),LocalDateTime.now())\n );\n repository.saveAll(products);\n repository.findAll().forEach(System.out::println);\n };\n }\n\n}\n",[676,98259,98260,98266,98276,98280,98300,98308,98312,98316,98322,98334,98344,98360,98400,98435,98470,98474,98482,98498,98502,98506,98510],{"__ignoreMap":674},[679,98261,98262,98264],{"class":681,"line":682},[679,98263,4116],{"class":693},[679,98265,6068],{"class":685},[679,98267,98268,98270,98272,98274],{"class":681,"line":790},[679,98269,6073],{"class":685},[679,98271,4512],{"class":685},[679,98273,16878],{"class":880},[679,98275,884],{"class":693},[679,98277,98278],{"class":681,"line":892},[679,98279,889],{"emptyLinePlaceholder":797},[679,98281,98282,98284,98286,98288,98290,98292,98294,98296,98298],{"class":681,"line":901},[679,98283,6089],{"class":685},[679,98285,6092],{"class":685},[679,98287,6095],{"class":685},[679,98289,6098],{"class":880},[679,98291,745],{"class":693},[679,98293,4758],{"class":685},[679,98295,16901],{"class":693},[679,98297,6108],{"class":2099},[679,98299,4390],{"class":693},[679,98301,98302,98304,98306],{"class":681,"line":909},[679,98303,6115],{"class":693},[679,98305,6118],{"class":880},[679,98307,16914],{"class":693},[679,98309,98310],{"class":681,"line":918},[679,98311,985],{"class":693},[679,98313,98314],{"class":681,"line":935},[679,98315,889],{"emptyLinePlaceholder":797},[679,98317,98318,98320],{"class":681,"line":944},[679,98319,6872],{"class":693},[679,98321,16929],{"class":685},[679,98323,98324,98326,98328,98330,98332],{"class":681,"line":959},[679,98325,20982],{"class":693},[679,98327,23712],{"class":880},[679,98329,97884],{"class":693},[679,98331,16596],{"class":2099},[679,98333,4390],{"class":693},[679,98335,98336,98338,98340,98342],{"class":681,"line":964},[679,98337,9444],{"class":685},[679,98339,16952],{"class":693},[679,98341,16955],{"class":685},[679,98343,884],{"class":693},[679,98345,98346,98348,98350,98352,98354,98356,98358],{"class":681,"line":977},[679,98347,16962],{"class":693},[679,98349,97605],{"class":685},[679,98351,97908],{"class":693},[679,98353,686],{"class":685},[679,98355,16669],{"class":693},[679,98357,16672],{"class":880},[679,98359,21337],{"class":693},[679,98361,98362,98365,98367,98369,98371,98373,98375,98377,98380,98382,98384,98387,98389,98392,98395,98397],{"class":681,"line":982},[679,98363,98364],{"class":685}," new",[679,98366,97646],{"class":880},[679,98368,745],{"class":693},[679,98370,97925],{"class":689},[679,98372,1202],{"class":693},[679,98374,3441],{"class":931},[679,98376,1202],{"class":693},[679,98378,98379],{"class":931},"1.99F",[679,98381,1202],{"class":693},[679,98383,8930],{"class":685},[679,98385,98386],{"class":880}," BigDecimal",[679,98388,745],{"class":693},[679,98390,98391],{"class":931},"9.99",[679,98393,98394],{"class":693},"),LocalDateTime.",[679,98396,90866],{"class":880},[679,98398,98399],{"class":693},"()),\n",[679,98401,98402,98404,98406,98408,98410,98412,98414,98416,98419,98421,98423,98425,98427,98429,98431,98433],{"class":681,"line":988},[679,98403,98364],{"class":685},[679,98405,97646],{"class":880},[679,98407,745],{"class":693},[679,98409,97938],{"class":689},[679,98411,1202],{"class":693},[679,98413,1135],{"class":931},[679,98415,2797],{"class":693},[679,98417,98418],{"class":931},"3.99F",[679,98420,1202],{"class":693},[679,98422,8930],{"class":685},[679,98424,98386],{"class":880},[679,98426,745],{"class":693},[679,98428,98391],{"class":931},[679,98430,98394],{"class":693},[679,98432,90866],{"class":880},[679,98434,98399],{"class":693},[679,98436,98437,98439,98441,98443,98445,98447,98449,98451,98454,98456,98458,98460,98462,98464,98466,98468],{"class":681,"line":993},[679,98438,98364],{"class":685},[679,98440,97646],{"class":880},[679,98442,745],{"class":693},[679,98444,97951],{"class":689},[679,98446,2797],{"class":693},[679,98448,3441],{"class":931},[679,98450,2797],{"class":693},[679,98452,98453],{"class":931},"19.99F",[679,98455,1202],{"class":693},[679,98457,8930],{"class":685},[679,98459,98386],{"class":880},[679,98461,745],{"class":693},[679,98463,98391],{"class":931},[679,98465,98394],{"class":693},[679,98467,90866],{"class":880},[679,98469,40172],{"class":693},[679,98471,98472],{"class":681,"line":2129},[679,98473,27885],{"class":693},[679,98475,98476,98478,98480],{"class":681,"line":2140},[679,98477,17009],{"class":693},[679,98479,16716],{"class":880},[679,98481,97963],{"class":693},[679,98483,98484,98486,98488,98490,98492,98494,98496],{"class":681,"line":2145},[679,98485,17009],{"class":693},[679,98487,34142],{"class":880},[679,98489,10541],{"class":693},[679,98491,46928],{"class":880},[679,98493,97984],{"class":693},[679,98495,90007],{"class":685},[679,98497,97989],{"class":693},[679,98499,98500],{"class":681,"line":2154},[679,98501,17018],{"class":693},[679,98503,98504],{"class":681,"line":2159},[679,98505,985],{"class":693},[679,98507,98508],{"class":681,"line":2164},[679,98509,889],{"emptyLinePlaceholder":797},[679,98511,98512],{"class":681,"line":3134},[679,98513,996],{"class":693},[4542,98515,98517],{"id":98516},"defining-our-schema","Defining our Schema",[651,98519,98520],{},"After successfully creating our products and ensuring they're in the database, let's move onto defining our GraphQL schema.",[651,98522,63025,98523,98525,98526,98529,98530,98532,98533,98535,98536,23212,98538,82847,98540,664],{},[676,98524,87935],{}," directory, we will create a new file named **",[676,98527,98528],{},"**schema.graphqls",". Here we will start with our object type, ",[2939,98531,97605],{},". With ID and Title being our two types, we will declare ",[2939,98534,11341],{}," as an ",[2939,98537,22662],{},[2939,98539,11750],{},[2939,98541,4758],{},[651,98543,98544],{},"Next, we define our 'Query' type. We want a way to get all of our products and this will return a collection of products.",[669,98546,98548],{"className":66259,"code":98547,"language":66261,"meta":674,"style":674},"type Product {\n id: ID!\n title: String\n}\n\ntype Query {\n allProducts: [Product]!\n}\n",[676,98549,98550,98555,98560,98565,98569,98573,98577,98582],{"__ignoreMap":674},[679,98551,98552],{"class":681,"line":682},[679,98553,98554],{},"type Product {\n",[679,98556,98557],{"class":681,"line":790},[679,98558,98559],{}," id: ID!\n",[679,98561,98562],{"class":681,"line":892},[679,98563,98564],{}," title: String\n",[679,98566,98567],{"class":681,"line":901},[679,98568,996],{},[679,98570,98571],{"class":681,"line":909},[679,98572,889],{"emptyLinePlaceholder":797},[679,98574,98575],{"class":681,"line":918},[679,98576,86689],{},[679,98578,98579],{"class":681,"line":935},[679,98580,98581],{}," allProducts: [Product]!\n",[679,98583,98584],{"class":681,"line":944},[679,98585,996],{},[4542,98587,98589],{"id":98588},"controller-creation-and-testing","Controller Creation and Testing",[651,98591,98592],{},"Now it's time to create our product controller, along with a 'Find All' method, which will pull all of our products from the database. Finally, we link our GraphQL 'allProducts' query to our repository's 'findAll' method.",[669,98594,98596],{"className":4107,"code":98595,"language":4109,"meta":674,"style":674},"@Controller\npublic class ProductController {\n\n private final ProductRepository repository;\n\n public ProductController(ProductRepository repository) {\n this.repository = repository;\n }\n\n @QueryMapping\n public List\u003CProduct> allProducts() {\n return repository.findAll();\n }\n}\n",[676,98597,98598,98604,98615,98619,98628,98632,98644,98654,98658,98662,98668,98683,98693,98697],{"__ignoreMap":674},[679,98599,98600,98602],{"class":681,"line":682},[679,98601,4116],{"class":693},[679,98603,9942],{"class":685},[679,98605,98606,98608,98610,98613],{"class":681,"line":790},[679,98607,6073],{"class":685},[679,98609,4512],{"class":685},[679,98611,98612],{"class":880}," ProductController",[679,98614,884],{"class":693},[679,98616,98617],{"class":681,"line":892},[679,98618,889],{"emptyLinePlaceholder":797},[679,98620,98621,98623,98625],{"class":681,"line":901},[679,98622,9232],{"class":685},[679,98624,12768],{"class":685},[679,98626,98627],{"class":693}," ProductRepository repository;\n",[679,98629,98630],{"class":681,"line":909},[679,98631,889],{"emptyLinePlaceholder":797},[679,98633,98634,98636,98638,98640,98642],{"class":681,"line":918},[679,98635,6089],{"class":685},[679,98637,98612],{"class":880},[679,98639,97884],{"class":693},[679,98641,16596],{"class":2099},[679,98643,4390],{"class":693},[679,98645,98646,98648,98650,98652],{"class":681,"line":935},[679,98647,7862],{"class":931},[679,98649,16605],{"class":693},[679,98651,686],{"class":685},[679,98653,16610],{"class":693},[679,98655,98656],{"class":681,"line":944},[679,98657,985],{"class":693},[679,98659,98660],{"class":681,"line":959},[679,98661,889],{"emptyLinePlaceholder":797},[679,98663,98664,98666],{"class":681,"line":964},[679,98665,6872],{"class":693},[679,98667,88905],{"class":685},[679,98669,98670,98672,98674,98676,98678,98681],{"class":681,"line":977},[679,98671,6089],{"class":685},[679,98673,87217],{"class":693},[679,98675,97605],{"class":685},[679,98677,20881],{"class":693},[679,98679,98680],{"class":880},"allProducts",[679,98682,2667],{"class":693},[679,98684,98685,98687,98689,98691],{"class":681,"line":982},[679,98686,9444],{"class":685},[679,98688,85606],{"class":693},[679,98690,34142],{"class":880},[679,98692,9317],{"class":693},[679,98694,98695],{"class":681,"line":988},[679,98696,985],{"class":693},[679,98698,98699],{"class":681,"line":993},[679,98700,996],{"class":693},[651,98702,98703],{},"Once we restart our application, we can navigate to localhost:8080/graphiql and test whether our query returns the products we stored earlier.",[4542,98705,98707],{"id":98706},"dealing-with-four-built-in-scalars","Dealing with Four Built-in Scalars",[651,98709,98710,98711,98714,98715,98718],{},"With our application working so far, let's move on to implementing some more built-in scalars - the boolean and float types. For our Product model, we'll add two new properties: ",[2939,98712,98713],{},"onSale"," (a boolean) and ",[2939,98716,98717],{},"weight"," (a float). Remember to update your schema definition to include these new fields.",[669,98720,98722],{"className":4107,"code":98721,"language":4109,"meta":674,"style":674},"@Entity\npublic class Product {\n\n @Id\n @GeneratedValue\n private Integer id;\n private String title;\n private Boolean isOnSale;\n private Float weight;\n\n // constructors, getters, setters and toSring\n\n}\n",[676,98723,98724,98730,98740,98744,98750,98756,98762,98768,98775,98782,98786,98790,98794],{"__ignoreMap":674},[679,98725,98726,98728],{"class":681,"line":682},[679,98727,4116],{"class":693},[679,98729,11234],{"class":685},[679,98731,98732,98734,98736,98738],{"class":681,"line":790},[679,98733,6073],{"class":685},[679,98735,4512],{"class":685},[679,98737,97646],{"class":880},[679,98739,884],{"class":693},[679,98741,98742],{"class":681,"line":892},[679,98743,889],{"emptyLinePlaceholder":797},[679,98745,98746,98748],{"class":681,"line":901},[679,98747,6872],{"class":693},[679,98749,33530],{"class":685},[679,98751,98752,98754],{"class":681,"line":909},[679,98753,6872],{"class":693},[679,98755,11261],{"class":685},[679,98757,98758,98760],{"class":681,"line":918},[679,98759,9232],{"class":685},[679,98761,83952],{"class":693},[679,98763,98764,98766],{"class":681,"line":935},[679,98765,9232],{"class":685},[679,98767,93032],{"class":693},[679,98769,98770,98772],{"class":681,"line":944},[679,98771,9232],{"class":685},[679,98773,98774],{"class":693}," Boolean isOnSale;\n",[679,98776,98777,98779],{"class":681,"line":959},[679,98778,9232],{"class":685},[679,98780,98781],{"class":693}," Float weight;\n",[679,98783,98784],{"class":681,"line":964},[679,98785,889],{"emptyLinePlaceholder":797},[679,98787,98788],{"class":681,"line":977},[679,98789,98205],{"class":1400},[679,98791,98792],{"class":681,"line":982},[679,98793,889],{"emptyLinePlaceholder":797},[679,98795,98796],{"class":681,"line":988},[679,98797,996],{"class":693},[651,98799,98800],{},"Now, if we rerun our application and add 'onSale' and 'weight' to our query, we should successfully receive back the details we stored for them.",[651,98802,98803],{},"So, what do we do when we need some custom scalars? Let's investigate how to handle this.",[4542,98805,98807],{"id":98806},"implementing-custom-scalars","Implementing Custom Scalars",[651,98809,98810,98811,98814,98815,98818,98819,82847,98821,664],{},"Let's add a ",[2939,98812,98813],{},"price"," to our Product model, as a ",[2939,98816,98817],{},"BigDecimal",", and a ",[2939,98820,5161],{},[2939,98822,98823],{},"LocalDateTime",[669,98825,98827],{"className":4107,"code":98826,"language":4109,"meta":674,"style":674},"@Entity\npublic class Product {\n\n @Id\n @GeneratedValue\n private Integer id;\n private String title;\n private Boolean isOnSale;\n private Float weight;\n private BigDecimal price;\n private LocalDateTime dateCreated;\n",[676,98828,98829,98835,98845,98849,98855,98861,98867,98873,98879,98885,98892],{"__ignoreMap":674},[679,98830,98831,98833],{"class":681,"line":682},[679,98832,4116],{"class":693},[679,98834,11234],{"class":685},[679,98836,98837,98839,98841,98843],{"class":681,"line":790},[679,98838,6073],{"class":685},[679,98840,4512],{"class":685},[679,98842,97646],{"class":880},[679,98844,884],{"class":693},[679,98846,98847],{"class":681,"line":892},[679,98848,889],{"emptyLinePlaceholder":797},[679,98850,98851,98853],{"class":681,"line":901},[679,98852,6872],{"class":693},[679,98854,33530],{"class":685},[679,98856,98857,98859],{"class":681,"line":909},[679,98858,6872],{"class":693},[679,98860,11261],{"class":685},[679,98862,98863,98865],{"class":681,"line":918},[679,98864,9232],{"class":685},[679,98866,83952],{"class":693},[679,98868,98869,98871],{"class":681,"line":935},[679,98870,9232],{"class":685},[679,98872,93032],{"class":693},[679,98874,98875,98877],{"class":681,"line":944},[679,98876,9232],{"class":685},[679,98878,98774],{"class":693},[679,98880,98881,98883],{"class":681,"line":959},[679,98882,9232],{"class":685},[679,98884,98781],{"class":693},[679,98886,98887,98889],{"class":681,"line":964},[679,98888,9232],{"class":685},[679,98890,98891],{"class":693}," BigDecimal price;\n",[679,98893,98894,98896],{"class":681,"line":977},[679,98895,9232],{"class":685},[679,98897,98898],{"class":693}," LocalDateTime dateCreated;\n",[651,98900,98901],{},"Unfortunately, these two properties aren't covered by our built-in scalars, so we are going to need to use custom scalars.",[651,98903,98904,98905,98908,98909,664],{},"The first place to look for help with this is the GraphQL Java documentation. Herein, we also find details of the ",[7300,98906,98907],{},"Extended Scalars"," - which includes special types like BigDecimal and BigInteger - but this requires a separate library, the ",[7300,98910,98911],{},"GraphQL Java Extended Scalars Library",[669,98913,98915],{"className":9101,"code":98914,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>com.graphql-java\u003C/groupId>\n \u003CartifactId>graphql-java-extended-scalars\u003C/artifactId>\n \u003Cversion>20.0\u003C/version>\n\u003C/dependency>\n",[676,98916,98917,98921,98926,98931,98936],{"__ignoreMap":674},[679,98918,98919],{"class":681,"line":682},[679,98920,9110],{},[679,98922,98923],{"class":681,"line":790},[679,98924,98925],{}," \u003CgroupId>com.graphql-java\u003C/groupId>\n",[679,98927,98928],{"class":681,"line":892},[679,98929,98930],{}," \u003CartifactId>graphql-java-extended-scalars\u003C/artifactId>\n",[679,98932,98933],{"class":681,"line":901},[679,98934,98935],{}," \u003Cversion>20.0\u003C/version>\n",[679,98937,98938],{"class":681,"line":909},[679,98939,9125],{},[651,98941,98942],{},"To use it, we need to add the library to our Maven dependencies and create a new config bean for the extended scalars. This allows us to declare the scalar in the GraphQL schema definition.",[669,98944,98946],{"className":4107,"code":98945,"language":4109,"meta":674,"style":674},"@Configuration\npublic class GraphQlConfig {\n\n @Bean\n public RuntimeWiringConfigurer runtimeWiringConfigurer() {\n return wiringBuilder -> wiringBuilder\n .scalar(ExtendedScalars.GraphQLBigDecimal);\n\n }\n\n}\n",[676,98947,98948,98954,98965,98969,98975,98987,98999,99009,99013,99017,99021],{"__ignoreMap":674},[679,98949,98950,98952],{"class":681,"line":682},[679,98951,4116],{"class":693},[679,98953,6212],{"class":685},[679,98955,98956,98958,98960,98963],{"class":681,"line":790},[679,98957,6073],{"class":685},[679,98959,4512],{"class":685},[679,98961,98962],{"class":880}," GraphQlConfig",[679,98964,884],{"class":693},[679,98966,98967],{"class":681,"line":892},[679,98968,889],{"emptyLinePlaceholder":797},[679,98970,98971,98973],{"class":681,"line":901},[679,98972,6872],{"class":693},[679,98974,16929],{"class":685},[679,98976,98977,98979,98982,98985],{"class":681,"line":909},[679,98978,6089],{"class":685},[679,98980,98981],{"class":693}," RuntimeWiringConfigurer ",[679,98983,98984],{"class":880},"runtimeWiringConfigurer",[679,98986,2667],{"class":693},[679,98988,98989,98991,98994,98996],{"class":681,"line":918},[679,98990,9444],{"class":685},[679,98992,98993],{"class":693}," wiringBuilder ",[679,98995,16955],{"class":685},[679,98997,98998],{"class":693}," wiringBuilder\n",[679,99000,99001,99003,99006],{"class":681,"line":935},[679,99002,73482],{"class":693},[679,99004,99005],{"class":880},"scalar",[679,99007,99008],{"class":693},"(ExtendedScalars.GraphQLBigDecimal);\n",[679,99010,99011],{"class":681,"line":944},[679,99012,889],{"emptyLinePlaceholder":797},[679,99014,99015],{"class":681,"line":959},[679,99016,985],{"class":693},[679,99018,99019],{"class":681,"line":964},[679,99020,889],{"emptyLinePlaceholder":797},[679,99022,99023],{"class":681,"line":977},[679,99024,996],{"class":693},[669,99026,99028],{"className":66259,"code":99027,"language":66261,"meta":674,"style":674},"scalar BigDecimal\nscalar LocalDateTime\n\ntype Product {\n id: ID!\n title: String\n isOnSale: Boolean\n weight: Float\n price: BigDecimal\n dateCreated: LocalDateTime\n}\n\ntype Query {\n allProducts: [Product]!\n}\n",[676,99029,99030,99035,99040,99044,99048,99052,99056,99061,99066,99071,99076,99080,99084,99088,99092],{"__ignoreMap":674},[679,99031,99032],{"class":681,"line":682},[679,99033,99034],{},"scalar BigDecimal\n",[679,99036,99037],{"class":681,"line":790},[679,99038,99039],{},"scalar LocalDateTime\n",[679,99041,99042],{"class":681,"line":892},[679,99043,889],{"emptyLinePlaceholder":797},[679,99045,99046],{"class":681,"line":901},[679,99047,98554],{},[679,99049,99050],{"class":681,"line":909},[679,99051,98559],{},[679,99053,99054],{"class":681,"line":918},[679,99055,98564],{},[679,99057,99058],{"class":681,"line":935},[679,99059,99060],{}," isOnSale: Boolean\n",[679,99062,99063],{"class":681,"line":944},[679,99064,99065],{}," weight: Float\n",[679,99067,99068],{"class":681,"line":959},[679,99069,99070],{}," price: BigDecimal\n",[679,99072,99073],{"class":681,"line":964},[679,99074,99075],{}," dateCreated: LocalDateTime\n",[679,99077,99078],{"class":681,"line":977},[679,99079,996],{},[679,99081,99082],{"class":681,"line":982},[679,99083,889],{"emptyLinePlaceholder":797},[679,99085,99086],{"class":681,"line":988},[679,99087,86689],{},[679,99089,99090],{"class":681,"line":993},[679,99091,98581],{},[679,99093,99094],{"class":681,"line":2129},[679,99095,996],{},[651,99097,99098,99099,99102,99103,664],{},"The LocalDateTime type isn't covered in the Extended Scalars but another project – ",[7300,99100,99101],{},"GraphQL Java DateTime"," - can take care of this one. Include this new dependency and update your ",[676,99104,87939],{},[651,99106,99107],{},"Having made these changes, let's rerun our application one last time and see if we are successful.",[4542,99109,9042],{"id":9041},[651,99111,99112],{},"As we can see, we have now successfully used all of the five built-in scalar types, and even created some custom scalars for our application.",[651,99114,99115],{},"Remember though, before you go all out creating your own custom scalar, always check if there's an existing scalar that could do the job for you.",[651,99117,99118],{},"GraphQL is great. It makes building APIs easy and manageable, and I love using it with Spring. I hope you found this tutorial helpful, and remember, happy coding!",[786,99120,99121],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":99123},[99124,99125,99126,99127,99128,99129,99130],{"id":98110,"depth":790,"text":98111},{"id":98132,"depth":790,"text":98133},{"id":98516,"depth":790,"text":98517},{"id":98588,"depth":790,"text":98589},{"id":98706,"depth":790,"text":98707},{"id":98806,"depth":790,"text":98807},{"id":9041,"depth":790,"text":9042},{"slug":99132,"date":99133,"published":797,"author":798,"tags":99134,"cover":99136,"video":99137,"github":99138,"keywords":99139},"graphql-custom-scalars","2023-01-31T08:00:00.000Z",[7077,99135],"GraphQL","./graphql-scalars-thumbnail.png","https://www.youtube.com/embed/ooknmgr4WiA","https://github.com/danvega/graphql-scalars","Spring Boot, Spring for GraphQL, GraphQL Java, GraphQL Scalars, Java, GraphQL",{"title":348,"description":849},"blog/2023/01/31/graphql-custom-scalars","YYxJ7N86NkrIUXBAsUaE3hnwrX_d038hVbAfMQBbWIc",{"id":99144,"title":345,"body":99145,"description":849,"extension":793,"meta":99674,"navigation":797,"path":346,"seo":99682,"stem":99683,"__hash__":99684},"content/blog/2023/02/03/native-images-graalvm.md",{"type":648,"value":99146,"toc":99667},[99147,99150,99152,99155,99159,99166,99170,99179,99185,99214,99251,99316,99381,99391,99514,99524,99528,99534,99537,99550,99556,99572,99590,99651,99659,99661,99664],[651,99148,99149],{},"When building native executables with GraalVM, you might run into issues when your application uses dynamic language features of the JVM. This article will walk you through a practical example of how to work with these types of features and provide metadata to GraalVM's Native Image Builder.",[4542,99151,11142],{"id":11141},[651,99153,99154],{},"When you build a native executable without any dynamic language features, everything works as expected. However, when you introduce dynamic features like reflection, you need to instruct GraalVM where the different dynamic features in your application are.",[4542,99156,99158],{"id":99157},"graalvm-reachability-metadata","GraalVM Reachability Metadata",[651,99160,99161,99162,99165],{},"GraalVM provides a way to include metadata about the dynamic features of your JVM application. You can supply metadata to the Native Image Builder by providing JSON files stored in the ",[676,99163,99164],{},"META-INF/native-image"," directory. This article will demonstrate this approach using a sample Java project.",[4542,99167,99169],{"id":99168},"creating-a-java-project-with-dynamic-features","Creating a Java Project with Dynamic Features",[651,99171,99172,99173,99178],{},"First, create a new Java project with Maven and the GraalVM Native Maven plugin. In this example, the project will use Java, Maven, and the ",[812,99174,99177],{"href":99175,"rel":99176},"https://www.graalvm.org/",[816],"GraalVM JDK 17.22.3",". Create a simple \"Hello World\" application with no dynamic features to start.",[651,99180,99181],{},[660,99182],{"alt":99183,"src":99184},"Create new Java Project in IntelliJ","/images/blog/2023/02/03/intellij-new-project.png",[651,99186,99187,99188,99191,99192,23212,99195,51880,99198,99200,99201,51880,99204,23212,99206,99208,99209,99211,99212,78287],{},"Next, introduce dynamic capabilities in the form of a ",[676,99189,99190],{},"Message"," interface, with two implementations: ",[676,99193,99194],{},"NiceMessage",[676,99196,99197],{},"MeanMessage",[676,99199,99190],{}," interface will have a single method called ",[676,99202,99203],{},"printMessage()",[676,99205,99194],{},[676,99207,99197],{}," classes will each implement the ",[676,99210,99190],{}," interface with their versions of the ",[676,99213,99203],{},[669,99215,99217],{"className":4107,"code":99216,"language":4109,"meta":674,"style":674},"public interface Message {\n\n void printMessage();\n\n}\n",[676,99218,99219,99230,99234,99243,99247],{"__ignoreMap":674},[679,99220,99221,99223,99225,99228],{"class":681,"line":682},[679,99222,6073],{"class":685},[679,99224,6994],{"class":685},[679,99226,99227],{"class":880}," Message",[679,99229,884],{"class":693},[679,99231,99232],{"class":681,"line":790},[679,99233,889],{"emptyLinePlaceholder":797},[679,99235,99236,99238,99241],{"class":681,"line":892},[679,99237,3314],{"class":685},[679,99239,99240],{"class":880}," printMessage",[679,99242,9317],{"class":693},[679,99244,99245],{"class":681,"line":901},[679,99246,889],{"emptyLinePlaceholder":797},[679,99248,99249],{"class":681,"line":909},[679,99250,996],{"class":693},[669,99252,99254],{"className":4107,"code":99253,"language":4109,"meta":674,"style":674},"public class NiceMessage implements Message {\n\n @Override\n public void printMessage() {\n System.out.println(\"This is a nice message!\");\n }\n\n}\n",[676,99255,99256,99271,99275,99281,99291,99304,99308,99312],{"__ignoreMap":674},[679,99257,99258,99260,99262,99265,99267,99269],{"class":681,"line":682},[679,99259,6073],{"class":685},[679,99261,4512],{"class":685},[679,99263,99264],{"class":880}," NiceMessage",[679,99266,4661],{"class":685},[679,99268,99227],{"class":880},[679,99270,884],{"class":693},[679,99272,99273],{"class":681,"line":790},[679,99274,889],{"emptyLinePlaceholder":797},[679,99276,99277,99279],{"class":681,"line":892},[679,99278,6872],{"class":693},[679,99280,10723],{"class":685},[679,99282,99283,99285,99287,99289],{"class":681,"line":901},[679,99284,6089],{"class":685},[679,99286,6095],{"class":685},[679,99288,99240],{"class":880},[679,99290,2667],{"class":693},[679,99292,99293,99295,99297,99299,99302],{"class":681,"line":909},[679,99294,9592],{"class":693},[679,99296,1729],{"class":880},[679,99298,745],{"class":693},[679,99300,99301],{"class":689},"\"This is a nice message!\"",[679,99303,1208],{"class":693},[679,99305,99306],{"class":681,"line":918},[679,99307,985],{"class":693},[679,99309,99310],{"class":681,"line":935},[679,99311,889],{"emptyLinePlaceholder":797},[679,99313,99314],{"class":681,"line":944},[679,99315,996],{"class":693},[669,99317,99319],{"className":4107,"code":99318,"language":4109,"meta":674,"style":674},"public class MeanMessage implements Message {\n\n @Override\n public void printMessage() {\n System.out.println(\"This is a mean message!\");\n }\n\n}\n",[676,99320,99321,99336,99340,99346,99356,99369,99373,99377],{"__ignoreMap":674},[679,99322,99323,99325,99327,99330,99332,99334],{"class":681,"line":682},[679,99324,6073],{"class":685},[679,99326,4512],{"class":685},[679,99328,99329],{"class":880}," MeanMessage",[679,99331,4661],{"class":685},[679,99333,99227],{"class":880},[679,99335,884],{"class":693},[679,99337,99338],{"class":681,"line":790},[679,99339,889],{"emptyLinePlaceholder":797},[679,99341,99342,99344],{"class":681,"line":892},[679,99343,6872],{"class":693},[679,99345,10723],{"class":685},[679,99347,99348,99350,99352,99354],{"class":681,"line":901},[679,99349,6089],{"class":685},[679,99351,6095],{"class":685},[679,99353,99240],{"class":880},[679,99355,2667],{"class":693},[679,99357,99358,99360,99362,99364,99367],{"class":681,"line":909},[679,99359,9592],{"class":693},[679,99361,1729],{"class":880},[679,99363,745],{"class":693},[679,99365,99366],{"class":689},"\"This is a mean message!\"",[679,99368,1208],{"class":693},[679,99370,99371],{"class":681,"line":918},[679,99372,985],{"class":693},[679,99374,99375],{"class":681,"line":935},[679,99376,889],{"emptyLinePlaceholder":797},[679,99378,99379],{"class":681,"line":944},[679,99380,996],{"class":693},[651,99382,99383,99384,51393,99386,99388,99389,78287],{},"The main class of the application will dynamically invoke either the ",[676,99385,99194],{},[676,99387,99197],{}," implementation based on a program argument. To accomplish this, the main class will use reflection to dynamically load and call the target implementation's ",[676,99390,99203],{},[669,99392,99394],{"className":4107,"code":99393,"language":4109,"meta":674,"style":674},"public class Application {\n\n public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,\n InvocationTargetException, InstantiationException, IllegalAccessException {\n Class\u003C?> clazz = Class.forName(\"dev.danvega.\" + args[0]);\n clazz.getMethod(\"printMessage\").invoke(clazz.getConstructor().newInstance());\n }\n\n}\n",[676,99395,99396,99406,99410,99435,99440,99472,99502,99506,99510],{"__ignoreMap":674},[679,99397,99398,99400,99402,99404],{"class":681,"line":682},[679,99399,6073],{"class":685},[679,99401,4512],{"class":685},[679,99403,16878],{"class":880},[679,99405,884],{"class":693},[679,99407,99408],{"class":681,"line":790},[679,99409,889],{"emptyLinePlaceholder":797},[679,99411,99412,99414,99416,99418,99420,99422,99424,99426,99428,99430,99432],{"class":681,"line":892},[679,99413,6089],{"class":685},[679,99415,6092],{"class":685},[679,99417,6095],{"class":685},[679,99419,6098],{"class":880},[679,99421,745],{"class":693},[679,99423,4758],{"class":685},[679,99425,16901],{"class":693},[679,99427,6108],{"class":2099},[679,99429,2378],{"class":693},[679,99431,9580],{"class":685},[679,99433,99434],{"class":693}," ClassNotFoundException, NoSuchMethodException,\n",[679,99436,99437],{"class":681,"line":901},[679,99438,99439],{"class":693}," InvocationTargetException, InstantiationException, IllegalAccessException {\n",[679,99441,99442,99445,99447,99450,99452,99455,99458,99460,99463,99465,99468,99470],{"class":681,"line":909},[679,99443,99444],{"class":693}," Class\u003C",[679,99446,2381],{"class":685},[679,99448,99449],{"class":693},"> clazz ",[679,99451,686],{"class":685},[679,99453,99454],{"class":693}," Class.",[679,99456,99457],{"class":880},"forName",[679,99459,745],{"class":693},[679,99461,99462],{"class":689},"\"dev.danvega.\"",[679,99464,3059],{"class":685},[679,99466,99467],{"class":693}," args[",[679,99469,1060],{"class":931},[679,99471,67196],{"class":693},[679,99473,99474,99477,99479,99481,99484,99486,99489,99492,99495,99497,99500],{"class":681,"line":918},[679,99475,99476],{"class":693}," clazz.",[679,99478,10572],{"class":880},[679,99480,745],{"class":693},[679,99482,99483],{"class":689},"\"printMessage\"",[679,99485,47213],{"class":693},[679,99487,99488],{"class":880},"invoke",[679,99490,99491],{"class":693},"(clazz.",[679,99493,99494],{"class":880},"getConstructor",[679,99496,10541],{"class":693},[679,99498,99499],{"class":880},"newInstance",[679,99501,9431],{"class":693},[679,99503,99504],{"class":681,"line":935},[679,99505,985],{"class":693},[679,99507,99508],{"class":681,"line":944},[679,99509,889],{"emptyLinePlaceholder":797},[679,99511,99512],{"class":681,"line":959},[679,99513,996],{"class":693},[651,99515,99516,99517,99519,99520,99523],{},"When you run the application with your desired argument (e.g., ",[676,99518,99194],{},"), it will work correctly. However, when you attempt to build a native image with GraalVM, it will fail at runtime with a ",[676,99521,99522],{},"ClassNotFoundException",". This is because GraalVM does not include the dynamic classes in the final binary since it cannot discover them through static analysis.",[4542,99525,99527],{"id":99526},"using-a-tracing-agent","Using a Tracing Agent",[651,99529,99530,99531,99533],{},"To resolve this issue, you can use a tracing agent to supply metadata about the dynamic features in your application. The tracing agent is a JVM command-line option that you can use when running your application. It generates JSON configuration files in the ",[676,99532,99164],{}," directory to inform GraalVM's Native Image Builder about the dynamically reachable classes and methods.",[651,99535,99536],{},"For example, in IntelliJ IDEA, go to the \"Edit Configurations\" menu and add the following VM option:",[669,99538,99540],{"className":7993,"code":99539,"language":7995,"meta":674,"style":674},"-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image\n",[676,99541,99542],{"__ignoreMap":674},[679,99543,99544,99547],{"class":681,"line":682},[679,99545,99546],{"class":880},"-agentlib:native-image-agent",[679,99548,99549],{"class":689},"=config-output-dir=src/main/resources/META-INF/native-image\n",[651,99551,99552],{},[660,99553],{"alt":99554,"src":99555},"VM Options in IntelliJ","/images/blog/2023/02/03/vm-options.png",[651,99557,99558,99559,99562,99563,99566,99567,23212,99569,99571],{},"Now, when you run your application with the tracing agent enabled, GraalVM will generate the required configuration files, such as ",[676,99560,99561],{},"reflect-config.json",", in the specified output directory (",[676,99564,99565],{},"src/main/resources/META-INF/native-image","). These files tell GraalVM about the dynamically reachable classes and methods, such as the ",[676,99568,99194],{},[676,99570,99197],{}," implementations.",[651,99573,99574,99575,99577,99578,99580,99581,99583,99584,99586,99587,99589],{},"However, as you might have noticed, the generated configuration files only include information about the ",[676,99576,99194],{}," class since that was the only class dynamically invoked during application runtime with the tracing agent enabled. To add the ",[676,99579,99197],{}," class, you can either run the application again with the ",[676,99582,99197],{}," argument and the tracing agent enabled, or you can manually edit the ",[676,99585,99561],{}," file and add a new entry for the ",[676,99588,99197],{}," class, like this:",[669,99591,99593],{"className":28439,"code":99592,"language":28441,"meta":674,"style":674},"{\n \"name\": \"dev.danvega.MeanMessage\",\n \"methods\": [\n {\n \"name\": \"printMessage\",\n \"parameterTypes\": []\n }\n ]\n}\n",[676,99594,99595,99599,99610,99617,99621,99631,99639,99643,99647],{"__ignoreMap":674},[679,99596,99597],{"class":681,"line":682},[679,99598,28448],{"class":693},[679,99600,99601,99603,99605,99608],{"class":681,"line":790},[679,99602,44594],{"class":931},[679,99604,4282],{"class":693},[679,99606,99607],{"class":689},"\"dev.danvega.MeanMessage\"",[679,99609,12083],{"class":693},[679,99611,99612,99615],{"class":681,"line":892},[679,99613,99614],{"class":931}," \"methods\"",[679,99616,28491],{"class":693},[679,99618,99619],{"class":681,"line":901},[679,99620,28496],{"class":693},[679,99622,99623,99625,99627,99629],{"class":681,"line":909},[679,99624,33445],{"class":931},[679,99626,4282],{"class":693},[679,99628,99483],{"class":689},[679,99630,12083],{"class":693},[679,99632,99633,99636],{"class":681,"line":918},[679,99634,99635],{"class":931}," \"parameterTypes\"",[679,99637,99638],{"class":693},": []\n",[679,99640,99641],{"class":681,"line":935},[679,99642,985],{"class":693},[679,99644,99645],{"class":681,"line":944},[679,99646,44050],{"class":693},[679,99648,99649],{"class":681,"line":959},[679,99650,996],{"class":693},[651,99652,99653,99654,23212,99656,99658],{},"After updating the configuration files, rebuild the native image with GraalVM. This time, you'll be able to successfully run the native image with both ",[676,99655,99194],{},[676,99657,99197],{}," arguments.",[4542,99660,9042],{"id":9041},[651,99662,99663],{},"In this article, we created a Java project with dynamic features and learned how to provide metadata to GraalVM's Native Image Builder using JSON configuration files generated by a tracing agent. This approach helps GraalVM understand the dynamic features of your JVM application and build native images accordingly. By following these steps, you can ensure that your dynamic JVM applications can be successfully compiled to native executables using GraalVM.",[786,99665,99666],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":99668},[99669,99670,99671,99672,99673],{"id":11141,"depth":790,"text":11142},{"id":99157,"depth":790,"text":99158},{"id":99168,"depth":790,"text":99169},{"id":99526,"depth":790,"text":99527},{"id":9041,"depth":790,"text":9042},{"slug":99675,"date":99676,"published":797,"author":798,"tags":99677,"cover":99679,"video":99680,"keywords":99681},"native-images-graalvm","2023-02-03T10:00:00.000Z",[4109,99678],"graalvm","./graalvm-dynamic-new.png","https://www.youtube.com/embed/Rk4zfvVvRks","Java, GraalVM, Native Images",{"title":345,"description":849},"blog/2023/02/03/native-images-graalvm","QA6VhY89_OaLq_hXBol_pyRKOtNndC9hUc0r9MJGr7s",{"id":99686,"title":342,"body":99687,"description":100816,"extension":793,"meta":100817,"navigation":797,"path":343,"seo":100826,"stem":100827,"__hash__":100828},"content/blog/2023/03/02/spring-shell-intro.md",{"type":648,"value":99688,"toc":100805},[99689,99698,99702,99732,99737,99741,99744,99761,99910,99929,99931,99940,99971,99975,99988,100082,100088,100112,100116,100125,100134,100164,100173,100248,100255,100426,100432,100620,100626,100646,100650,100662,100682,100685,100707,100712,100716,100726,100743,100752,100791,100794,100796,100799,100802],[651,99690,99691,99692,99697],{},"Today, we're going to talk about building command line applications using ",[812,99693,99696],{"href":99694,"rel":99695},"https://spring.io/projects/spring-shell",[816],"Spring Shell",". Spring Shell allows you to use the programming model, tools, frameworks, and languages that you're used to using every day to build command line applications. We're going to build a command line application called \"Dad Joke\" that fetches a random dad joke from a public API.",[4542,99699,99701],{"id":99700},"setup","Setup",[27665,99703,99704,99710,99713,99716,99729],{},[5332,99705,95283,99706,99709],{},[812,99707,51358],{"href":7115,"rel":99708},[816]," to create a new Spring Boot project.",[5332,99711,99712],{},"Choose the \"Java\" project type and \"Maven\" as the build tool.",[5332,99714,99715],{},"Use the latest stable release of Spring Boot (3.0.3 at the time of this recording).",[5332,99717,99718,99719],{},"Set the artifact name to \"DadJoke\" and use the following dependencies:\n",[5316,99720,99721,99723,99726],{},[5332,99722,99696],{},[5332,99724,99725],{},"GraalVM Native support (more on this later)",[5332,99727,99728],{},"Spring WebFlux (Reactive Web)",[5332,99730,99731],{},"Generate and download the .zip file, then open it up in your favorite IDE.",[651,99733,99734],{},[660,99735],{"alt":7117,"src":99736},"/images/blog/2023/03/02/start-spring-init.png",[4542,99738,99740],{"id":99739},"creating-a-hello-command","Creating a Hello Command",[651,99742,99743],{},"When building command line applications with Spring Shell, we write functionality in classes called \"commands\". Let's create a basic \"Hello Command\":",[27665,99745,99746,99752],{},[5332,99747,99748,99749,664],{},"Create a new package called ",[676,99750,99751],{},"commands",[5332,99753,99754,99755,89334,99758,99760],{},"Create a new class ",[676,99756,99757],{},"HelloCommand",[676,99759,99751],{}," package with the following annotations and methods:",[669,99762,99764],{"className":4107,"code":99763,"language":4109,"meta":674,"style":674},"import org.springframework.shell.standard.ShellComponent;\nimport org.springframework.shell.standard.ShellMethod;\n\n@ShellComponent\npublic class HelloCommand {\n @ShellMethod(key = \"hello\", value = \"Say hello\")\n public String hello() {\n return \"Hello, world!\";\n }\n\n @ShellMethod(key = \"goodbye\", value = \"Say goodbye\")\n public String goodbye() {\n return \"Goodbye!\";\n }\n}\n",[676,99765,99766,99773,99780,99784,99791,99802,99829,99839,99848,99852,99856,99882,99893,99902,99906],{"__ignoreMap":674},[679,99767,99768,99770],{"class":681,"line":682},[679,99769,1999],{"class":685},[679,99771,99772],{"class":693}," org.springframework.shell.standard.ShellComponent;\n",[679,99774,99775,99777],{"class":681,"line":790},[679,99776,1999],{"class":685},[679,99778,99779],{"class":693}," org.springframework.shell.standard.ShellMethod;\n",[679,99781,99782],{"class":681,"line":892},[679,99783,889],{"emptyLinePlaceholder":797},[679,99785,99786,99788],{"class":681,"line":901},[679,99787,4116],{"class":693},[679,99789,99790],{"class":685},"ShellComponent\n",[679,99792,99793,99795,99797,99800],{"class":681,"line":909},[679,99794,6073],{"class":685},[679,99796,4512],{"class":685},[679,99798,99799],{"class":880}," HelloCommand",[679,99801,884],{"class":693},[679,99803,99804,99806,99809,99811,99813,99815,99818,99820,99822,99824,99827],{"class":681,"line":918},[679,99805,6872],{"class":693},[679,99807,99808],{"class":685},"ShellMethod",[679,99810,745],{"class":693},[679,99812,56128],{"class":931},[679,99814,6883],{"class":685},[679,99816,99817],{"class":689}," \"hello\"",[679,99819,2797],{"class":693},[679,99821,19934],{"class":931},[679,99823,6883],{"class":685},[679,99825,99826],{"class":689}," \"Say hello\"",[679,99828,1339],{"class":693},[679,99830,99831,99833,99835,99837],{"class":681,"line":935},[679,99832,6089],{"class":685},[679,99834,9289],{"class":693},[679,99836,72963],{"class":880},[679,99838,2667],{"class":693},[679,99840,99841,99843,99846],{"class":681,"line":944},[679,99842,9444],{"class":685},[679,99844,99845],{"class":689}," \"Hello, world!\"",[679,99847,1186],{"class":693},[679,99849,99850],{"class":681,"line":959},[679,99851,985],{"class":693},[679,99853,99854],{"class":681,"line":964},[679,99855,889],{"emptyLinePlaceholder":797},[679,99857,99858,99860,99862,99864,99866,99868,99871,99873,99875,99877,99880],{"class":681,"line":977},[679,99859,6872],{"class":693},[679,99861,99808],{"class":685},[679,99863,745],{"class":693},[679,99865,56128],{"class":931},[679,99867,6883],{"class":685},[679,99869,99870],{"class":689}," \"goodbye\"",[679,99872,2797],{"class":693},[679,99874,19934],{"class":931},[679,99876,6883],{"class":685},[679,99878,99879],{"class":689}," \"Say goodbye\"",[679,99881,1339],{"class":693},[679,99883,99884,99886,99888,99891],{"class":681,"line":982},[679,99885,6089],{"class":685},[679,99887,9289],{"class":693},[679,99889,99890],{"class":880},"goodbye",[679,99892,2667],{"class":693},[679,99894,99895,99897,99900],{"class":681,"line":988},[679,99896,9444],{"class":685},[679,99898,99899],{"class":689}," \"Goodbye!\"",[679,99901,1186],{"class":693},[679,99903,99904],{"class":681,"line":993},[679,99905,985],{"class":693},[679,99907,99908],{"class":681,"line":2129},[679,99909,996],{"class":693},[651,99911,40060,99912,99915,99916,99919,99920,99922,99923,99925,99926,99928],{},[676,99913,99914],{},"@ShellComponent"," annotation indicates that this class contains shell methods (annotated with ",[676,99917,99918],{},"@ShellMethod","). The ",[676,99921,99918],{}," annotation specifies the command name and the description. The ",[676,99924,56128],{}," argument is the command you will use to execute the method and the ",[676,99927,19934],{}," is the description that will appear in the help documentation.",[4542,99930,98048],{"id":7309},[651,99932,99933,99934,23212,99936,99939],{},"When you run the Spring Boot application, it will launch the Spring Shell and you can type the command names and execute the ",[676,99935,73000],{},[676,99937,99938],{},"goodbye()"," methods directly from the command line.",[669,99941,99943],{"className":5851,"code":99942,"language":5853,"meta":674,"style":674},"$ hello\nHello, world!\n$ goodbye\nGoodbye!\n",[676,99944,99945,99952,99959,99966],{"__ignoreMap":674},[679,99946,99947,99949],{"class":681,"line":682},[679,99948,30246],{"class":880},[679,99950,99951],{"class":689}," hello\n",[679,99953,99954,99956],{"class":681,"line":790},[679,99955,74347],{"class":880},[679,99957,99958],{"class":689}," world!\n",[679,99960,99961,99963],{"class":681,"line":892},[679,99962,30246],{"class":880},[679,99964,99965],{"class":689}," goodbye\n",[679,99967,99968],{"class":681,"line":901},[679,99969,99970],{"class":880},"Goodbye!\n",[5909,99972,99974],{"id":99973},"customizing-shell-methods-with-shelloption","Customizing Shell Methods with ShellOption",[651,99976,99977,99978,99981,99982,99984,99985,99987],{},"You can use the ",[676,99979,99980],{},"@ShellOption"," annotation to customize the handling of shell method parameters. For instance, you can add an optional ",[676,99983,16334],{}," parameter to the ",[676,99986,73000],{}," method:",[669,99989,99991],{"className":4107,"code":99990,"language":4109,"meta":674,"style":674},"import org.springframework.shell.standard.ShellOption;\n\n@ShellMethod(key = \"hello\", value = \"Say hello\")\npublic String hello(@ShellOption(value = \"name\", defaultValue = \"world\") String name) {\n return \"Hello, \" + name + \"!\";\n}\n",[676,99992,99993,100000,100004,100028,100060,100078],{"__ignoreMap":674},[679,99994,99995,99997],{"class":681,"line":682},[679,99996,1999],{"class":685},[679,99998,99999],{"class":693}," org.springframework.shell.standard.ShellOption;\n",[679,100001,100002],{"class":681,"line":790},[679,100003,889],{"emptyLinePlaceholder":797},[679,100005,100006,100008,100010,100012,100014,100016,100018,100020,100022,100024,100026],{"class":681,"line":892},[679,100007,4116],{"class":693},[679,100009,99808],{"class":685},[679,100011,745],{"class":693},[679,100013,56128],{"class":931},[679,100015,6883],{"class":685},[679,100017,99817],{"class":689},[679,100019,2797],{"class":693},[679,100021,19934],{"class":931},[679,100023,6883],{"class":685},[679,100025,99826],{"class":689},[679,100027,1339],{"class":693},[679,100029,100030,100032,100034,100036,100038,100041,100043,100045,100047,100049,100051,100053,100055,100057],{"class":681,"line":901},[679,100031,6073],{"class":685},[679,100033,9289],{"class":693},[679,100035,72963],{"class":880},[679,100037,73246],{"class":693},[679,100039,100040],{"class":685},"ShellOption",[679,100042,745],{"class":693},[679,100044,19934],{"class":931},[679,100046,6883],{"class":685},[679,100048,77877],{"class":689},[679,100050,2797],{"class":693},[679,100052,73254],{"class":931},[679,100054,6883],{"class":685},[679,100056,73259],{"class":689},[679,100058,100059],{"class":693},") String name) {\n",[679,100061,100062,100064,100066,100068,100071,100073,100076],{"class":681,"line":909},[679,100063,21478],{"class":685},[679,100065,89405],{"class":689},[679,100067,3059],{"class":685},[679,100069,100070],{"class":693}," name ",[679,100072,3065],{"class":685},[679,100074,100075],{"class":689}," \"!\"",[679,100077,1186],{"class":693},[679,100079,100080],{"class":681,"line":918},[679,100081,996],{"class":693},[651,100083,100084,100085,100087],{},"Now, when you run the ",[676,100086,72963],{}," command with an argument, it will display a personalized greeting:",[669,100089,100091],{"className":5851,"code":100090,"language":5853,"meta":674,"style":674},"$ hello --name Dan\nHello, Dan!\n",[676,100092,100093,100105],{"__ignoreMap":674},[679,100094,100095,100097,100099,100102],{"class":681,"line":682},[679,100096,30246],{"class":880},[679,100098,73094],{"class":689},[679,100100,100101],{"class":931}," --name",[679,100103,100104],{"class":689}," Dan\n",[679,100106,100107,100109],{"class":681,"line":790},[679,100108,74347],{"class":880},[679,100110,100111],{"class":689}," Dan!\n",[4542,100113,100115],{"id":100114},"fetching-a-random-dad-joke","Fetching a Random Dad Joke",[651,100117,100118,100119,100124],{},"Let's build the main functionality of our command line application: fetching a random dad joke from the ",[812,100120,100123],{"href":100121,"rel":100122},"https://icanhazdadjoke.com/api",[816],"icanhazdadjoke.com"," public API.",[651,100126,100127,100128,100130,100131,2391],{},"Create a new package ",[676,100129,10048],{}," and a new record class ",[676,100132,100133],{},"DadJokeResponse",[669,100135,100137],{"className":4107,"code":100136,"language":4109,"meta":674,"style":674},"public record DadJokeResponse(String id, String joke, int status) {\n\n}\n",[676,100138,100139,100156,100160],{"__ignoreMap":674},[679,100140,100141,100143,100145,100148,100151,100153],{"class":681,"line":682},[679,100142,6073],{"class":685},[679,100144,86928],{"class":685},[679,100146,100147],{"class":880}," DadJokeResponse",[679,100149,100150],{"class":693},"(String id, String joke, ",[679,100152,1078],{"class":685},[679,100154,100155],{"class":693}," status) {\n",[679,100157,100158],{"class":681,"line":790},[679,100159,889],{"emptyLinePlaceholder":797},[679,100161,100162],{"class":681,"line":892},[679,100163,996],{"class":693},[651,100165,100127,100166,100169,100170,2391],{},[676,100167,100168],{},"client"," and a new interface ",[676,100171,100172],{},"DadJokeClient",[669,100174,100176],{"className":4107,"code":100175,"language":4109,"meta":674,"style":674},"package dev.danvega.dadjoke.client;\n\nimport dev.danvega.dadjoke.model.DadJokeResponse;\nimport org.springframework.web.service.annotation.GetExchange;\n\npublic interface DadJokeClient {\n\n @GetExchange(\"/\")\n DadJokeResponse random();\n}\n",[676,100177,100178,100185,100189,100196,100203,100207,100218,100222,100235,100244],{"__ignoreMap":674},[679,100179,100180,100182],{"class":681,"line":682},[679,100181,2543],{"class":685},[679,100183,100184],{"class":693}," dev.danvega.dadjoke.client;\n",[679,100186,100187],{"class":681,"line":790},[679,100188,889],{"emptyLinePlaceholder":797},[679,100190,100191,100193],{"class":681,"line":892},[679,100192,1999],{"class":685},[679,100194,100195],{"class":693}," dev.danvega.dadjoke.model.DadJokeResponse;\n",[679,100197,100198,100200],{"class":681,"line":901},[679,100199,1999],{"class":685},[679,100201,100202],{"class":693}," org.springframework.web.service.annotation.GetExchange;\n",[679,100204,100205],{"class":681,"line":909},[679,100206,889],{"emptyLinePlaceholder":797},[679,100208,100209,100211,100213,100216],{"class":681,"line":918},[679,100210,6073],{"class":685},[679,100212,6994],{"class":685},[679,100214,100215],{"class":880}," DadJokeClient",[679,100217,884],{"class":693},[679,100219,100220],{"class":681,"line":935},[679,100221,889],{"emptyLinePlaceholder":797},[679,100223,100224,100226,100229,100231,100233],{"class":681,"line":944},[679,100225,6872],{"class":693},[679,100227,100228],{"class":685},"GetExchange",[679,100230,745],{"class":693},[679,100232,10032],{"class":689},[679,100234,1339],{"class":693},[679,100236,100237,100240,100242],{"class":681,"line":959},[679,100238,100239],{"class":693}," DadJokeResponse ",[679,100241,5899],{"class":880},[679,100243,9317],{"class":693},[679,100245,100246],{"class":681,"line":964},[679,100247,996],{"class":693},[651,100249,100250,100251,100254],{},"Update the ",[676,100252,100253],{},"DadJokeCommands"," class to fetch a random dad joke and output it to the command line:",[669,100256,100258],{"className":4107,"code":100257,"language":4109,"meta":674,"style":674},"import org.springframework.shell.standard.ShellMethod;\nimport org.springframework.shell.standard.ShellComponent;\nimport org.springframework.beans.factory.annotation.Autowired;\n\n@ShellComponent\npublic class DadJokeCommands {\n\n private final DadJokeClient dadJokeClient;\n\n @Autowired\n public DadJokeCommands(DadJokeClient dadJokeClient) {\n this.dadJokeClient = dadJokeClient;\n }\n\n @ShellMethod(key = \"random\", value = \"Get a random dad joke\")\n public String getRandomDadJoke() {\n DadJokeResponse response = dadJokeClient.random();\n return response.joke();\n }\n}\n",[676,100259,100260,100266,100272,100278,100282,100288,100299,100303,100312,100316,100322,100336,100348,100352,100356,100382,100393,100407,100418,100422],{"__ignoreMap":674},[679,100261,100262,100264],{"class":681,"line":682},[679,100263,1999],{"class":685},[679,100265,99779],{"class":693},[679,100267,100268,100270],{"class":681,"line":790},[679,100269,1999],{"class":685},[679,100271,99772],{"class":693},[679,100273,100274,100276],{"class":681,"line":892},[679,100275,1999],{"class":685},[679,100277,73366],{"class":693},[679,100279,100280],{"class":681,"line":901},[679,100281,889],{"emptyLinePlaceholder":797},[679,100283,100284,100286],{"class":681,"line":909},[679,100285,4116],{"class":693},[679,100287,99790],{"class":685},[679,100289,100290,100292,100294,100297],{"class":681,"line":918},[679,100291,6073],{"class":685},[679,100293,4512],{"class":685},[679,100295,100296],{"class":880}," DadJokeCommands",[679,100298,884],{"class":693},[679,100300,100301],{"class":681,"line":935},[679,100302,889],{"emptyLinePlaceholder":797},[679,100304,100305,100307,100309],{"class":681,"line":944},[679,100306,9232],{"class":685},[679,100308,12768],{"class":685},[679,100310,100311],{"class":693}," DadJokeClient dadJokeClient;\n",[679,100313,100314],{"class":681,"line":959},[679,100315,889],{"emptyLinePlaceholder":797},[679,100317,100318,100320],{"class":681,"line":964},[679,100319,6872],{"class":693},[679,100321,9257],{"class":685},[679,100323,100324,100326,100328,100331,100334],{"class":681,"line":977},[679,100325,6089],{"class":685},[679,100327,100296],{"class":880},[679,100329,100330],{"class":693},"(DadJokeClient ",[679,100332,100333],{"class":2099},"dadJokeClient",[679,100335,4390],{"class":693},[679,100337,100338,100340,100343,100345],{"class":681,"line":982},[679,100339,7862],{"class":931},[679,100341,100342],{"class":693},".dadJokeClient ",[679,100344,686],{"class":685},[679,100346,100347],{"class":693}," dadJokeClient;\n",[679,100349,100350],{"class":681,"line":988},[679,100351,985],{"class":693},[679,100353,100354],{"class":681,"line":993},[679,100355,889],{"emptyLinePlaceholder":797},[679,100357,100358,100360,100362,100364,100366,100368,100371,100373,100375,100377,100380],{"class":681,"line":2129},[679,100359,6872],{"class":693},[679,100361,99808],{"class":685},[679,100363,745],{"class":693},[679,100365,56128],{"class":931},[679,100367,6883],{"class":685},[679,100369,100370],{"class":689}," \"random\"",[679,100372,2797],{"class":693},[679,100374,19934],{"class":931},[679,100376,6883],{"class":685},[679,100378,100379],{"class":689}," \"Get a random dad joke\"",[679,100381,1339],{"class":693},[679,100383,100384,100386,100388,100391],{"class":681,"line":2140},[679,100385,6089],{"class":685},[679,100387,9289],{"class":693},[679,100389,100390],{"class":880},"getRandomDadJoke",[679,100392,2667],{"class":693},[679,100394,100395,100398,100400,100403,100405],{"class":681,"line":2145},[679,100396,100397],{"class":693}," DadJokeResponse response ",[679,100399,686],{"class":685},[679,100401,100402],{"class":693}," dadJokeClient.",[679,100404,5899],{"class":880},[679,100406,9317],{"class":693},[679,100408,100409,100411,100413,100416],{"class":681,"line":2154},[679,100410,9444],{"class":685},[679,100412,46743],{"class":693},[679,100414,100415],{"class":880},"joke",[679,100417,9317],{"class":693},[679,100419,100420],{"class":681,"line":2159},[679,100421,985],{"class":693},[679,100423,100424],{"class":681,"line":2164},[679,100425,996],{"class":693},[651,100427,100428,100429],{},"Ask Spring to turn that interface into an implementation at runtime by using the ",[676,100430,100431],{},"HttpServiceProxyFactory",[669,100433,100435],{"className":4107,"code":100434,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n DadJokeClient dadJokeClient() {\n WebClient client = WebClient.builder()\n .baseUrl(\"https://icanhazdadjoke.com\")\n .defaultHeader(\"Accept\",\"application/json\")\n .build();\n\n HttpServiceProxyFactory factory = HttpServiceProxyFactory\n .builder(WebClientAdapter.forClient(client))\n .build();\n return factory.createClient(DadJokeClient.class);\n }\n}\n",[676,100436,100437,100443,100453,100457,100477,100485,100489,100493,100499,100508,100522,100536,100554,100562,100566,100576,100591,100599,100612,100616],{"__ignoreMap":674},[679,100438,100439,100441],{"class":681,"line":682},[679,100440,4116],{"class":693},[679,100442,6068],{"class":685},[679,100444,100445,100447,100449,100451],{"class":681,"line":790},[679,100446,6073],{"class":685},[679,100448,4512],{"class":685},[679,100450,16878],{"class":880},[679,100452,884],{"class":693},[679,100454,100455],{"class":681,"line":892},[679,100456,889],{"emptyLinePlaceholder":797},[679,100458,100459,100461,100463,100465,100467,100469,100471,100473,100475],{"class":681,"line":901},[679,100460,6089],{"class":685},[679,100462,6092],{"class":685},[679,100464,6095],{"class":685},[679,100466,6098],{"class":880},[679,100468,745],{"class":693},[679,100470,4758],{"class":685},[679,100472,16901],{"class":693},[679,100474,6108],{"class":2099},[679,100476,4390],{"class":693},[679,100478,100479,100481,100483],{"class":681,"line":909},[679,100480,6115],{"class":693},[679,100482,6118],{"class":880},[679,100484,16914],{"class":693},[679,100486,100487],{"class":681,"line":918},[679,100488,985],{"class":693},[679,100490,100491],{"class":681,"line":935},[679,100492,889],{"emptyLinePlaceholder":797},[679,100494,100495,100497],{"class":681,"line":944},[679,100496,6872],{"class":693},[679,100498,16929],{"class":685},[679,100500,100501,100504,100506],{"class":681,"line":959},[679,100502,100503],{"class":693}," DadJokeClient ",[679,100505,100333],{"class":880},[679,100507,2667],{"class":693},[679,100509,100510,100513,100515,100518,100520],{"class":681,"line":964},[679,100511,100512],{"class":693}," WebClient client ",[679,100514,686],{"class":685},[679,100516,100517],{"class":693}," WebClient.",[679,100519,90934],{"class":880},[679,100521,17545],{"class":693},[679,100523,100524,100526,100529,100531,100534],{"class":681,"line":977},[679,100525,73482],{"class":693},[679,100527,100528],{"class":880},"baseUrl",[679,100530,745],{"class":693},[679,100532,100533],{"class":689},"\"https://icanhazdadjoke.com\"",[679,100535,1339],{"class":693},[679,100537,100538,100540,100543,100545,100548,100550,100552],{"class":681,"line":982},[679,100539,73482],{"class":693},[679,100541,100542],{"class":880},"defaultHeader",[679,100544,745],{"class":693},[679,100546,100547],{"class":689},"\"Accept\"",[679,100549,1202],{"class":693},[679,100551,79927],{"class":689},[679,100553,1339],{"class":693},[679,100555,100556,100558,100560],{"class":681,"line":988},[679,100557,73482],{"class":693},[679,100559,23612],{"class":880},[679,100561,9317],{"class":693},[679,100563,100564],{"class":681,"line":993},[679,100565,889],{"emptyLinePlaceholder":797},[679,100567,100568,100571,100573],{"class":681,"line":2129},[679,100569,100570],{"class":693}," HttpServiceProxyFactory factory ",[679,100572,686],{"class":685},[679,100574,100575],{"class":693}," HttpServiceProxyFactory\n",[679,100577,100578,100580,100582,100585,100588],{"class":681,"line":2140},[679,100579,40148],{"class":693},[679,100581,90934],{"class":880},[679,100583,100584],{"class":693},"(WebClientAdapter.",[679,100586,100587],{"class":880},"forClient",[679,100589,100590],{"class":693},"(client))\n",[679,100592,100593,100595,100597],{"class":681,"line":2145},[679,100594,40148],{"class":693},[679,100596,23612],{"class":880},[679,100598,9317],{"class":693},[679,100600,100601,100603,100606,100609],{"class":681,"line":2154},[679,100602,9444],{"class":685},[679,100604,100605],{"class":693}," factory.",[679,100607,100608],{"class":880},"createClient",[679,100610,100611],{"class":693},"(DadJokeClient.class);\n",[679,100613,100614],{"class":681,"line":2159},[679,100615,985],{"class":693},[679,100617,100618],{"class":681,"line":2164},[679,100619,996],{"class":693},[651,100621,100622,100623,100625],{},"Run the Spring Boot application and test the ",[676,100624,5899],{}," command:",[669,100627,100629],{"className":5851,"code":100628,"language":5853,"meta":674,"style":674},"$ random\nWhy don't scientists trust atoms? Because they make up everything.\n",[676,100630,100631,100638],{"__ignoreMap":674},[679,100632,100633,100635],{"class":681,"line":682},[679,100634,30246],{"class":880},[679,100636,100637],{"class":689}," random\n",[679,100639,100640,100643],{"class":681,"line":790},[679,100641,100642],{"class":880},"Why",[679,100644,100645],{"class":689}," don't scientists trust atoms? Because they make up everything.\n",[4542,100647,100649],{"id":100648},"building-a-native-application","Building a Native Application",[651,100651,100652,100653,23212,100656,100658,100659,100661],{},"To make our command line application fast and portable, let's build a native application using GraalVM. Configure ",[676,100654,100655],{},"Logback",[676,100657,7077],{}," properties in ",[676,100660,16242],{}," to remove unnecessary logging and disable the Spring Boot banner:",[669,100663,100665],{"className":76589,"code":100664,"language":35538,"meta":674,"style":674},"logging.level.root=off\nspring.main.banner-mode=off\n",[676,100666,100667,100675],{"__ignoreMap":674},[679,100668,100669,100672],{"class":681,"line":682},[679,100670,100671],{"class":685},"logging.level.root",[679,100673,100674],{"class":693},"=off\n",[679,100676,100677,100680],{"class":681,"line":790},[679,100678,100679],{"class":685},"spring.main.banner-mode",[679,100681,100674],{"class":693},[651,100683,100684],{},"Build the native application using Maven:",[669,100686,100688],{"className":5851,"code":100687,"language":5853,"meta":674,"style":674},"$ ./mvnw -Pnative -DskipTests=true clean package\n",[676,100689,100690],{"__ignoreMap":674},[679,100691,100692,100694,100697,100700,100703,100705],{"class":681,"line":682},[679,100693,30246],{"class":880},[679,100695,100696],{"class":689}," ./mvnw",[679,100698,100699],{"class":931}," -Pnative",[679,100701,100702],{"class":931}," -DskipTests=true",[679,100704,81925],{"class":689},[679,100706,32496],{"class":689},[651,100708,100709,100710,81509],{},"The native application will be generated in the ",[676,100711,77995],{},[4542,100713,100715],{"id":100714},"using-the-native-application","Using the Native Application",[651,100717,100718,100719,51393,100722,100725],{},"Create an alias for the native application in your shell configuration file (e.g., ",[676,100720,100721],{},".bashrc",[676,100723,100724],{},".zshrc","):",[669,100727,100729],{"className":5851,"code":100728,"language":5853,"meta":674,"style":674},"alias dadjoke=\"path/to/your/target/DadJoke\"\n",[676,100730,100731],{"__ignoreMap":674},[679,100732,100733,100735,100738,100740],{"class":681,"line":682},[679,100734,42664],{"class":685},[679,100736,100737],{"class":693}," dadjoke",[679,100739,686],{"class":685},[679,100741,100742],{"class":689},"\"path/to/your/target/DadJoke\"\n",[27665,100744,100745],{},[5332,100746,100747,100748,100751],{},"Run the native application by typing ",[676,100749,100750],{},"dadjoke"," in your command line:",[669,100753,100755],{"className":5851,"code":100754,"language":5853,"meta":674,"style":674},"$ dadjoke\nDad Joke> random\nWhy don't some couples go to the gym? Because some relationships don't work out.\n",[676,100756,100757,100764,100778],{"__ignoreMap":674},[679,100758,100759,100761],{"class":681,"line":682},[679,100760,30246],{"class":880},[679,100762,100763],{"class":689}," dadjoke\n",[679,100765,100766,100769,100772,100774,100776],{"class":681,"line":790},[679,100767,100768],{"class":880},"Dad",[679,100770,100771],{"class":689}," Jok",[679,100773,9400],{"class":693},[679,100775,5860],{"class":685},[679,100777,100637],{"class":689},[679,100779,100780,100782,100785,100788],{"class":681,"line":892},[679,100781,100642],{"class":880},[679,100783,100784],{"class":689}," don't some couples go to the gym? Because some relationships don't",[679,100786,100787],{"class":689}," work",[679,100789,100790],{"class":689}," out.\n",[651,100792,100793],{},"Now you can enjoy random dad jokes anytime you want right from your command line!",[4542,100795,9042],{"id":9041},[651,100797,100798],{},"In this tutorial, we built a command line application using Spring Shell that fetches random dad jokes from a public API. We demonstrated how to create shell commands, fetch data from an API, and build a native application using GraalVM for fast startup times.",[651,100800,100801],{},"As you can see, Spring Shell is a powerful tool that allows you to build command line applications using the familiar Spring Boot environment. Give it a try for your next CLI project!",[786,100803,100804],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":100806},[100807,100808,100809,100812,100813,100814,100815],{"id":99700,"depth":790,"text":99701},{"id":99739,"depth":790,"text":99740},{"id":7309,"depth":790,"text":98048,"children":100810},[100811],{"id":99973,"depth":892,"text":99974},{"id":100114,"depth":790,"text":100115},{"id":100648,"depth":790,"text":100649},{"id":100714,"depth":790,"text":100715},{"id":9041,"depth":790,"text":9042},"In this introduction to Spring Shell, you will learn you how to build CLI applications in a familiar programming environment using Java + Spring.",{"slug":100818,"date":100819,"published":797,"author":798,"tags":100820,"cover":100823,"video":100824,"keywords":100825},"spring-shell-intro","2023-03-02T11:00:00.000Z",[100821,100822],"spring shell","Spring boot","./spring-shell-introduction.png","https://www.youtube.com/embed/8B0IjOIzicU","Spring Shell, Spring Boot, Spring CLI",{"title":342,"description":100816},"blog/2023/03/02/spring-shell-intro","1YwewgYGyLRYRBpPLhpZJSbbZjp7Hhh4ESkDGtCwnLo",{"id":100830,"title":339,"body":100831,"description":849,"extension":793,"meta":101327,"navigation":797,"path":340,"seo":101333,"stem":101334,"__hash__":101335},"content/blog/2023/03/09/spring-boot-crash-course.md",{"type":648,"value":100832,"toc":101305},[100833,100837,100840,100843,100846,100852,100854,100857,100866,100874,100879,100882,100886,100889,100893,100900,100903,100912,100924,100929,100932,100934,100937,100940,100951,100956,100960,100967,100970,100973,100976,100986,101007,101012,101015,101018,101025,101028,101033,101038,101041,101044,101047,101051,101054,101062,101065,101068,101072,101075,101078,101081,101087,101091,101098,101103,101107,101110,101113,101117,101120,101126,101128,101131,101133,101175,101177,101200,101202,101217,101219,101284,101286,101303],[4542,100834,100836],{"id":100835},"where-do-i-start","Where do I start?",[651,100838,100839],{},"I often receive the question, \"I want to learn Spring. Where do I start?\" This is usually asked by someone with Java experience who wants to learn Spring for either their current job or a job they are seeking. Another question I frequently hear is whether someone needs to learn Spring before learning Spring Boot. This article aims to answer these questions.",[651,100841,100842],{},"I decided to sit down and put together an outline for a course that would help anyone interested in learning Spring get up and running quickly. The first goal of this course is that it is accessible to anyone who wanted to learn Spring and with that I decided to make it available for free on my YouTube channel. Before we take a look at what you need to learn let’s talk about what you should know first.",[651,100844,100845],{},"Next, I wanted to ensure that the course was the appropriate length, being long enough to cover the material, but still something they can complete over a weekend. As a Udemy course instructor, I have access to statistics on students who start long courses versus those who finish them, and the numbers are rather alarming.",[651,100847,100848],{},[660,100849],{"alt":100850,"src":100851},"Where do I start learning Spring?","/images/blog/2023/03/09/photo-1501504905252-473c47e087f8.jpeg",[4542,100853,44199],{"id":44198},[651,100855,100856],{},"To get started with Spring, you should have some experience with the Java Programming Language. Although it is possible to build applications with Kotlin or Groovy, most developers find it helpful to learn Java first and then pick up other JVM languages later.",[651,100858,100859,100860,100865],{},"If you want to follow the course that I have created, you will need a basic understanding of Java. If you are new to Java, you can check out my ",[812,100861,100864],{"href":100862,"rel":100863},"https://www.danvega.dev/courses",[816],"free course"," on Getting Started with Java.",[651,100867,100868,100869,100873],{},"For the latest version of Spring, we have set Java 17 as the baseline. Therefore, before proceeding, make sure that you have Java 17 installed on your system. You will need an IDE or text editor that supports Java development. I am a big fan of ",[812,100870,100872],{"href":20753,"rel":100871},[816],"IntelliJ IDEA"," Ultimate which is what I use in this course but feel free to use whatever IDE or text editor you’re most productive in.",[651,100875,100876],{},[660,100877],{"alt":44199,"src":100878},"/images/blog/2023/03/09/prerequisites.png",[651,100880,100881],{},"Once you feel comfortable that you are the ideal student for this course and have everything installed we can start by covering the basics.",[4542,100883,100885],{"id":100884},"spring-framework-vs-spring-boot","Spring Framework vs Spring Boot",[651,100887,100888],{},"If you are new to Spring and have started researching, you have probably encountered the terms Spring Framework and Spring Boot. Do you need to learn both? Which one should you learn first? These are probably some of the questions running through your head, and I hope to help you answer them.",[5909,100890,100892],{"id":100891},"spring-framework","Spring Framework",[651,100894,100895,100899],{},[812,100896,100892],{"href":100897,"rel":100898},"https://spring.io/projects/spring-framework",[816]," is the foundation which all Spring applications are built upon. The great thing and probably what can be confusing for anyone new to the Spring Framework is that it can be used to build a wide range of applications.",[651,100901,100902],{},"Traditionally, Spring has been used for building web applications using Spring MVC, which enables the creation of fast, responsive applications that can connect to any data store. However, Spring introduced Spring Webflux to allow for asynchronous, non-blocking architecture, which can improve the performance and scalability of certain applications.",[651,100904,100905,100906,100911],{},"You can use Spring to break down monolithic architectures into smaller, independently deployable microservices. Building distributed architectures present a set of problems that need to be solved, but don't worry, ",[812,100907,100910],{"href":100908,"rel":100909},"https://spring.io/projects/spring-cloud",[816],"Spring Cloud"," has you covered.",[651,100913,100914,100915,100920,100921,664],{},"Spring can be used to build serverless workloads that provide ultimate flexibility by scaling on demand, as well as scaling to zero when there is no demand. You can use ",[812,100916,100919],{"href":100917,"rel":100918},"https://spring.io/projects/spring-batch",[816],"Spring Batch"," to build automated tasks for processing large sets of data. Finally, one of my new favorite ways to use Spring is to build command-line applications with ",[812,100922,99696],{"href":99694,"rel":100923},[816],[651,100925,100926],{},[660,100927],{"alt":100892,"src":100928},"/images/blog/2023/03/09/spring-framework.png",[651,100930,100931],{},"As you can see, Spring can be used to provide solutions for a variety of application needs. That's why when people ask me how to get started with Spring, I usually ask them what they want to use it for. By understanding the type of applications they're trying to build, they can narrow down their learning path to something much more manageable.",[5909,100933,7077],{"id":20742},[651,100935,100936],{},"Now that you have a good understanding of what the Spring Framework is, let's discuss where Spring Boot fits into the picture. Surprisingly, you don't actually need Spring Boot to build Spring applications. However, as you'll learn here, you would never want to build one without it.",[651,100938,100939],{},"Spring Boot provides a lot of features out of the box for building Spring applications but I want to focus on 3 of them in this article:",[5316,100941,100942,100945,100948],{},[5332,100943,100944],{},"Spring Boot Starters",[5332,100946,100947],{},"AutoConfiguration",[5332,100949,100950],{},"Production Ready",[651,100952,100953],{},[660,100954],{"alt":7077,"src":100955},"/images/blog/2023/03/09/spring-boot.png",[651,100957,100958],{},[2939,100959,100944],{},[651,100961,100962,100963,100966],{},"As I mentioned earlier, you don't necessarily need to use Spring Boot to create a Spring application. In fact, I ",[812,100964,84807],{"href":82636,"rel":100965},[816]," on how to do this, which outlines the process. While the example I provided is relatively simple, the process can become more complicated depending on the type of application you want to build. Nonetheless, I hope this tutorial helps you get started.",[651,100968,100969],{},"Spring Boot starters are dependency descriptors that simplify the process of including libraries in your project. They provide a quick and easy way to add common functionality to your application, without the need to manually configure each library. Spring Boot starters are available for a wide range of applications, including web, data, testing, and security.",[651,100971,100972],{},"For example, suppose you want to build a web application. First, you should determine which libraries you need to meet your requirements. For a typical web application, you need Spring Core, along with an embedded servlet container like Tomcat, Spring MVC, and a library like Jackson for serializing and deserializing objects to and from JSON.",[651,100974,100975],{},"Once you have identified the dependencies of your application, you need to determine their respective versions and whether they are compatible with each other. This task becomes increasingly difficult as the complexity of your application grows.",[651,100977,100978,100979,100982,100983,100985],{},"When creating a new project at ",[812,100980,77478],{"href":20748,"rel":100981},[816],", one of the options is to select your dependencies. If you choose Spring Web, you are indicating that you want to build a traditional Spring MVC application. By doing so, you will receive a single Spring Boot Starter called ",[676,100984,24082],{},", which includes all of the necessary dependencies with the correct versions.",[669,100987,100989],{"className":9101,"code":100988,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n\u003C/dependency>\n",[676,100990,100991,100995,100999,101003],{"__ignoreMap":674},[679,100992,100993],{"class":681,"line":682},[679,100994,9110],{},[679,100996,100997],{"class":681,"line":790},[679,100998,9115],{},[679,101000,101001],{"class":681,"line":892},[679,101002,9138],{},[679,101004,101005],{"class":681,"line":901},[679,101006,9125],{},[651,101008,101009],{},[2939,101010,101011],{},"Auto-Configuration",[651,101013,101014],{},"The next important feature of Spring Boot we need to discuss is auto-configuration. Spring Boot auto-configuration attempts to configure your application based on the dependencies that you have added.",[651,101016,101017],{},"If you create a Spring Web application, an embedded version of Tomcat is included. In a typical Java application, you would need to set up and configure Tomcat yourself. However, since Tomcat is on the classpath, Spring automatically configures it and provides sensible defaults such as setting the default port to 8080. You can always change the port through your own configuration, but Spring sets this default to get you up and running quickly.",[651,101019,101020,101021,101024],{},"Here's another example of how to include a database like ",[676,101022,101023],{},"H2"," in your project. In a typical Java application, you would need to set up and configure a data source, which can be a tedious process that slows down your development. With Spring, however, you can easily configure a data source without any extra hassle. If needed, you can always modify the configuration to fit your specific requirements.",[651,101026,101027],{},"Auto-configuration is a feature of Spring Boot that allows developers to configure their applications without writing any boilerplate code. Spring Boot combines a set of sensible default configurations, based on what dependencies you have added to your project, to help you get up and running quickly. This means that you can focus on writing business logic rather than worrying about configuration details.",[651,101029,101030],{},[2939,101031,101032],{},"Production Ready Features",[651,101034,101035],{},[660,101036],{"alt":101032,"src":101037},"/images/blog/2023/03/09/photo-1580106815433-a5b1d1d53d85.jpeg",[651,101039,101040],{},"Enabling developers to build the next generation of applications is not enough if you cannot get them into production. Spring provides a range of features that can assist you in building your applications for any environment and comprehending how they are performing in that environment.",[651,101042,101043],{},"The first step in moving to production is to build an artifact suitable for the environment you are moving to. The Spring Boot Maven and Gradle plugins allow you to build an executable JAR that can be run in any environment that supports Java. For environments that don't support Java, you can use Cloud Native Buildpacks to create an optimized Docker-compatible container image. Finally, for certain workloads, it might make sense to create a native executable.",[651,101045,101046],{},"Once you move your application to production you need to ensure that it is working as expected. You can use the Spring Actuator to gain observability into your application, allowing you to monitor and troubleshoot issues as they arise. Spring Boot provides a range of features to help you build and deploy your applications for any environment, whether it be on-premise or in the cloud.",[5909,101048,101050],{"id":101049},"which-one-do-i-learn-first","Which one do I learn first?",[651,101052,101053],{},"Now that we know the difference between Spring Framework and Spring Boot, we can answer the question of which one to learn first. When building your first Spring application, you will use Spring Boot. However, in the process of building it, you will inherently learn Spring Framework.",[651,101055,101056,101057,664],{},"The Spring Framework is based on the concepts of Inversion of Control (IoC) and Dependency Injection (DI). In this crash course, you will learn about these concepts and come to understand terms like Application Context, Containers, and beans. Once you have a basic understanding of these concepts and have built something with them, you can dive deeper into the ",[812,101058,101061],{"href":101059,"rel":101060},"https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core",[816],"Spring Reference documentation",[4542,101063,23034],{"id":101064},"spring-boot-crash-course",[651,101066,101067],{},"By now, I hope you have a good understanding of Spring. I believe that theory can only take you so far, and it's time to get your hands dirty. As a proponent of hands-on learning, I encourage you to build something real and tangible when learning something new.",[5909,101069,101071],{"id":101070},"what-are-we-building","What are we building",[651,101073,101074],{},"This is a REST API application that will communicate with a database. To focus our application, we decided to build out a content calendar since the content has many different properties and we could include a couple of Enums.",[651,101076,101077],{},"When a client calls the REST API, it initially talks to an in-memory collection of data. Once the application is fully functional, we will move the in-memory collection to a database. You will learn about connecting to, reading, and persisting data in Java and Spring.",[651,101079,101080],{},"Ultimately, it is important to get our applications into production. Therefore, I will guide you through deploying your application and database into production using a free hosting service.",[651,101082,101083],{},[660,101084],{"alt":101085,"src":101086},"What are we building?","/images/blog/2023/03/09/what-are-we-building.png",[5909,101088,101090],{"id":101089},"what-are-we-going-to-learn","What are we going to learn?",[651,101092,101093,101094,101097],{},"To start, you will build a new application from scratch using ",[812,101095,77478],{"href":20748,"rel":101096},[816],". Along the way, you will learn many core concepts in Spring, such as Bean and Dependency Injection. You will also learn how to build a REST API using Spring MVC, connect and configure a database in Spring, and use Spring Data Repositories to remove boilerplate code and focus on application requirements. Finally, you will prepare your application for production, build and deploy it.",[651,101099,101100],{},[660,101101],{"alt":101090,"src":101102},"/images/blog/2023/03/09/what-are-we-learning.png",[5909,101104,101106],{"id":101105},"spring-boot-crash-course-video","Spring Boot Crash Course Video",[651,101108,101109],{},"If you didn't receive the link to the video at the beginning of the article, you can find the course here. The video is almost 4 hours long, but there are chapters listed in the YouTube description if you need to jump around. I hope you enjoy it and would love to hear your feedback.",[5988,101111],{"id":101112},"UgX5lgv4uVM",[5909,101114,101116],{"id":101115},"github-repository","Github Repository",[651,101118,101119],{},"You can find all of the code for the Spring Boot Crash Course at the GitHub Repository listed below.",[651,101121,101122],{},[812,101123,101124],{"href":101124,"rel":101125},"https://github.com/danvega/content-calendar",[816],[4542,101127,21931],{"id":21930},[651,101129,101130],{},"This is a list of resources I recommend when learning Spring.",[5909,101132,40845],{"id":40844},[5316,101134,101135,101142,101149,101156,101162,101168],{},[5332,101136,101137],{},[812,101138,101141],{"href":101139,"rel":101140},"https://docs.spring.io/spring-framework/docs/current/reference/html/",[816],"Spring Framework Reference",[5332,101143,101144],{},[812,101145,101148],{"href":101146,"rel":101147},"https://docs.spring.io/spring-framework/docs/current/javadoc-api/",[816],"Spring Framework API",[5332,101150,101151],{},[812,101152,101155],{"href":101153,"rel":101154},"https://docs.spring.io/spring-boot/docs/current/reference/html/index.html",[816],"Spring Boot Reference",[5332,101157,101158],{},[812,101159,21161],{"href":101160,"rel":101161},"https://docs.spring.io/spring-boot/docs/current/api/",[816],[5332,101163,101164],{},[812,101165,101167],{"href":78015,"rel":101166},[816],"Spring Boot Guides",[5332,101169,101170],{},[812,101171,101174],{"href":101172,"rel":101173},"https://tanzu.vmware.com/developer/guides/",[816],"Tanzu Developer Center Guides",[5909,101176,6016],{"id":39340},[5316,101178,101179,101186,101193],{},[5332,101180,101181],{},[812,101182,101185],{"href":101183,"rel":101184},"https://amzn.to/3WOSutb",[816],"Spring Boot Up and Running - Mark Heckler",[5332,101187,101188],{},[812,101189,101192],{"href":101190,"rel":101191},"https://amzn.to/3CuCgxc",[816],"Learning Spring Boot 3.0 - Greg Turnquist",[5332,101194,101195],{},[812,101196,101199],{"href":101197,"rel":101198},"https://amzn.to/3ZcI3kx",[816],"Spring Boot in Action - Craig Walls",[5909,101201,26378],{"id":39439},[5316,101203,101204,101211],{},[5332,101205,101206],{},[812,101207,101210],{"href":101208,"rel":101209},"http://bootifulpodcast.fm/",[816],"Bootiful Podcast",[5332,101212,101213],{},[812,101214,97345],{"href":101215,"rel":101216},"https://www.springofficehours.io",[816],[5909,101218,15432],{"id":61224},[5316,101220,101221,101228,101235,101242,101249,101256,101263,101270,101277],{},[5332,101222,101223],{},[812,101224,101227],{"href":101225,"rel":101226},"https://www.youtube.com/@SpringBootLearning",[816],"Spring Boot Learning",[5332,101229,101230],{},[812,101231,101234],{"href":101232,"rel":101233},"https://www.youtube.com/playlist?list=PLgGXSWYM2FpPw8rV0tZoMiJYSCiLhPnOc",[816],"Spring Tips",[5332,101236,101237],{},[812,101238,101241],{"href":101239,"rel":101240},"https://www.youtube.com/@SpringSourceDev",[816],"Spring Developer",[5332,101243,101244],{},[812,101245,101248],{"href":101246,"rel":101247},"https://www.youtube.com/@amigoscode",[816],"Amigoscode",[5332,101250,101251],{},[812,101252,101255],{"href":101253,"rel":101254},"https://www.youtube.com/c/JavaBrainsChannel",[816],"Java Brains",[5332,101257,101258],{},[812,101259,101262],{"href":101260,"rel":101261},"https://www.youtube.com/@dashaun",[816],"DaShaun Carter",[5332,101264,101265],{},[812,101266,101269],{"href":101267,"rel":101268},"https://www.youtube.com/@coffeesoftware",[816],"Coffee and Software",[5332,101271,101272],{},[812,101273,101276],{"href":101274,"rel":101275},"https://www.youtube.com/@DailyCodeBuffer",[816],"Daily Code Buffer",[5332,101278,101279],{},[812,101280,101283],{"href":101281,"rel":101282},"https://www.youtube.com/@MarcoCodes",[816],"Marco Codes",[4542,101285,9042],{"id":9041},[651,101287,101288,101289,101292,101293,101298,101299,101302],{},"I had a lot of fun putting together this Spring Boot Crash Course. I hope it helps anyone interested in learning Spring, and that the video and this article help answer any questions you have about getting started. Please feel free to follow me on ",[812,101290,51474],{"href":51472,"rel":101291},[816],", Subscribe to my ",[812,101294,101297],{"href":101295,"rel":101296},"https://www.youtube.com/@danvega",[816],"YouTube channel",", or sign up for my free ",[812,101300,67441],{"href":82778,"rel":101301},[816]," to stay up to date with what I am working on.",[786,101304,24464],{},{"title":674,"searchDepth":790,"depth":790,"links":101306},[101307,101308,101309,101314,101320,101326],{"id":100835,"depth":790,"text":100836},{"id":44198,"depth":790,"text":44199},{"id":100884,"depth":790,"text":100885,"children":101310},[101311,101312,101313],{"id":100891,"depth":892,"text":100892},{"id":20742,"depth":892,"text":7077},{"id":101049,"depth":892,"text":101050},{"id":101064,"depth":790,"text":23034,"children":101315},[101316,101317,101318,101319],{"id":101070,"depth":892,"text":101071},{"id":101089,"depth":892,"text":101090},{"id":101105,"depth":892,"text":101106},{"id":101115,"depth":892,"text":101116},{"id":21930,"depth":790,"text":21931,"children":101321},[101322,101323,101324,101325],{"id":40844,"depth":892,"text":40845},{"id":39340,"depth":892,"text":6016},{"id":39439,"depth":892,"text":26378},{"id":61224,"depth":892,"text":15432},{"id":9041,"depth":790,"text":9042},{"slug":101064,"date":101328,"published":797,"author":798,"tags":101329,"cover":101330,"video":101331,"keywords":101332},"2023-03-09T11:30:00.000Z",[7077,100892],"./spring-boot-crash-course-new-thumbnail.png","https://www.youtube.com/embed/UgX5lgv4uVM","Spring Framework, Spring Boot, Learn Spring, Learn Spring Boot, Java, Spring Boot REST API,Spring Boot Crash Course, Spring Tutorial for Beginners",{"title":339,"description":849},"blog/2023/03/09/spring-boot-crash-course","JWuNJ4sSfUtAcyCNkigDPr5Kh9DemdKg3toJAFHrN7o",{"id":101337,"title":336,"body":101338,"description":849,"extension":793,"meta":102015,"navigation":797,"path":337,"seo":102022,"stem":102023,"__hash__":102024},"content/blog/2023/03/12/notion-api-file-expired.md",{"type":648,"value":101339,"toc":102010},[101340,101349,101358,101361,101364,101373,101379,101382,101388,101391,101395,101398,101404,101407,101528,101540,101551,101888,101894,101963,101970,101976,101980,101983,101989,101998,102000,102007],[651,101341,101342,101343,101348],{},"If you follow me, you know I'm a huge fan of Notion. I use it for everything in my personal and professional life. Since the release of the ",[812,101344,101347],{"href":101345,"rel":101346},"https://developers.notion.com/",[816],"Notion API",", I've been playing with it, but I've never built anything that I was going to launch publicly.",[651,101350,101351,101352,101357],{},"That's all about to change as I work on launching my wife's new blog, ",[812,101353,101356],{"href":101354,"rel":101355},"https://www.thisismymomoir.com/",[816],"This is my Momoir",". To prepare for that, I put together a simple blog called AI Blog. This is a blog that I'm writing in Notion, using Notion AI to generate blog posts.",[651,101359,101360],{},"I figured this would be a great playground for me to work out any issues I might come across before I work on launching her site. I'm building the website in Vue using Nuxt 3. There are a bunch of things I want to blog about or create YouTube videos on, but I wanted to start with this one.",[651,101362,101363],{},"After finishing the AI Blog, I wanted to deploy it on Netlify to see if there were any issues with publishing it out in the wild. I decided to statically generate the entire site because there will only be about 1-2 blog posts per week, making this a perfect scenario to prioritize performance over real-time updates.",[651,101365,101366,101367,101372],{},"When I first deployed the ",[812,101368,101371],{"href":101369,"rel":101370},"https://ai-notion-blog.netlify.app/",[816],"site"," everything looked as I expected it to:",[651,101374,101375],{},[660,101376],{"alt":101377,"src":101378},"API Blog","/images/blog/2023/03/12/api-blog-home.png",[651,101380,101381],{},"When I returned to the blog the following day, I noticed that all of the images were missing. This was puzzling, so I decided to investigate further. Using the Chrome Dev Tools, I quickly discovered that I was receiving a 403 Forbidden error on each of the images. What was particularly strange was that some of the blog posts had images that were displaying properly.",[651,101383,101384],{},[660,101385],{"alt":101386,"src":101387},"Images Missing","/images/blog/2023/03/12/images-missing.png",[651,101389,101390],{},"I decided to redeploy the website and sure enough, everything was working again. I came back a couple of hours later and the images were missing again.",[4542,101392,101394],{"id":101393},"notion-page-property-file-media-type","Notion Page Property File & Media Type",[651,101396,101397],{},"I decided to investigate the cause of this issue. It turns out that the cover images are a property of the page in Notion. If you take a look at the screenshot below, you can see that the page has various properties such as status, date created, and Image. The \"Image\" property is a file type, which I use to select the thumbnail image for this post.",[651,101399,101400],{},[660,101401],{"alt":101402,"src":101403},"Notion Page Properties","/images/blog/2023/03/12/notion-page-properties.png",[651,101405,101406],{},"When you retrieve the details for a page and request a specific property, you will receive some data that looks like this. Is there anything that stands out to you?",[669,101408,101410],{"className":28439,"code":101409,"language":28441,"meta":674,"style":674},"\"Image\": {\n \"id\": \"%5EmJf\",\n \"type\": \"files\",\n \"files\": [\n {\n \"name\": \"arseny-togulev-MECKPoKJYjM-unsplash.jpg\",\n \"type\": \"file\",\n \"file\": {\n \"url\": \"https://s3.us-west-2.amazonaws.com/secure.notion-static.com\n cee6e5ed-8b97-464a-934c-864830b60afa/arseny-togulev-MECKPoKJYjM-unsplash.jpg\",\n \"expiry_time\": \"2023-03-11T20:56:53.872Z\"\n }\n }\n ]\n},\n",[676,101411,101412,101419,101430,101442,101449,101454,101466,101478,101485,101495,101502,101512,101516,101520,101524],{"__ignoreMap":674},[679,101413,101414,101417],{"class":681,"line":682},[679,101415,101416],{"class":689},"\"Image\"",[679,101418,28468],{"class":693},[679,101420,101421,101423,101425,101428],{"class":681,"line":790},[679,101422,33276],{"class":931},[679,101424,4282],{"class":693},[679,101426,101427],{"class":689},"\"%5EmJf\"",[679,101429,12083],{"class":693},[679,101431,101432,101435,101437,101440],{"class":681,"line":892},[679,101433,101434],{"class":931}," \"type\"",[679,101436,4282],{"class":693},[679,101438,101439],{"class":689},"\"files\"",[679,101441,12083],{"class":693},[679,101443,101444,101447],{"class":681,"line":901},[679,101445,101446],{"class":931}," \"files\"",[679,101448,28491],{"class":693},[679,101450,101451],{"class":681,"line":909},[679,101452,101453],{"class":693}," {\n",[679,101455,101456,101459,101461,101464],{"class":681,"line":918},[679,101457,101458],{"class":931}," \"name\"",[679,101460,4282],{"class":693},[679,101462,101463],{"class":689},"\"arseny-togulev-MECKPoKJYjM-unsplash.jpg\"",[679,101465,12083],{"class":693},[679,101467,101468,101471,101473,101476],{"class":681,"line":935},[679,101469,101470],{"class":931}," \"type\"",[679,101472,4282],{"class":693},[679,101474,101475],{"class":689},"\"file\"",[679,101477,12083],{"class":693},[679,101479,101480,101483],{"class":681,"line":944},[679,101481,101482],{"class":931}," \"file\"",[679,101484,28468],{"class":693},[679,101486,101487,101490,101492],{"class":681,"line":959},[679,101488,101489],{"class":931}," \"url\"",[679,101491,4282],{"class":693},[679,101493,101494],{"class":689},"\"https://s3.us-west-2.amazonaws.com/secure.notion-static.com\n",[679,101496,101497,101500],{"class":681,"line":964},[679,101498,101499],{"class":689}," cee6e5ed-8b97-464a-934c-864830b60afa/arseny-togulev-MECKPoKJYjM-unsplash.jpg\"",[679,101501,12083],{"class":693},[679,101503,101504,101507,101509],{"class":681,"line":977},[679,101505,101506],{"class":931}," \"expiry_time\"",[679,101508,4282],{"class":693},[679,101510,101511],{"class":689},"\"2023-03-11T20:56:53.872Z\"\n",[679,101513,101514],{"class":681,"line":982},[679,101515,25517],{"class":693},[679,101517,101518],{"class":681,"line":988},[679,101519,1290],{"class":693},[679,101521,101522],{"class":681,"line":993},[679,101523,52918],{"class":693},[679,101525,101526],{"class":681,"line":2129},[679,101527,6481],{"class":693},[651,101529,101530,101531,101535,101536,101539],{},"That's right, each file and media type, including images returned by the ",[812,101532,101347],{"href":101533,"rel":101534},"https://developers.notion.com/reference/file-object",[816],", has an ",[676,101537,101538],{},"expiry_time"," field. This means that they will expire after 1 hour. If the cover image is going to expire after an hour and I'm statically generating the entire site, I only have one option, which is to download all of the cover images.",[651,101541,101542,101543,101546,101547,101550],{},"This raises some questions about build time performance when the site gets larger, but I can put that issue aside for now as I have bigger problems to deal with. To solve this, I wrote a function that will take the image URL and a destination to save it in. When working in Nuxt 3, you should save any public images to the ",[676,101544,101545],{},"/public"," directory. In this case, I am putting them into an ",[676,101548,101549],{},"images"," directory, but on a larger site, I could create a directory structure for each post.",[669,101552,101554],{"className":25132,"code":101553,"language":25134,"meta":674,"style":674},"database.results.forEach(post => {\n // cover: post.properties.Image.files[0]?.file.url,\n const imgName = post.properties.Image.files[0]?.name;\n\n posts.push({\n id: post.id,\n title: post.properties.Name.title[0].plain_text,\n slug: post.properties.Slug.rich_text[0].plain_text,\n description: post.properties.Excerpt.rich_text[0].plain_text,\n cover: post.properties.Image.files[0]?.name\n });\n // the cover image expires after 1 hour so we need to download it\n downloadImage(\n post.properties.Image.files[0]?.file.url,\n `./public/images/${imgName}`\n );\n});\n\nasync function downloadImage(url, filepath) {\n fetch(url)\n .then(\n res =>\n new Promise((resolve, reject) => {\n const dest = fs.createWriteStream(filepath);\n res.body.pipe(dest);\n res.body.on(\"end\", () =>\n resolve(filepath.split(\"/\")[3] + \" was downloaded successfully.\")\n );\n dest.on(\"error\", reject);\n })\n )\n .then(x => console.log(x));\n}\n",[676,101555,101556,101571,101576,101593,101597,101606,101611,101621,101630,101639,101649,101653,101658,101665,101675,101686,101690,101694,101698,101719,101727,101735,101743,101764,101782,101792,101809,101837,101842,101857,101861,101865,101884],{"__ignoreMap":674},[679,101557,101558,101561,101563,101565,101567,101569],{"class":681,"line":682},[679,101559,101560],{"class":693},"database.results.",[679,101562,46928],{"class":880},[679,101564,745],{"class":693},[679,101566,5100],{"class":2099},[679,101568,44760],{"class":685},[679,101570,884],{"class":693},[679,101572,101573],{"class":681,"line":790},[679,101574,101575],{"class":1400}," // cover: post.properties.Image.files[0]?.file.url,\n",[679,101577,101578,101580,101583,101585,101588,101590],{"class":681,"line":892},[679,101579,46903],{"class":685},[679,101581,101582],{"class":931}," imgName",[679,101584,6883],{"class":685},[679,101586,101587],{"class":693}," post.properties.Image.files[",[679,101589,1060],{"class":931},[679,101591,101592],{"class":693},"]?.name;\n",[679,101594,101595],{"class":681,"line":901},[679,101596,889],{"emptyLinePlaceholder":797},[679,101598,101599,101602,101604],{"class":681,"line":909},[679,101600,101601],{"class":693}," posts.",[679,101603,59269],{"class":880},[679,101605,21218],{"class":693},[679,101607,101608],{"class":681,"line":918},[679,101609,101610],{"class":693}," id: post.id,\n",[679,101612,101613,101616,101618],{"class":681,"line":935},[679,101614,101615],{"class":693}," title: post.properties.Name.title[",[679,101617,1060],{"class":931},[679,101619,101620],{"class":693},"].plain_text,\n",[679,101622,101623,101626,101628],{"class":681,"line":944},[679,101624,101625],{"class":693}," slug: post.properties.Slug.rich_text[",[679,101627,1060],{"class":931},[679,101629,101620],{"class":693},[679,101631,101632,101635,101637],{"class":681,"line":959},[679,101633,101634],{"class":693}," description: post.properties.Excerpt.rich_text[",[679,101636,1060],{"class":931},[679,101638,101620],{"class":693},[679,101640,101641,101644,101646],{"class":681,"line":964},[679,101642,101643],{"class":693}," cover: post.properties.Image.files[",[679,101645,1060],{"class":931},[679,101647,101648],{"class":693},"]?.name\n",[679,101650,101651],{"class":681,"line":977},[679,101652,56103],{"class":693},[679,101654,101655],{"class":681,"line":982},[679,101656,101657],{"class":1400}," // the cover image expires after 1 hour so we need to download it\n",[679,101659,101660,101663],{"class":681,"line":988},[679,101661,101662],{"class":880}," downloadImage",[679,101664,21337],{"class":693},[679,101666,101667,101670,101672],{"class":681,"line":993},[679,101668,101669],{"class":693}," post.properties.Image.files[",[679,101671,1060],{"class":931},[679,101673,101674],{"class":693},"]?.file.url,\n",[679,101676,101677,101680,101683],{"class":681,"line":2129},[679,101678,101679],{"class":689}," `./public/images/${",[679,101681,101682],{"class":693},"imgName",[679,101684,101685],{"class":689},"}`\n",[679,101687,101688],{"class":681,"line":2140},[679,101689,59436],{"class":693},[679,101691,101692],{"class":681,"line":2145},[679,101693,46842],{"class":693},[679,101695,101696],{"class":681,"line":2154},[679,101697,889],{"emptyLinePlaceholder":797},[679,101699,101700,101702,101704,101707,101709,101712,101714,101717],{"class":681,"line":2159},[679,101701,55212],{"class":685},[679,101703,21700],{"class":685},[679,101705,101706],{"class":880}," downloadImage",[679,101708,745],{"class":693},[679,101710,101711],{"class":2099},"url",[679,101713,2797],{"class":693},[679,101715,101716],{"class":2099},"filepath",[679,101718,4390],{"class":693},[679,101720,101721,101724],{"class":681,"line":2164},[679,101722,101723],{"class":880}," fetch",[679,101725,101726],{"class":693},"(url)\n",[679,101728,101729,101731,101733],{"class":681,"line":3134},[679,101730,40107],{"class":693},[679,101732,46728],{"class":880},[679,101734,21337],{"class":693},[679,101736,101737,101740],{"class":681,"line":3139},[679,101738,101739],{"class":2099}," res",[679,101741,101742],{"class":685}," =>\n",[679,101744,101745,101747,101749,101751,101753,101755,101758,101760,101762],{"class":681,"line":3144},[679,101746,98364],{"class":685},[679,101748,76955],{"class":931},[679,101750,51931],{"class":693},[679,101752,67264],{"class":2099},[679,101754,2797],{"class":693},[679,101756,101757],{"class":2099},"reject",[679,101759,2378],{"class":693},[679,101761,21350],{"class":685},[679,101763,884],{"class":693},[679,101765,101766,101769,101772,101774,101776,101779],{"class":681,"line":3149},[679,101767,101768],{"class":685}," const",[679,101770,101771],{"class":931}," dest",[679,101773,6883],{"class":685},[679,101775,67285],{"class":693},[679,101777,101778],{"class":880},"createWriteStream",[679,101780,101781],{"class":693},"(filepath);\n",[679,101783,101784,101787,101789],{"class":681,"line":3169},[679,101785,101786],{"class":693}," res.body.",[679,101788,27522],{"class":880},[679,101790,101791],{"class":693},"(dest);\n",[679,101793,101794,101796,101799,101801,101804,101806],{"class":681,"line":3185},[679,101795,101786],{"class":693},[679,101797,101798],{"class":880},"on",[679,101800,745],{"class":693},[679,101802,101803],{"class":689},"\"end\"",[679,101805,64887],{"class":693},[679,101807,101808],{"class":685},"=>\n",[679,101810,101811,101814,101817,101819,101821,101823,101826,101828,101830,101832,101835],{"class":681,"line":3194},[679,101812,101813],{"class":880}," resolve",[679,101815,101816],{"class":693},"(filepath.",[679,101818,55948],{"class":880},[679,101820,745],{"class":693},[679,101822,10032],{"class":689},[679,101824,101825],{"class":693},")[",[679,101827,66599],{"class":931},[679,101829,51966],{"class":693},[679,101831,3065],{"class":685},[679,101833,101834],{"class":689}," \" was downloaded successfully.\"",[679,101836,1339],{"class":693},[679,101838,101839],{"class":681,"line":3199},[679,101840,101841],{"class":693}," );\n",[679,101843,101844,101847,101849,101851,101854],{"class":681,"line":3212},[679,101845,101846],{"class":693}," dest.",[679,101848,101798],{"class":880},[679,101850,745],{"class":693},[679,101852,101853],{"class":689},"\"error\"",[679,101855,101856],{"class":693},", reject);\n",[679,101858,101859],{"class":681,"line":3217},[679,101860,79971],{"class":693},[679,101862,101863],{"class":681,"line":3222},[679,101864,35523],{"class":693},[679,101866,101867,101869,101871,101873,101875,101877,101879,101881],{"class":681,"line":3227},[679,101868,40107],{"class":693},[679,101870,46728],{"class":880},[679,101872,745],{"class":693},[679,101874,72458],{"class":2099},[679,101876,44760],{"class":685},[679,101878,21371],{"class":693},[679,101880,21374],{"class":880},[679,101882,101883],{"class":693},"(x));\n",[679,101885,101886],{"class":681,"line":3232},[679,101887,996],{"class":693},[651,101889,101890,101891,81509],{},"Back on the home page where I display the image I can now just reference the local ",[676,101892,101893],{},"/images",[669,101895,101897],{"className":4496,"code":101896,"language":4498,"meta":674,"style":674},"\u003CNuxtLink :to=\"`/blog/${post.slug}`\">\n \u003Cimg\n :src=\"`/images/${post.cover}`\"\n alt=\"Blog post image\"\n class=\"w-full h-48 object-cover\"\n />\n\u003C/NuxtLink>\n",[676,101898,101899,101915,101922,101931,101941,101951,101955],{"__ignoreMap":674},[679,101900,101901,101903,101906,101908,101910,101913],{"class":681,"line":682},[679,101902,4505],{"class":693},[679,101904,101905],{"class":6561},"NuxtLink",[679,101907,66402],{"class":880},[679,101909,686],{"class":693},[679,101911,101912],{"class":689},"\"`/blog/${post.slug}`\"",[679,101914,4519],{"class":693},[679,101916,101917,101919],{"class":681,"line":790},[679,101918,11738],{"class":693},[679,101920,101921],{"class":4508},"img\n",[679,101923,101924,101926,101928],{"class":681,"line":892},[679,101925,67635],{"class":880},[679,101927,686],{"class":693},[679,101929,101930],{"class":689},"\"`/images/${post.cover}`\"\n",[679,101932,101933,101936,101938],{"class":681,"line":901},[679,101934,101935],{"class":880}," alt",[679,101937,686],{"class":693},[679,101939,101940],{"class":689},"\"Blog post image\"\n",[679,101942,101943,101946,101948],{"class":681,"line":909},[679,101944,101945],{"class":880}," class",[679,101947,686],{"class":693},[679,101949,101950],{"class":689},"\"w-full h-48 object-cover\"\n",[679,101952,101953],{"class":681,"line":918},[679,101954,48231],{"class":693},[679,101956,101957,101959,101961],{"class":681,"line":935},[679,101958,4586],{"class":693},[679,101960,101905],{"class":6561},[679,101962,4519],{"class":693},[651,101964,101965,101966,101969],{},"I tried writing the download image function using Nuxt’s ",[676,101967,101968],{},"$fetch"," but I didn’t have much luck. I decided to take my issues to Twitter and ask my friends there. I don’t have any answers yet but If I get one I will be sure to update this blog post.",[651,101971,101972],{},[812,101973,101974],{"href":101974,"rel":101975},"https://twitter.com/therealdanvega/status/1634977252931960832",[816],[4542,101977,101979],{"id":101978},"images-expiring-update","Images Expiring Update",[651,101981,101982],{},"I realized that after I deployed a test version of my Wife's new blog she was having the same problem on all of her post pages. This is because she was using her own images that she uploaded to Notion while I was using Unsplash Images on the AI Blog.",[651,101984,101985,101986,101988],{},"This now presents the same problem in each of her posts. I can use the same technique to download each of the images and save them to the ",[676,101987,6073],{}," directory. I'm not sure I want to do this for every single image and I sure don't want to do this all at build time. My current thought process is that I might look to see if this image exists first and if it doesn't then I will save it. This of course presents a problem if there is a newer version of the same file but I don't think I am going to run into that problem right now.",[651,101990,101991,101992,101997],{},"I’m also interested in using the ",[812,101993,101996],{"href":101994,"rel":101995},"https://image.nuxtjs.org/",[816],"Nuxt Image"," module which would allow me to use a single image and translate it to different versions for different screen sizes. The Nuxt Image module has many different options when it comes to providers but this is something I haven’t done before. My next question is how do I take these images and programmatically push them to one of these providers? My small task of using Notion as a CMS has quickly created some interesting problems.",[4542,101999,9042],{"id":9041},[651,102001,102002,102003,664],{},"I’m having a lot of fun writing a blog that uses Notion as the CMS. It certainly hasn’t been easy but I am learning a lot and that always makes me happy. If you’re interested in the code for the AI Blog you can check it out ",[812,102004,18263],{"href":102005,"rel":102006},"https://github.com/danvega/ai-blog",[816],[786,102008,102009],{},"html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":102011},[102012,102013,102014],{"id":101393,"depth":790,"text":101394},{"id":101978,"depth":790,"text":101979},{"id":9041,"depth":790,"text":9042},{"slug":102016,"date":102017,"updatedOn":102018,"published":797,"author":798,"tags":102019,"cover":102020,"keywords":102021},"notion-api-file-expired","2023-03-12T14:30:00.000Z","2023-03-14T14:30:00.000Z",[25134,56660],"./notion-cover-image.png","Notion, Notion API",{"title":336,"description":849},"blog/2023/03/12/notion-api-file-expired","SiHxEc01Nk24Lh6ZthDpguZdce_h3Vb-lxxZKQtS0g8",{"id":102026,"title":333,"body":102027,"description":849,"extension":793,"meta":102857,"navigation":797,"path":334,"seo":102864,"stem":102865,"__hash__":102866},"content/blog/2023/03/15/spring-security-lambda-dsl.md",{"type":648,"value":102028,"toc":102849},[102029,102032,102035,102042,102065,102070,102073,102077,102080,102094,102097,102163,102241,102245,102264,102442,102445,102456,102463,102467,102480,102612,102632,102736,102739,102825,102829,102832,102838,102841,102843,102846],[651,102030,102031],{},"Hello, friends! Today, we'll discuss two different ways to configure Spring Security in your applications and create a basic security configuration in a Spring Boot project using each approach. I will walk you through each method, explaining the pros and cons of each, so that you can better understand the different configurations you might come across in your projects.",[4542,102033,102034],{"id":94280},"Setting Up The Project",[651,102036,102037,102038,102041],{},"To get started, we'll create a new Spring Boot project at ",[812,102039,77478],{"href":7115,"rel":102040},[816],", using the following settings:",[5316,102043,102044,102047,102050,102053,102056,102059,102062],{},[5332,102045,102046],{},"Project Type: Maven Project",[5332,102048,102049],{},"Language: Java",[5332,102051,102052],{},"Packaging: Jar",[5332,102054,102055],{},"Java Version: 17",[5332,102057,102058],{},"Group: dev.danvega",[5332,102060,102061],{},"Artifact: hello-security",[5332,102063,102064],{},"Dependencies: Web & Security",[651,102066,102067],{},[660,102068],{"alt":94303,"src":102069},"/images/blog/2023/03/15/spring-init.png",[651,102071,102072],{},"Once our project is generated, we'll download the ZIP file and open it in IntelliJ IDEA or whatever IDE you’re most productive in.",[4542,102074,102076],{"id":102075},"creating-controllers","Creating Controllers",[651,102078,102079],{},"Next, we'll create two controllers so that we can demonstrate our security configurations:",[27665,102081,102082,102088],{},[5332,102083,102084,102087],{},[2939,102085,102086],{},"HomeController:"," This controller will handle requests to the root (\"/\") endpoint, returning a simple \"Hello, Spring Security\" message.",[5332,102089,102090,102093],{},[2939,102091,102092],{},"PostController:"," This controller will handle requests to the \"/api/posts\" endpoint, returning a string representing all available posts.",[651,102095,102096],{},"With our controllers in place, we can start exploring the different Spring Security configurations.",[669,102098,102100],{"className":4107,"code":102099,"language":4109,"meta":674,"style":674},"@RestController\npublic class HomeController {\n\n @GetMapping(\"/\")\n String home() {\n return \"Hello, Spring Security!\";\n }\n\n}\n",[676,102101,102102,102108,102118,102122,102134,102142,102151,102155,102159],{"__ignoreMap":674},[679,102103,102104,102106],{"class":681,"line":682},[679,102105,4116],{"class":693},[679,102107,9212],{"class":685},[679,102109,102110,102112,102114,102116],{"class":681,"line":790},[679,102111,6073],{"class":685},[679,102113,4512],{"class":685},[679,102115,18716],{"class":880},[679,102117,884],{"class":693},[679,102119,102120],{"class":681,"line":892},[679,102121,889],{"emptyLinePlaceholder":797},[679,102123,102124,102126,102128,102130,102132],{"class":681,"line":901},[679,102125,6872],{"class":693},[679,102127,20852],{"class":685},[679,102129,745],{"class":693},[679,102131,10032],{"class":689},[679,102133,1339],{"class":693},[679,102135,102136,102138,102140],{"class":681,"line":909},[679,102137,20195],{"class":693},[679,102139,12642],{"class":880},[679,102141,2667],{"class":693},[679,102143,102144,102146,102149],{"class":681,"line":918},[679,102145,9444],{"class":685},[679,102147,102148],{"class":689}," \"Hello, Spring Security!\"",[679,102150,1186],{"class":693},[679,102152,102153],{"class":681,"line":935},[679,102154,985],{"class":693},[679,102156,102157],{"class":681,"line":944},[679,102158,889],{"emptyLinePlaceholder":797},[679,102160,102161],{"class":681,"line":959},[679,102162,996],{"class":693},[669,102164,102165],{"className":4107,"code":23265,"language":4109,"meta":674,"style":674},[676,102166,102167,102173,102185,102195,102199,102207,102211,102223,102233,102237],{"__ignoreMap":674},[679,102168,102169,102171],{"class":681,"line":682},[679,102170,4116],{"class":693},[679,102172,9212],{"class":685},[679,102174,102175,102177,102179,102181,102183],{"class":681,"line":790},[679,102176,4116],{"class":693},[679,102178,9275],{"class":685},[679,102180,745],{"class":693},[679,102182,23141],{"class":689},[679,102184,1339],{"class":693},[679,102186,102187,102189,102191,102193],{"class":681,"line":892},[679,102188,6073],{"class":685},[679,102190,4512],{"class":685},[679,102192,23152],{"class":880},[679,102194,884],{"class":693},[679,102196,102197],{"class":681,"line":901},[679,102198,889],{"emptyLinePlaceholder":797},[679,102200,102201,102203,102205],{"class":681,"line":909},[679,102202,9232],{"class":685},[679,102204,12768],{"class":685},[679,102206,9977],{"class":693},[679,102208,102209],{"class":681,"line":918},[679,102210,889],{"emptyLinePlaceholder":797},[679,102212,102213,102215,102217,102219,102221],{"class":681,"line":935},[679,102214,6089],{"class":685},[679,102216,23152],{"class":880},[679,102218,9996],{"class":693},[679,102220,9999],{"class":2099},[679,102222,4390],{"class":693},[679,102224,102225,102227,102229,102231],{"class":681,"line":944},[679,102226,7862],{"class":931},[679,102228,10008],{"class":693},[679,102230,686],{"class":685},[679,102232,10013],{"class":693},[679,102234,102235],{"class":681,"line":959},[679,102236,985],{"class":693},[679,102238,102239],{"class":681,"line":964},[679,102240,996],{"class":693},[4542,102242,102244],{"id":102243},"chaining-style-configuration","Chaining Style Configuration",[651,102246,102247,102248,102251,102252,102254,102255,102257,102258,102260,102261,102263],{},"First, let's look at the ",[7300,102249,102250],{},"chaining style"," configuration, which is an older approach but still very valid. We'll create a new ",[676,102253,6139],{}," class called ",[676,102256,89455],{},", and add a ",[676,102259,95305],{}," bean definition to it. We'll use the ",[676,102262,95396],{}," object to define our security rules, as shown below:",[669,102265,102267],{"className":4107,"code":102266,"language":4109,"meta":674,"style":674},"@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n @Bean\n public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http.authorizeRequests()\n .requestMatchers(PathRequest.to(\"/\")).permitAll()\n .requestMatchers(PathRequest.to(\"/api/posts\")).hasRole(\"ADMIN\")\n .anyRequest().authenticated()\n .and()\n .csrf().disable()\n .httpBasic()\n .and()\n .build();\n }\n\n}\n\n",[676,102268,102269,102275,102281,102291,102295,102301,102319,102329,102349,102374,102386,102394,102406,102414,102422,102430,102434,102438],{"__ignoreMap":674},[679,102270,102271,102273],{"class":681,"line":682},[679,102272,4116],{"class":693},[679,102274,6212],{"class":685},[679,102276,102277,102279],{"class":681,"line":790},[679,102278,4116],{"class":693},[679,102280,89474],{"class":685},[679,102282,102283,102285,102287,102289],{"class":681,"line":892},[679,102284,6073],{"class":685},[679,102286,4512],{"class":685},[679,102288,89483],{"class":880},[679,102290,884],{"class":693},[679,102292,102293],{"class":681,"line":901},[679,102294,889],{"emptyLinePlaceholder":797},[679,102296,102297,102299],{"class":681,"line":909},[679,102298,6872],{"class":693},[679,102300,16929],{"class":685},[679,102302,102303,102305,102307,102309,102311,102313,102315,102317],{"class":681,"line":918},[679,102304,6089],{"class":685},[679,102306,89502],{"class":693},[679,102308,89505],{"class":880},[679,102310,89508],{"class":693},[679,102312,89511],{"class":2099},[679,102314,2378],{"class":693},[679,102316,9580],{"class":685},[679,102318,10466],{"class":693},[679,102320,102321,102323,102325,102327],{"class":681,"line":935},[679,102322,9444],{"class":685},[679,102324,92844],{"class":693},[679,102326,40110],{"class":880},[679,102328,17545],{"class":693},[679,102330,102331,102333,102335,102337,102339,102341,102343,102345,102347],{"class":681,"line":944},[679,102332,73482],{"class":693},[679,102334,40124],{"class":880},[679,102336,95478],{"class":693},[679,102338,40130],{"class":880},[679,102340,745],{"class":693},[679,102342,10032],{"class":689},[679,102344,65060],{"class":693},[679,102346,40151],{"class":880},[679,102348,17545],{"class":693},[679,102350,102351,102353,102355,102357,102359,102361,102363,102365,102367,102369,102372],{"class":681,"line":959},[679,102352,73482],{"class":693},[679,102354,40124],{"class":880},[679,102356,95478],{"class":693},[679,102358,40130],{"class":880},[679,102360,745],{"class":693},[679,102362,23141],{"class":689},[679,102364,65060],{"class":693},[679,102366,40179],{"class":880},[679,102368,745],{"class":693},[679,102370,102371],{"class":689},"\"ADMIN\"",[679,102373,1339],{"class":693},[679,102375,102376,102378,102380,102382,102384],{"class":681,"line":964},[679,102377,73482],{"class":693},[679,102379,89569],{"class":880},[679,102381,10541],{"class":693},[679,102383,89574],{"class":880},[679,102385,17545],{"class":693},[679,102387,102388,102390,102392],{"class":681,"line":977},[679,102389,40148],{"class":693},[679,102391,40257],{"class":880},[679,102393,17545],{"class":693},[679,102395,102396,102398,102400,102402,102404],{"class":681,"line":982},[679,102397,73482],{"class":693},[679,102399,89531],{"class":880},[679,102401,10541],{"class":693},[679,102403,89542],{"class":880},[679,102405,17545],{"class":693},[679,102407,102408,102410,102412],{"class":681,"line":988},[679,102409,73482],{"class":693},[679,102411,89615],{"class":880},[679,102413,17545],{"class":693},[679,102415,102416,102418,102420],{"class":681,"line":993},[679,102417,40148],{"class":693},[679,102419,40257],{"class":880},[679,102421,17545],{"class":693},[679,102423,102424,102426,102428],{"class":681,"line":2129},[679,102425,73482],{"class":693},[679,102427,23612],{"class":880},[679,102429,9317],{"class":693},[679,102431,102432],{"class":681,"line":2140},[679,102433,985],{"class":693},[679,102435,102436],{"class":681,"line":2145},[679,102437,889],{"emptyLinePlaceholder":797},[679,102439,102440],{"class":681,"line":2154},[679,102441,996],{"class":693},[651,102443,102444],{},"This configuration sets the following rules:",[27665,102446,102447,102450,102453],{},[5332,102448,102449],{},"Allow anyone to access the root (\"/\") endpoint.",[5332,102451,102452],{},"Require the \"ADMIN\" role to access the \"/api/posts\" endpoint.",[5332,102454,102455],{},"Require authentication for any other request.",[651,102457,102458,102459,102462],{},"As you can see, this approach involves chaining each configuration option using the ",[676,102460,102461],{},"and()"," method. This can become quite lengthy in more complex configurations.",[4542,102464,102466],{"id":102465},"lambda-dsl-configuration","Lambda DSL Configuration",[651,102468,102469,102470,102473,102474,102476,102477,102479],{},"Now, let's explore the ",[7300,102471,102472],{},"Lambda DSL"," configuration, which offers a cleaner and more concise approach. We'll create another ",[676,102475,95305],{}," bean definition in our ",[676,102478,89455],{}," class, implementing the same security rules but using lambda expressions:",[669,102481,102483],{"className":4107,"code":102482,"language":4109,"meta":674,"style":674},"@Bean\npublic SecurityFilterChain securityFilterChain(HttpSecurity http) {\n return http.authorizeRequests(auth -> auth\n .requestMatchers(PathRequest.to(\"/\")).permitAll()\n .requestMatchers(PathRequest.to(\"/api/posts\")).hasRole(\"ADMIN\")\n .anyRequest().authenticated())\n .csrf(csrf -> csrf.disable())\n .httpBasic(withDefaults())\n .build();\n}\n\n",[676,102484,102485,102491,102502,102516,102536,102560,102572,102588,102600,102608],{"__ignoreMap":674},[679,102486,102487,102489],{"class":681,"line":682},[679,102488,4116],{"class":693},[679,102490,16929],{"class":685},[679,102492,102493,102495,102497,102499],{"class":681,"line":790},[679,102494,6073],{"class":685},[679,102496,89502],{"class":693},[679,102498,89505],{"class":880},[679,102500,102501],{"class":693},"(HttpSecurity http) {\n",[679,102503,102504,102506,102508,102510,102512,102514],{"class":681,"line":892},[679,102505,21478],{"class":685},[679,102507,92844],{"class":693},[679,102509,40110],{"class":880},[679,102511,95465],{"class":693},[679,102513,16955],{"class":685},[679,102515,89561],{"class":693},[679,102517,102518,102520,102522,102524,102526,102528,102530,102532,102534],{"class":681,"line":901},[679,102519,40148],{"class":693},[679,102521,40124],{"class":880},[679,102523,95478],{"class":693},[679,102525,40130],{"class":880},[679,102527,745],{"class":693},[679,102529,10032],{"class":689},[679,102531,65060],{"class":693},[679,102533,40151],{"class":880},[679,102535,17545],{"class":693},[679,102537,102538,102540,102542,102544,102546,102548,102550,102552,102554,102556,102558],{"class":681,"line":909},[679,102539,40148],{"class":693},[679,102541,40124],{"class":880},[679,102543,95478],{"class":693},[679,102545,40130],{"class":880},[679,102547,745],{"class":693},[679,102549,23141],{"class":689},[679,102551,65060],{"class":693},[679,102553,40179],{"class":880},[679,102555,745],{"class":693},[679,102557,102371],{"class":689},[679,102559,1339],{"class":693},[679,102561,102562,102564,102566,102568,102570],{"class":681,"line":918},[679,102563,40148],{"class":693},[679,102565,89569],{"class":880},[679,102567,10541],{"class":693},[679,102569,89574],{"class":880},[679,102571,40172],{"class":693},[679,102573,102574,102576,102578,102580,102582,102584,102586],{"class":681,"line":935},[679,102575,21331],{"class":693},[679,102577,89531],{"class":880},[679,102579,89534],{"class":693},[679,102581,16955],{"class":685},[679,102583,89539],{"class":693},[679,102585,89542],{"class":880},[679,102587,40172],{"class":693},[679,102589,102590,102592,102594,102596,102598],{"class":681,"line":944},[679,102591,21331],{"class":693},[679,102593,89615],{"class":880},[679,102595,745],{"class":693},[679,102597,89621],{"class":880},[679,102599,40172],{"class":693},[679,102601,102602,102604,102606],{"class":681,"line":959},[679,102603,21331],{"class":693},[679,102605,23612],{"class":880},[679,102607,9317],{"class":693},[679,102609,102610],{"class":681,"line":964},[679,102611,996],{"class":693},[651,102613,102614,102615,102617,102618,102620,102621,102625,102626,102628,102629,664],{},"As you can see, the Lambda DSL configuration is shorter and easier to read. It automatically returns the ",[676,102616,95396],{}," instance, so there's no need to chain options using the ",[676,102619,102461],{}," method. If you’re curious how this works you can take a look at the ",[812,102622,95396],{"href":102623,"rel":102624},"https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/builders/HttpSecurity.html",[816]," class which exposes a method named ",[676,102627,95392],{}," which accepts an argument of type ",[676,102630,102631],{},"Customizer\u003CT>",[669,102633,102635],{"className":4107,"code":102634,"language":4109,"meta":674,"style":674},"public HttpSecurity authorizeHttpRequests(Customizer\u003CAuthorizeHttpRequestsConfigurer\u003CHttpSecurity>.\nAuthorizationManagerRequestMatcherRegistry> authorizeHttpRequestsCustomizer) throws Exception {\n ApplicationContext context = this.getContext();\n authorizeHttpRequestsCustomizer.customize(((AuthorizeHttpRequestsConfigurer)\n this.getOrApply(new AuthorizeHttpRequestsConfigurer(context))).getRegistry());\n return this;\n}\n",[676,102636,102637,102663,102673,102689,102700,102724,102732],{"__ignoreMap":674},[679,102638,102639,102641,102644,102646,102649,102651,102654,102656,102658,102660],{"class":681,"line":682},[679,102640,6073],{"class":685},[679,102642,102643],{"class":693}," HttpSecurity ",[679,102645,95392],{"class":880},[679,102647,102648],{"class":693},"(Customizer",[679,102650,4505],{"class":685},[679,102652,102653],{"class":693},"AuthorizeHttpRequestsConfigurer",[679,102655,4505],{"class":685},[679,102657,95396],{"class":693},[679,102659,5860],{"class":685},[679,102661,102662],{"class":693},".\n",[679,102664,102665,102668,102670],{"class":681,"line":790},[679,102666,102667],{"class":693},"AuthorizationManagerRequestMatcherRegistry",[679,102669,5860],{"class":685},[679,102671,102672],{"class":693}," authorizeHttpRequestsCustomizer) throws Exception {\n",[679,102674,102675,102678,102680,102682,102684,102687],{"class":681,"line":892},[679,102676,102677],{"class":693}," ApplicationContext context ",[679,102679,686],{"class":685},[679,102681,21353],{"class":931},[679,102683,664],{"class":693},[679,102685,102686],{"class":880},"getContext",[679,102688,9317],{"class":693},[679,102690,102691,102694,102697],{"class":681,"line":901},[679,102692,102693],{"class":693}," authorizeHttpRequestsCustomizer.",[679,102695,102696],{"class":880},"customize",[679,102698,102699],{"class":693},"(((AuthorizeHttpRequestsConfigurer)\n",[679,102701,102702,102704,102706,102709,102711,102713,102716,102719,102722],{"class":681,"line":909},[679,102703,27825],{"class":931},[679,102705,664],{"class":693},[679,102707,102708],{"class":880},"getOrApply",[679,102710,745],{"class":693},[679,102712,8930],{"class":685},[679,102714,102715],{"class":880}," AuthorizeHttpRequestsConfigurer",[679,102717,102718],{"class":693},"(context))).",[679,102720,102721],{"class":880},"getRegistry",[679,102723,9431],{"class":693},[679,102725,102726,102728,102730],{"class":681,"line":918},[679,102727,21478],{"class":685},[679,102729,21353],{"class":931},[679,102731,1186],{"class":693},[679,102733,102734],{"class":681,"line":935},[679,102735,996],{"class":693},[651,102737,102738],{},"That Customizer type is a Functional Interface which means its a candidate for a lambda expression.",[669,102740,102742],{"className":4107,"code":102741,"language":4109,"meta":674,"style":674},"@FunctionalInterface\npublic interface Customizer\u003CT> {\n void customize(T t);\n\n static \u003CT> Customizer\u003CT> withDefaults() {\n return (t) -> {\n };\n }\n}\n",[676,102743,102744,102750,102765,102779,102783,102802,102813,102817,102821],{"__ignoreMap":674},[679,102745,102746,102748],{"class":681,"line":682},[679,102747,4116],{"class":693},[679,102749,16770],{"class":685},[679,102751,102752,102754,102756,102759,102761,102763],{"class":681,"line":790},[679,102753,6073],{"class":685},[679,102755,6994],{"class":685},[679,102757,102758],{"class":880}," Customizer",[679,102760,4505],{"class":693},[679,102762,8482],{"class":685},[679,102764,16397],{"class":693},[679,102766,102767,102769,102772,102775,102777],{"class":681,"line":892},[679,102768,3314],{"class":685},[679,102770,102771],{"class":880}," customize",[679,102773,102774],{"class":693},"(T ",[679,102776,55961],{"class":2099},[679,102778,1208],{"class":693},[679,102780,102781],{"class":681,"line":901},[679,102782,889],{"emptyLinePlaceholder":797},[679,102784,102785,102787,102789,102791,102794,102796,102798,102800],{"class":681,"line":909},[679,102786,967],{"class":685},[679,102788,48609],{"class":693},[679,102790,8482],{"class":685},[679,102792,102793],{"class":693},"> Customizer\u003C",[679,102795,8482],{"class":685},[679,102797,20881],{"class":693},[679,102799,89621],{"class":880},[679,102801,2667],{"class":693},[679,102803,102804,102806,102809,102811],{"class":681,"line":918},[679,102805,9444],{"class":685},[679,102807,102808],{"class":693}," (t) ",[679,102810,16955],{"class":685},[679,102812,884],{"class":693},[679,102814,102815],{"class":681,"line":935},[679,102816,17018],{"class":693},[679,102818,102819],{"class":681,"line":944},[679,102820,985],{"class":693},[679,102822,102823],{"class":681,"line":959},[679,102824,996],{"class":693},[4542,102826,102828],{"id":102827},"testing-the-configurations","Testing the Configurations",[651,102830,102831],{},"To test our configurations, we can run the application and use the default user and generated password provided by Spring Security. When accessing the root (\"/\") endpoint, we should receive a 200 OK response with the \"Hello, Spring Security\" message. If we try to access the \"/api/posts\" endpoint without providing valid credentials, we'll get a 401 Unauthorized response.",[651,102833,102834],{},[660,102835],{"alt":102836,"src":102837},"Testing the Configuration","/images/blog/2023/03/15/test-config.png",[651,102839,102840],{},"Both of these configurations achieve the same result; ultimately, it's a matter of personal preference which one you choose to use. However, keep in mind that the Lambda DSL approach is generally more concise and easier to read, especially for more complex configurations.",[4542,102842,9042],{"id":9041},[651,102844,102845],{},"In this tutorial, we explored two different Spring Security configuration approaches: chaining style and Lambda DSL. Both approaches are valid and used in various projects, but the Lambda DSL is cleaner and more concise. Experiment with both configurations and choose the one that you find the most comfortable to work with. Remember to stay up-to-date with the latest features and best practices of the Spring ecosystem to ensure that your projects are efficient and maintainable. Happy coding!",[786,102847,102848],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":102850},[102851,102852,102853,102854,102855,102856],{"id":94280,"depth":790,"text":102034},{"id":102075,"depth":790,"text":102076},{"id":102243,"depth":790,"text":102244},{"id":102465,"depth":790,"text":102466},{"id":102827,"depth":790,"text":102828},{"id":9041,"depth":790,"text":9042},{"slug":102858,"date":102859,"published":797,"author":798,"tags":102860,"cover":102861,"video":102862,"keywords":102863},"spring-security-lambda-dsl","2023-03-15T09:30:00.000Z",[23988],"./spring-security-lambda-dsl.png","https://www.youtube.com/embed/PWnEZh_t0WI","Spring Security, Spring Security Configuration, Spring Security Lambda DSL",{"title":333,"description":849},"blog/2023/03/15/spring-security-lambda-dsl","PTSaJVRwkxLwKbQQULY95eERPoO93XTTILOWywypRso",{"id":102868,"title":330,"body":102869,"description":104393,"extension":793,"meta":104394,"navigation":797,"path":331,"seo":104400,"stem":104401,"__hash__":104402},"content/blog/2023/03/20/graphql-mutations.md",{"type":648,"value":102870,"toc":104380},[102871,102874,102878,102885,102898,102904,102908,102920,102954,103077,103093,103162,103182,103211,103215,103218,103223,103261,103279,103397,103406,103411,103414,103418,103439,103518,103534,103652,103663,103725,103729,103732,103736,103759,103824,103833,103852,103859,103898,103902,103915,103932,103941,103967,103987,104053,104062,104084,104087,104126,104130,104146,104257,104265,104291,104294,104372,104374,104377],[651,102872,102873],{},"In this blog post, we'll be taking a look at mutations in GraphQL, specifically using Spring Boot, GraphQL Java, and Spring Data JPA. Up until now, we've mostly been focused on getting started with GraphQL and simple operations like queries. However, there's a lot more to GraphQL, like mutations, subscriptions, and new features in recent releases. Let's dive in and create a new project to explore these concepts!",[4542,102875,102877],{"id":102876},"create-a-spring-boot-project-with-graphql","Create a Spring Boot Project with GraphQL",[651,102879,102880,102881,102884],{},"To get started, we'll need a new Java Maven project. Using ",[812,102882,7117],{"href":7115,"rel":102883},[816],", create a project with the following configuration:",[5316,102886,102887,102890,102892,102895],{},[5332,102888,102889],{},"Project type: Maven",[5332,102891,102052],{},[5332,102893,102894],{},"Java: 17",[5332,102896,102897],{},"Dependencies: Web, Spring Data JPA, H2, and Spring Boot GraphQL",[651,102899,102900,102903],{},[660,102901],{"alt":94303,"src":102902},"/images/blog/2023/03/20/spring-init.png","Download the generated project and open it in your favorite IDE. We're going to create a simple application that simulates a library system with books and reviews.",[5909,102905,102907],{"id":102906},"set-up-entities-and-repositories","Set Up Entities and Repositories",[651,102909,102910,102911,23212,102913,102916,102917,102919],{},"First, let's create some model classes for our application. We'll use Spring Data JPA to define ",[676,102912,86678],{},[676,102914,102915],{},"Review"," entities, along with a ",[676,102918,88081],{}," interface for database operations:",[27665,102921,102922],{},[5332,102923,77744,102924,102926,102927,102929,102930,102932,102933,2797,102935,2797,102937,2797,102940,48406,102942,102945,102946,23212,102948,102950,102951,102953],{},[676,102925,86678],{}," class in the ",[676,102928,10048],{}," package and annotate it with ",[676,102931,16256],{},". Add fields for ",[676,102934,11341],{},[676,102936,11750],{},[676,102938,102939],{},"pages",[676,102941,6526],{},[676,102943,102944],{},"reviews",". Use appropriate JPA annotations, such as ",[676,102947,97614],{},[676,102949,97621],{}," for the ",[676,102952,11341],{}," field.",[669,102955,102957],{"className":4107,"code":102956,"language":4109,"meta":674,"style":674},"@Entity\npublic class Book {\n\n @Id\n @GeneratedValue\n private Integer id;\n private String title;\n private Integer pages;\n private String author;\n\n @OneToMany(cascade = CascadeType.ALL)\n @JoinColumn(name = \"book_id\")\n private List\u003CReview> reviews;\n\n // Constructors, getters, and setters\n}\n",[676,102958,102959,102965,102975,102979,102985,102991,102997,103003,103010,103017,103021,103036,103053,103064,103068,103073],{"__ignoreMap":674},[679,102960,102961,102963],{"class":681,"line":682},[679,102962,4116],{"class":693},[679,102964,11234],{"class":685},[679,102966,102967,102969,102971,102973],{"class":681,"line":790},[679,102968,6073],{"class":685},[679,102970,4512],{"class":685},[679,102972,86931],{"class":880},[679,102974,884],{"class":693},[679,102976,102977],{"class":681,"line":892},[679,102978,889],{"emptyLinePlaceholder":797},[679,102980,102981,102983],{"class":681,"line":901},[679,102982,6872],{"class":693},[679,102984,33530],{"class":685},[679,102986,102987,102989],{"class":681,"line":909},[679,102988,6872],{"class":693},[679,102990,11261],{"class":685},[679,102992,102993,102995],{"class":681,"line":918},[679,102994,9232],{"class":685},[679,102996,83952],{"class":693},[679,102998,102999,103001],{"class":681,"line":935},[679,103000,9232],{"class":685},[679,103002,93032],{"class":693},[679,103004,103005,103007],{"class":681,"line":944},[679,103006,9232],{"class":685},[679,103008,103009],{"class":693}," Integer pages;\n",[679,103011,103012,103014],{"class":681,"line":959},[679,103013,9232],{"class":685},[679,103015,103016],{"class":693}," String author;\n",[679,103018,103019],{"class":681,"line":964},[679,103020,889],{"emptyLinePlaceholder":797},[679,103022,103023,103025,103028,103030,103032,103034],{"class":681,"line":977},[679,103024,6872],{"class":693},[679,103026,103027],{"class":685},"OneToMany",[679,103029,745],{"class":693},[679,103031,83990],{"class":931},[679,103033,6883],{"class":685},[679,103035,83995],{"class":693},[679,103037,103038,103040,103042,103044,103046,103048,103051],{"class":681,"line":982},[679,103039,6872],{"class":693},[679,103041,84002],{"class":685},[679,103043,745],{"class":693},[679,103045,16334],{"class":931},[679,103047,6883],{"class":685},[679,103049,103050],{"class":689}," \"book_id\"",[679,103052,1339],{"class":693},[679,103054,103055,103057,103059,103061],{"class":681,"line":988},[679,103056,9232],{"class":685},[679,103058,87217],{"class":693},[679,103060,102915],{"class":685},[679,103062,103063],{"class":693},"> reviews;\n",[679,103065,103066],{"class":681,"line":993},[679,103067,889],{"emptyLinePlaceholder":797},[679,103069,103070],{"class":681,"line":2129},[679,103071,103072],{"class":1400}," // Constructors, getters, and setters\n",[679,103074,103075],{"class":681,"line":2140},[679,103076,996],{"class":693},[27665,103078,103079],{"start":790},[5332,103080,77744,103081,102926,103083,102929,103085,102932,103087,2797,103089,48406,103091,664],{},[676,103082,102915],{},[676,103084,10048],{},[676,103086,16256],{},[676,103088,11341],{},[676,103090,11750],{},[676,103092,42076],{},[669,103094,103096],{"className":4107,"code":103095,"language":4109,"meta":674,"style":674},"@Entity\npublic class Review {\n\n @Id\n @GeneratedValue\n private Integer id;\n private String title;\n private String comment;\n\n // Constructors, getters, and setters\n}\n",[676,103097,103098,103104,103115,103119,103125,103131,103137,103143,103150,103154,103158],{"__ignoreMap":674},[679,103099,103100,103102],{"class":681,"line":682},[679,103101,4116],{"class":693},[679,103103,11234],{"class":685},[679,103105,103106,103108,103110,103113],{"class":681,"line":790},[679,103107,6073],{"class":685},[679,103109,4512],{"class":685},[679,103111,103112],{"class":880}," Review",[679,103114,884],{"class":693},[679,103116,103117],{"class":681,"line":892},[679,103118,889],{"emptyLinePlaceholder":797},[679,103120,103121,103123],{"class":681,"line":901},[679,103122,6872],{"class":693},[679,103124,33530],{"class":685},[679,103126,103127,103129],{"class":681,"line":909},[679,103128,6872],{"class":693},[679,103130,11261],{"class":685},[679,103132,103133,103135],{"class":681,"line":918},[679,103134,9232],{"class":685},[679,103136,83952],{"class":693},[679,103138,103139,103141],{"class":681,"line":935},[679,103140,9232],{"class":685},[679,103142,93032],{"class":693},[679,103144,103145,103147],{"class":681,"line":944},[679,103146,9232],{"class":685},[679,103148,103149],{"class":693}," String comment;\n",[679,103151,103152],{"class":681,"line":959},[679,103153,889],{"emptyLinePlaceholder":797},[679,103155,103156],{"class":681,"line":964},[679,103157,103072],{"class":1400},[679,103159,103160],{"class":681,"line":977},[679,103161,996],{"class":693},[27665,103163,103164],{"start":892},[5332,103165,77744,103166,103168,103169,103171,103172,103175,103176,103178,103179,103181],{},[676,103167,88081],{}," interface in the ",[676,103170,16596],{}," package, extending ",[676,103173,103174],{},"JpaRepository"," and specifying ",[676,103177,86678],{}," as the entity type and ",[676,103180,1083],{}," as the ID type:",[669,103183,103185],{"className":4107,"code":103184,"language":4109,"meta":674,"style":674},"public interface BookRepository extends JpaRepository\u003CBook, Integer> {}\n",[676,103186,103187],{"__ignoreMap":674},[679,103188,103189,103191,103193,103195,103197,103200,103202,103204,103206,103208],{"class":681,"line":682},[679,103190,6073],{"class":685},[679,103192,6994],{"class":685},[679,103194,87191],{"class":880},[679,103196,2767],{"class":685},[679,103198,103199],{"class":880}," JpaRepository",[679,103201,4505],{"class":693},[679,103203,86678],{"class":685},[679,103205,2797],{"class":693},[679,103207,1083],{"class":685},[679,103209,103210],{"class":693},"> {}\n",[5909,103212,103214],{"id":103213},"configure-application-properties-and-sample-data","Configure Application Properties and Sample Data",[651,103216,103217],{},"Next, let's configure our application properties to specify some settings for the H2 database connection, enable the H2 console, and show SQL statements.",[651,103219,103220,103221,94061],{},"Add the following properties to the ",[676,103222,16242],{},[669,103224,103226],{"className":76589,"code":103225,"language":35538,"meta":674,"style":674},"spring.datasource.name=books\nspring.datasource.generate-unique-name=false\nspring.h2.console.enabled=true\nspring.jpa.show-sql=true\nspring.graphql.servlet.enabled=true\n",[676,103227,103228,103235,103242,103248,103254],{"__ignoreMap":674},[679,103229,103230,103232],{"class":681,"line":682},[679,103231,27252],{"class":685},[679,103233,103234],{"class":693},"=books\n",[679,103236,103237,103239],{"class":681,"line":790},[679,103238,83875],{"class":685},[679,103240,103241],{"class":693},"=false\n",[679,103243,103244,103246],{"class":681,"line":892},[679,103245,7323],{"class":685},[679,103247,93485],{"class":693},[679,103249,103250,103252],{"class":681,"line":901},[679,103251,83895],{"class":685},[679,103253,93485],{"class":693},[679,103255,103256,103259],{"class":681,"line":909},[679,103257,103258],{"class":685},"spring.graphql.servlet.enabled",[679,103260,93485],{"class":693},[651,103262,103263,103264,103267,103268,103270,103271,103273,103274,103276,103277,664],{},"We'll also need some sample data for our application. In the ",[676,103265,103266],{},"Application.java"," class, create a ",[676,103269,16415],{}," bean that initializes a ",[676,103272,86678],{}," and a ",[676,103275,102915],{}," and saves them to the database using the ",[676,103278,88081],{},[669,103280,103282],{"className":4107,"code":103281,"language":4109,"meta":674,"style":674},"@Bean\npublic CommandLineRunner initData(BookRepository bookRepository) {\n return args -> {\n Book book = new Book(\"Reactive Spring\", 484, \"Josh Long\");\n Review review = new Review(\"Great book!\", \"I really enjoyed this book!\");\n book.setReviews(Collections.singletonList(review));\n bookRepository.save(book);\n };\n}\n",[676,103283,103284,103290,103302,103312,103339,103362,103379,103389,103393],{"__ignoreMap":674},[679,103285,103286,103288],{"class":681,"line":682},[679,103287,4116],{"class":693},[679,103289,16929],{"class":685},[679,103291,103292,103294,103296,103299],{"class":681,"line":790},[679,103293,6073],{"class":685},[679,103295,16936],{"class":693},[679,103297,103298],{"class":880},"initData",[679,103300,103301],{"class":693},"(BookRepository bookRepository) {\n",[679,103303,103304,103306,103308,103310],{"class":681,"line":892},[679,103305,21478],{"class":685},[679,103307,16952],{"class":693},[679,103309,16955],{"class":685},[679,103311,884],{"class":693},[679,103313,103314,103317,103319,103321,103323,103325,103328,103330,103333,103335,103337],{"class":681,"line":901},[679,103315,103316],{"class":693}," Book book ",[679,103318,686],{"class":685},[679,103320,2054],{"class":685},[679,103322,86931],{"class":880},[679,103324,745],{"class":693},[679,103326,103327],{"class":689},"\"Reactive Spring\"",[679,103329,2797],{"class":693},[679,103331,103332],{"class":931},"484",[679,103334,2797],{"class":693},[679,103336,87452],{"class":689},[679,103338,1208],{"class":693},[679,103340,103341,103344,103346,103348,103350,103352,103355,103357,103360],{"class":681,"line":909},[679,103342,103343],{"class":693}," Review review ",[679,103345,686],{"class":685},[679,103347,2054],{"class":685},[679,103349,103112],{"class":880},[679,103351,745],{"class":693},[679,103353,103354],{"class":689},"\"Great book!\"",[679,103356,2797],{"class":693},[679,103358,103359],{"class":689},"\"I really enjoyed this book!\"",[679,103361,1208],{"class":693},[679,103363,103364,103367,103370,103373,103376],{"class":681,"line":918},[679,103365,103366],{"class":693}," book.",[679,103368,103369],{"class":880},"setReviews",[679,103371,103372],{"class":693},"(Collections.",[679,103374,103375],{"class":880},"singletonList",[679,103377,103378],{"class":693},"(review));\n",[679,103380,103381,103384,103386],{"class":681,"line":935},[679,103382,103383],{"class":693}," bookRepository.",[679,103385,7629],{"class":880},[679,103387,103388],{"class":693},"(book);\n",[679,103390,103391],{"class":681,"line":944},[679,103392,50000],{"class":693},[679,103394,103395],{"class":681,"line":959},[679,103396,996],{"class":693},[651,103398,103399,103400,103405],{},"This is a great opportunity to tell you that you should pick up Josh Long's book, ",[812,103401,103404],{"href":103402,"rel":103403},"https://amzn.to/3K9d7fw",[816],"Reactive Spring",". It's a great read!",[651,103407,103408],{},[660,103409],{"alt":103404,"src":103410},"/images/blog/2023/03/20/reactive-spring-book.jpeg",[651,103412,103413],{},"At this point, you should be able to run your application, and you'll see that the book and review are saved to the database.",[5909,103415,103417],{"id":103416},"create-a-graphql-query","Create a GraphQL Query",[651,103419,103420,103421,103423,103424,103426,103427,103429,103430,103432,103433,103435,103436,103438],{},"Now that we have our basic data model set up, let's create a simple GraphQL query to fetch our books. In the ",[676,103422,21930],{}," folder, create a ",[676,103425,66261],{}," directory and a file called ",[676,103428,87939],{},". In this file, define a ",[676,103431,86678],{}," type, a ",[676,103434,102915],{}," type, and a ",[676,103437,36099],{}," type.",[669,103440,103442],{"className":66259,"code":103441,"language":66261,"meta":674,"style":674},"type Book {\n id: ID\n title: String\n pages: Int\n author: String\n reviews: [Review]\n}\n\ntype Review {\n id: ID\n title: String\n comment: String\n}\n\ntype Query {\n findAllBooks: [Book]\n}\n",[676,103443,103444,103448,103453,103457,103461,103466,103471,103475,103479,103484,103488,103492,103497,103501,103505,103509,103514],{"__ignoreMap":674},[679,103445,103446],{"class":681,"line":682},[679,103447,86540],{},[679,103449,103450],{"class":681,"line":790},[679,103451,103452],{}," id: ID\n",[679,103454,103455],{"class":681,"line":892},[679,103456,86550],{},[679,103458,103459],{"class":681,"line":901},[679,103460,86555],{},[679,103462,103463],{"class":681,"line":909},[679,103464,103465],{}," author: String\n",[679,103467,103468],{"class":681,"line":918},[679,103469,103470],{}," reviews: [Review]\n",[679,103472,103473],{"class":681,"line":935},[679,103474,996],{},[679,103476,103477],{"class":681,"line":944},[679,103478,889],{"emptyLinePlaceholder":797},[679,103480,103481],{"class":681,"line":959},[679,103482,103483],{},"type Review {\n",[679,103485,103486],{"class":681,"line":964},[679,103487,103452],{},[679,103489,103490],{"class":681,"line":977},[679,103491,86550],{},[679,103493,103494],{"class":681,"line":982},[679,103495,103496],{}," comment: String\n",[679,103498,103499],{"class":681,"line":988},[679,103500,996],{},[679,103502,103503],{"class":681,"line":993},[679,103504,889],{"emptyLinePlaceholder":797},[679,103506,103507],{"class":681,"line":2129},[679,103508,86689],{},[679,103510,103511],{"class":681,"line":2140},[679,103512,103513],{}," findAllBooks: [Book]\n",[679,103515,103516],{"class":681,"line":2145},[679,103517,996],{},[651,103519,103520,103521,103523,103524,103526,103527,103529,103530,103533],{},"In your application create a REST controller named ",[676,103522,88074],{}," with a method that returns all books from the ",[676,103525,88081],{},". Use the ",[676,103528,88882],{}," annotation to link the method to the ",[676,103531,103532],{},"findAllBooks"," query in your GraphQL schema.",[669,103535,103537],{"className":4107,"code":103536,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/books\")\npublic class BookController {\n\n private final BookRepository bookRepository;\n\n public BookController(BookRepository bookRepository) {\n this.bookRepository = bookRepository;\n }\n\n @QueryMapping\n public List\u003CBook> findAllBooks() {\n return bookRepository.findAll();\n }\n}\n",[676,103538,103539,103545,103558,103568,103572,103580,103584,103596,103606,103610,103614,103620,103634,103644,103648],{"__ignoreMap":674},[679,103540,103541,103543],{"class":681,"line":682},[679,103542,4116],{"class":693},[679,103544,9212],{"class":685},[679,103546,103547,103549,103551,103553,103556],{"class":681,"line":790},[679,103548,4116],{"class":693},[679,103550,9275],{"class":685},[679,103552,745],{"class":693},[679,103554,103555],{"class":689},"\"/api/books\"",[679,103557,1339],{"class":693},[679,103559,103560,103562,103564,103566],{"class":681,"line":892},[679,103561,6073],{"class":685},[679,103563,4512],{"class":685},[679,103565,88106],{"class":880},[679,103567,884],{"class":693},[679,103569,103570],{"class":681,"line":901},[679,103571,889],{"emptyLinePlaceholder":797},[679,103573,103574,103576,103578],{"class":681,"line":909},[679,103575,9232],{"class":685},[679,103577,12768],{"class":685},[679,103579,88121],{"class":693},[679,103581,103582],{"class":681,"line":918},[679,103583,889],{"emptyLinePlaceholder":797},[679,103585,103586,103588,103590,103592,103594],{"class":681,"line":935},[679,103587,6089],{"class":685},[679,103589,88106],{"class":880},[679,103591,88134],{"class":693},[679,103593,88137],{"class":2099},[679,103595,4390],{"class":693},[679,103597,103598,103600,103602,103604],{"class":681,"line":944},[679,103599,7862],{"class":931},[679,103601,88146],{"class":693},[679,103603,686],{"class":685},[679,103605,88151],{"class":693},[679,103607,103608],{"class":681,"line":959},[679,103609,985],{"class":693},[679,103611,103612],{"class":681,"line":964},[679,103613,889],{"emptyLinePlaceholder":797},[679,103615,103616,103618],{"class":681,"line":977},[679,103617,6872],{"class":693},[679,103619,88905],{"class":685},[679,103621,103622,103624,103626,103628,103630,103632],{"class":681,"line":982},[679,103623,6089],{"class":685},[679,103625,87217],{"class":693},[679,103627,86678],{"class":685},[679,103629,20881],{"class":693},[679,103631,103532],{"class":880},[679,103633,2667],{"class":693},[679,103635,103636,103638,103640,103642],{"class":681,"line":988},[679,103637,9444],{"class":685},[679,103639,88253],{"class":693},[679,103641,34142],{"class":880},[679,103643,9317],{"class":693},[679,103645,103646],{"class":681,"line":993},[679,103647,985],{"class":693},[679,103649,103650],{"class":681,"line":2129},[679,103651,996],{"class":693},[651,103653,103654,103655,103659,103660,103662],{},"At this point, you should be able to run your application and use the ",[812,103656,103658],{"href":88458,"rel":103657},[816],"GraphiQL Playground"," to test your ",[676,103661,103532],{}," query.",[669,103664,103666],{"className":66259,"code":103665,"language":66261,"meta":674,"style":674},"query {\n findAllBooks {\n id\n title\n pages\n author\n reviews {\n id\n title\n comment\n }\n }\n}\n",[676,103667,103668,103672,103677,103682,103686,103690,103695,103700,103704,103708,103713,103717,103721],{"__ignoreMap":674},[679,103669,103670],{"class":681,"line":682},[679,103671,86446],{},[679,103673,103674],{"class":681,"line":790},[679,103675,103676],{}," findAllBooks {\n",[679,103678,103679],{"class":681,"line":892},[679,103680,103681],{}," id\n",[679,103683,103684],{"class":681,"line":901},[679,103685,52587],{},[679,103687,103688],{"class":681,"line":909},[679,103689,86460],{},[679,103691,103692],{"class":681,"line":918},[679,103693,103694],{}," author\n",[679,103696,103697],{"class":681,"line":935},[679,103698,103699],{}," reviews {\n",[679,103701,103702],{"class":681,"line":944},[679,103703,52627],{},[679,103705,103706],{"class":681,"line":959},[679,103707,52632],{},[679,103709,103710],{"class":681,"line":964},[679,103711,103712],{}," comment\n",[679,103714,103715],{"class":681,"line":977},[679,103716,985],{},[679,103718,103719],{"class":681,"line":982},[679,103720,21405],{},[679,103722,103723],{"class":681,"line":988},[679,103724,996],{},[4542,103726,103728],{"id":103727},"working-with-graphql-mutations","Working with GraphQL Mutations",[651,103730,103731],{},"Now let's explore different ways we can work with mutations in our application. A mutation is essentially an operation that makes changes to our data, such as creating, updating, or deleting records.",[5909,103733,103735],{"id":103734},"simple-mutation-with-basic-types","Simple Mutation with Basic Types",[651,103737,103738,103739,103742,103743,2797,103745,48406,103747,103749,103750,103526,103752,103754,103755,103758],{},"First, let's create a simple mutation that takes in basic types like strings and integers as inputs. In your controller, create a new method called ",[676,103740,103741],{},"createBook"," that accepts three parameters (",[676,103744,11750],{},[676,103746,102939],{},[676,103748,6526],{},"), and returns a ",[676,103751,86678],{},[676,103753,88885],{}," and annotation to associate the method with a mutation field in your GraphQL schema. The ",[676,103756,103757],{},"@Argument"," annotation maps the GraphQL named arguments to your method parameters.",[669,103760,103762],{"className":4107,"code":103761,"language":4109,"meta":674,"style":674},"@MutationMapping\npublic Book createBook(@Argument String title, @Argument Integer pages, @Argument String author) {\n Book book = new Book(title, pages, author);\n return bookRepository.save(book);\n}\n",[676,103763,103764,103771,103796,103810,103820],{"__ignoreMap":674},[679,103765,103766,103768],{"class":681,"line":682},[679,103767,4116],{"class":693},[679,103769,103770],{"class":685},"MutationMapping\n",[679,103772,103773,103775,103777,103779,103781,103783,103786,103788,103791,103793],{"class":681,"line":790},[679,103774,6073],{"class":685},[679,103776,87304],{"class":693},[679,103778,103741],{"class":880},[679,103780,73246],{"class":693},[679,103782,88763],{"class":685},[679,103784,103785],{"class":693}," String title, @",[679,103787,88763],{"class":685},[679,103789,103790],{"class":693}," Integer pages, @",[679,103792,88763],{"class":685},[679,103794,103795],{"class":693}," String author) {\n",[679,103797,103798,103801,103803,103805,103807],{"class":681,"line":892},[679,103799,103800],{"class":693}," Book book ",[679,103802,686],{"class":685},[679,103804,2054],{"class":685},[679,103806,86931],{"class":880},[679,103808,103809],{"class":693},"(title, pages, author);\n",[679,103811,103812,103814,103816,103818],{"class":681,"line":901},[679,103813,21478],{"class":685},[679,103815,88253],{"class":693},[679,103817,7629],{"class":880},[679,103819,103388],{"class":693},[679,103821,103822],{"class":681,"line":909},[679,103823,996],{"class":693},[651,103825,103826,103827,103829,103830,103832],{},"Next, update your GraphQL schema to define the ",[676,103828,86665],{}," type with a ",[676,103831,103741],{}," field:",[669,103834,103836],{"className":66259,"code":103835,"language":66261,"meta":674,"style":674},"type Mutation {\n createBook(title: String, pages: Int, author: String): Book\n}\n",[676,103837,103838,103843,103848],{"__ignoreMap":674},[679,103839,103840],{"class":681,"line":682},[679,103841,103842],{},"type Mutation {\n",[679,103844,103845],{"class":681,"line":790},[679,103846,103847],{}," createBook(title: String, pages: Int, author: String): Book\n",[679,103849,103850],{"class":681,"line":892},[679,103851,996],{},[651,103853,103854,103855,103858],{},"With this setup, you can now use a tool like ",[812,103856,103658],{"href":88458,"rel":103857},[816]," to run a mutation that creates a new book.",[669,103860,103862],{"className":66259,"code":103861,"language":66261,"meta":674,"style":674},"mutation CreateBook {\n createBook(title: \"My new book\", pages: 99, author: \"Dan Vega\") {\n id\n title\n pages\n author\n }\n}\n",[676,103863,103864,103869,103874,103878,103882,103886,103890,103894],{"__ignoreMap":674},[679,103865,103866],{"class":681,"line":682},[679,103867,103868],{},"mutation CreateBook {\n",[679,103870,103871],{"class":681,"line":790},[679,103872,103873],{}," createBook(title: \"My new book\", pages: 99, author: \"Dan Vega\") {\n",[679,103875,103876],{"class":681,"line":892},[679,103877,103681],{},[679,103879,103880],{"class":681,"line":901},[679,103881,52587],{},[679,103883,103884],{"class":681,"line":909},[679,103885,86460],{},[679,103887,103888],{"class":681,"line":918},[679,103889,103694],{},[679,103891,103892],{"class":681,"line":935},[679,103893,21405],{},[679,103895,103896],{"class":681,"line":944},[679,103897,996],{},[5909,103899,103901],{"id":103900},"mutation-with-object-input-type","Mutation with Object Input Type",[651,103903,103904,103905,103908,103909,103911,103912,103914],{},"In more complex scenarios, you might need to accept an entire object as input for a mutation. To do this, create a new ",[676,103906,103907],{},"BookInput"," record in your ",[676,103910,10048],{}," package, which essentially mirrors the properties of your ",[676,103913,86678],{}," entity:",[669,103916,103918],{"className":4107,"code":103917,"language":4109,"meta":674,"style":674},"public record BookInput(String title, Integer pages, String author) {}\n",[676,103919,103920],{"__ignoreMap":674},[679,103921,103922,103924,103926,103929],{"class":681,"line":682},[679,103923,6073],{"class":685},[679,103925,86928],{"class":685},[679,103927,103928],{"class":880}," BookInput",[679,103930,103931],{"class":693},"(String title, Integer pages, String author) {}\n",[651,103933,103934,103935,103937,103938,103940],{},"Define an ",[676,103936,27722],{}," type in your GraphQL schema that corresponds to your ",[676,103939,103907],{}," record:",[669,103942,103944],{"className":66259,"code":103943,"language":66261,"meta":674,"style":674},"input BookInput {\n title: String\n pages: Int\n author: String\n}\n",[676,103945,103946,103951,103955,103959,103963],{"__ignoreMap":674},[679,103947,103948],{"class":681,"line":682},[679,103949,103950],{},"input BookInput {\n",[679,103952,103953],{"class":681,"line":790},[679,103954,86550],{},[679,103956,103957],{"class":681,"line":892},[679,103958,86555],{},[679,103960,103961],{"class":681,"line":901},[679,103962,103465],{},[679,103964,103965],{"class":681,"line":909},[679,103966,996],{},[651,103968,103969,103970,84406,103973,103975,103976,103978,103979,103981,103982,23212,103984,103986],{},"Create a new method called ",[676,103971,103972],{},"addBook",[676,103974,88074],{}," that accepts a ",[676,103977,103907],{}," argument and returns a ",[676,103980,86678],{},". As before, use the ",[676,103983,88885],{},[676,103985,103757],{}," annotations:",[669,103988,103990],{"className":4107,"code":103989,"language":4109,"meta":674,"style":674},"@MutationMapping\npublic Book addBook(@Argument BookInput bookInput) {\n Book book = new Book(bookInput.title(), bookInput.pages(), bookInput.author());\n return bookRepository.save(book);\n}\n\n",[676,103991,103992,103998,104013,104039,104049],{"__ignoreMap":674},[679,103993,103994,103996],{"class":681,"line":682},[679,103995,4116],{"class":693},[679,103997,103770],{"class":685},[679,103999,104000,104002,104004,104006,104008,104010],{"class":681,"line":790},[679,104001,6073],{"class":685},[679,104003,87304],{"class":693},[679,104005,103972],{"class":880},[679,104007,73246],{"class":693},[679,104009,88763],{"class":685},[679,104011,104012],{"class":693}," BookInput bookInput) {\n",[679,104014,104015,104017,104019,104021,104023,104026,104028,104031,104033,104035,104037],{"class":681,"line":892},[679,104016,103800],{"class":693},[679,104018,686],{"class":685},[679,104020,2054],{"class":685},[679,104022,86931],{"class":880},[679,104024,104025],{"class":693},"(bookInput.",[679,104027,11750],{"class":880},[679,104029,104030],{"class":693},"(), bookInput.",[679,104032,102939],{"class":880},[679,104034,104030],{"class":693},[679,104036,6526],{"class":880},[679,104038,9431],{"class":693},[679,104040,104041,104043,104045,104047],{"class":681,"line":901},[679,104042,21478],{"class":685},[679,104044,88253],{"class":693},[679,104046,7629],{"class":880},[679,104048,103388],{"class":693},[679,104050,104051],{"class":681,"line":909},[679,104052,996],{"class":693},[651,104054,104055,104056,104058,104059,104061],{},"Update your GraphQL schema to add an ",[676,104057,103972],{}," field to the ",[676,104060,86665],{}," type:",[669,104063,104065],{"className":66259,"code":104064,"language":66261,"meta":674,"style":674},"type Mutation {\n createBook(title: String, pages: Int, author: String): Book\n addBook(book: BookInput!): Book\n}\n",[676,104066,104067,104071,104075,104080],{"__ignoreMap":674},[679,104068,104069],{"class":681,"line":682},[679,104070,103842],{},[679,104072,104073],{"class":681,"line":790},[679,104074,103847],{},[679,104076,104077],{"class":681,"line":892},[679,104078,104079],{}," addBook(book: BookInput!): Book\n",[679,104081,104082],{"class":681,"line":901},[679,104083,996],{},[651,104085,104086],{},"Now you can run a mutation that creates a new book using an object input rather than individual strings and integers.",[669,104088,104090],{"className":66259,"code":104089,"language":66261,"meta":674,"style":674},"mutation AddBook {\n addBook(book: { title: \"My New Book\", pages: 199, author: \"Dan Vega\" }) {\n id\n title\n pages\n author\n }\n}\n",[676,104091,104092,104097,104102,104106,104110,104114,104118,104122],{"__ignoreMap":674},[679,104093,104094],{"class":681,"line":682},[679,104095,104096],{},"mutation AddBook {\n",[679,104098,104099],{"class":681,"line":790},[679,104100,104101],{}," addBook(book: { title: \"My New Book\", pages: 199, author: \"Dan Vega\" }) {\n",[679,104103,104104],{"class":681,"line":892},[679,104105,103681],{},[679,104107,104108],{"class":681,"line":901},[679,104109,52587],{},[679,104111,104112],{"class":681,"line":909},[679,104113,86460],{},[679,104115,104116],{"class":681,"line":918},[679,104117,103694],{},[679,104119,104120],{"class":681,"line":935},[679,104121,21405],{},[679,104123,104124],{"class":681,"line":944},[679,104125,996],{},[5909,104127,104129],{"id":104128},"mutation-with-list-input-type","Mutation with List Input Type",[651,104131,104132,104133,104136,104137,104139,104140,104142,104143,104145],{},"Finally, let's create a mutation that accepts a list of objects as input. Add a new method called ",[676,104134,104135],{},"batchCreate"," in your ",[676,104138,88074],{}," that takes a list of ",[676,104141,103907],{}," objects and maps each input object to a ",[676,104144,86678],{}," entity, and saves the books in the database.",[669,104147,104149],{"className":4107,"code":104148,"language":4109,"meta":674,"style":674},"@MutationMapping\npublic List\u003CBook> batchCreate(@Argument List\u003CBookInput> books) {\n return books.stream()\n .map(bookInput -> new Book(bookInput.title(), bookInput.pages(), bookInput.author()))\n .map(bookRepository::save)\n .collect(Collectors.toList());\n}\n",[676,104150,104151,104157,104187,104197,104227,104241,104253],{"__ignoreMap":674},[679,104152,104153,104155],{"class":681,"line":682},[679,104154,4116],{"class":693},[679,104156,103770],{"class":685},[679,104158,104159,104161,104163,104165,104167,104169,104172,104174,104176,104178,104180,104182,104184],{"class":681,"line":790},[679,104160,6073],{"class":685},[679,104162,96047],{"class":693},[679,104164,4505],{"class":685},[679,104166,86678],{"class":693},[679,104168,5860],{"class":685},[679,104170,104171],{"class":880}," batchCreate",[679,104173,73246],{"class":693},[679,104175,88763],{"class":685},[679,104177,96047],{"class":693},[679,104179,4505],{"class":685},[679,104181,103907],{"class":693},[679,104183,5860],{"class":685},[679,104185,104186],{"class":693}," books) {\n",[679,104188,104189,104191,104193,104195],{"class":681,"line":892},[679,104190,21478],{"class":685},[679,104192,87320],{"class":693},[679,104194,87323],{"class":880},[679,104196,17545],{"class":693},[679,104198,104199,104201,104203,104206,104208,104210,104212,104214,104216,104218,104220,104222,104224],{"class":681,"line":901},[679,104200,21331],{"class":693},[679,104202,51904],{"class":880},[679,104204,104205],{"class":693},"(bookInput ",[679,104207,16955],{"class":685},[679,104209,2054],{"class":685},[679,104211,86931],{"class":880},[679,104213,104025],{"class":693},[679,104215,11750],{"class":880},[679,104217,104030],{"class":693},[679,104219,102939],{"class":880},[679,104221,104030],{"class":693},[679,104223,6526],{"class":880},[679,104225,104226],{"class":693},"()))\n",[679,104228,104229,104231,104233,104236,104238],{"class":681,"line":909},[679,104230,21331],{"class":693},[679,104232,51904],{"class":880},[679,104234,104235],{"class":693},"(bookRepository",[679,104237,90007],{"class":685},[679,104239,104240],{"class":693},"save)\n",[679,104242,104243,104245,104247,104249,104251],{"class":681,"line":918},[679,104244,21331],{"class":693},[679,104246,90908],{"class":880},[679,104248,90911],{"class":693},[679,104250,85083],{"class":880},[679,104252,9431],{"class":693},[679,104254,104255],{"class":681,"line":935},[679,104256,996],{"class":693},[651,104258,104259,104260,104058,104262,104264],{},"Update your GraphQL schema to add a ",[676,104261,104135],{},[676,104263,86665],{}," type, specifying that the input list should not be empty:",[669,104266,104268],{"className":66259,"code":104267,"language":66261,"meta":674,"style":674},"type Mutation {\n createBook(title: String, pages: Int, author: String): Book\n addBook(book: BookInput!): Book\n batchCreate(books: [BookInput!]!): [Book]\n}\n",[676,104269,104270,104274,104278,104282,104287],{"__ignoreMap":674},[679,104271,104272],{"class":681,"line":682},[679,104273,103842],{},[679,104275,104276],{"class":681,"line":790},[679,104277,103847],{},[679,104279,104280],{"class":681,"line":892},[679,104281,104079],{},[679,104283,104284],{"class":681,"line":901},[679,104285,104286],{}," batchCreate(books: [BookInput!]!): [Book]\n",[679,104288,104289],{"class":681,"line":909},[679,104290,996],{},[651,104292,104293],{},"Now you can run a mutation that creates multiple books in a single operation by passing in a list of book inputs.",[669,104295,104297],{"className":66259,"code":104296,"language":66261,"meta":674,"style":674},"mutation BatchCreate {\n batchCreate(\n books: [\n { title: \"Batch Book 1\", pages: 99, author: \"New Author\" }\n { title: \"Batch Book 2\", pages: 99, author: \"New Author\" }\n { title: \"Batch Book 3\", pages: 99, author: \"New Author\" }\n { title: \"Batch Book 4\", pages: 99, author: \"New Author\" }\n { title: \"Batch Book 5\", pages: 99, author: \"New Author\" }\n ]\n ) {\n id\n title\n pages\n author\n }\n}\n",[676,104298,104299,104304,104309,104314,104319,104324,104329,104334,104339,104343,104348,104352,104356,104360,104364,104368],{"__ignoreMap":674},[679,104300,104301],{"class":681,"line":682},[679,104302,104303],{},"mutation BatchCreate {\n",[679,104305,104306],{"class":681,"line":790},[679,104307,104308],{}," batchCreate(\n",[679,104310,104311],{"class":681,"line":892},[679,104312,104313],{}," books: [\n",[679,104315,104316],{"class":681,"line":901},[679,104317,104318],{}," { title: \"Batch Book 1\", pages: 99, author: \"New Author\" }\n",[679,104320,104321],{"class":681,"line":909},[679,104322,104323],{}," { title: \"Batch Book 2\", pages: 99, author: \"New Author\" }\n",[679,104325,104326],{"class":681,"line":918},[679,104327,104328],{}," { title: \"Batch Book 3\", pages: 99, author: \"New Author\" }\n",[679,104330,104331],{"class":681,"line":935},[679,104332,104333],{}," { title: \"Batch Book 4\", pages: 99, author: \"New Author\" }\n",[679,104335,104336],{"class":681,"line":944},[679,104337,104338],{}," { title: \"Batch Book 5\", pages: 99, author: \"New Author\" }\n",[679,104340,104341],{"class":681,"line":959},[679,104342,52918],{},[679,104344,104345],{"class":681,"line":964},[679,104346,104347],{}," ) {\n",[679,104349,104350],{"class":681,"line":977},[679,104351,103681],{},[679,104353,104354],{"class":681,"line":982},[679,104355,52587],{},[679,104357,104358],{"class":681,"line":988},[679,104359,86460],{},[679,104361,104362],{"class":681,"line":993},[679,104363,103694],{},[679,104365,104366],{"class":681,"line":2129},[679,104367,21405],{},[679,104369,104370],{"class":681,"line":2140},[679,104371,996],{},[4542,104373,9042],{"id":9041},[651,104375,104376],{},"In this blog post, we explored how to work with mutations in GraphQL, specifically using Spring Boot, GraphQL Java, and Spring Data JPA. We covered different ways of passing input to our mutations, including simple types, object input types, and list input types. With this knowledge in hand, you can create powerful GraphQL APIs that allow clients to make complex updates to your data models. Happy coding!",[786,104378,104379],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":104381},[104382,104387,104392],{"id":102876,"depth":790,"text":102877,"children":104383},[104384,104385,104386],{"id":102906,"depth":892,"text":102907},{"id":103213,"depth":892,"text":103214},{"id":103416,"depth":892,"text":103417},{"id":103727,"depth":790,"text":103728,"children":104388},[104389,104390,104391],{"id":103734,"depth":892,"text":103735},{"id":103900,"depth":892,"text":103901},{"id":104128,"depth":892,"text":104129},{"id":9041,"depth":790,"text":9042},"In this blog post, we'll be taking a look at mutations in GraphQL, specifically using Spring Boot, GraphQL Java, and Spring Data JPA.",{"slug":104395,"date":104396,"published":797,"author":798,"tags":104397,"cover":104398,"video":104399,"keywords":102863},"graphql-mutations","2023-03-20T11:00:00.000Z",[99135,7077],"./graphql-mutations.png","https://www.youtube.com/embed/u3FFRq3-0CM",{"title":330,"description":104393},"blog/2023/03/20/graphql-mutations","3y4j3CXG8Q50BxgU4C18-jCsjVUHoEQjvISt-mxJ38A",{"id":104404,"title":327,"body":104405,"description":104599,"extension":793,"meta":104600,"navigation":797,"path":328,"seo":104606,"stem":104607,"__hash__":104608},"content/blog/2023/03/31/videotap.md",{"type":648,"value":104406,"toc":104587},[104407,104415,104419,104427,104430,104433,104436,104440,104443,104446,104452,104456,104459,104462,104466,104469,104472,104478,104481,104487,104490,104496,104503,104509,104518,104521,104549,104553,104562,104568,104570,104581],[651,104408,104409,104410,104414],{},"Are you a content creator who struggles to find enough time to produce written content alongside videos? Do you wish there was an easy way to transform your videos into compelling blog posts? Look no further than ",[812,104411,97413],{"href":104412,"rel":104413},"https://videotapit.com/?via=dan",[816],"! This innovative service transcribes and converts your videos into high-quality blog posts, complete with code blocks and other engaging features. Plus, with their recent increase in video length limit to 25 minutes, most videos are a perfect fit. Say goodbye to the hassle of creating separate written content and hello to a more efficient workflow with VideoTap.",[4542,104416,104418],{"id":104417},"me-as-a-content-creator","Me as a Content Creator",[651,104420,104421,104422,104426],{},"As a content creator, I produce content for various mediums. I began my blog as a means of helping others who were encountering the same problems as I was. Through this process, I developed a deep appreciation for the writing process. This love for writing has inspired me to continue posting blog entries for over 15 years. I also started a ",[812,104423,104425],{"href":82778,"rel":104424},[816],"weekly newsletter"," as a way of keeping myself writing regularly.",[651,104428,104429],{},"Throughout this process I began to get very interested in Video for the same reasons. My fellow developers were on YouTube and looking for solutions to their problems. I also realized that I could create courses as a side hustle to make a little extra money.",[651,104431,104432],{},"I enjoy writing and producing video content, but there's a problem. As I've dedicated more time to creating videos, I've become less consistent with my writing. Unless I figure out how to clone myself (which I don't think anyone wants), I will have to choose where to spend my time.",[651,104434,104435],{},"A main goal of mine over the last 2 years has been to grow my YouTube channel, so that's where I'm currently focusing my efforts. Many people may not realize the amount of time and effort that goes into creating a single video, but I can attest that there's a lot involved.",[5909,104437,104439],{"id":104438},"blog-post-video","Blog Post → Video",[651,104441,104442],{},"Once I'm ready to publish a video, I often lack the energy to write a full blog post to accompany it. In an ideal scenario, I would start with a blog post. This would enable me to work on the code demo and decide exactly what I want to say, and more importantly, what I don't want to spend time on. After preparing the blog post, I could use it as a script to guide the video creation process.",[651,104444,104445],{},"However, in the real world, with a full-time job, family, and limited time, I need to be precise about how I spend my time. I would rather create two videos than one blog post plus a video. Nevertheless, I would prefer to make two videos and two blog posts instead of only one. That is why I'm excited about this new service that will allow me to do that.",[651,104447,104448],{},[660,104449],{"alt":104450,"src":104451},"Writing a new blog post","/images/blog/2023/03/31/photo-1504691342899-4d92b50853e1.jpeg",[4542,104453,104455],{"id":104454},"what-is-videotap","What is VideoTap",[651,104457,104458],{},"Now that you understand the problem that VideoTap solves, let's discuss how it does so. VideoTap is a service that transcribes and converts videos into blog posts. If all it did was take the transcript and create a blog post, it wouldn't be very helpful. In fact, I've tried doing this on my own before, and I can attest that nobody wants to read a video transcript.",[651,104460,104461],{},"What sets VideoTap apart is the quality of its blog posts. Instead of simply using a transcription, they utilize GPT-4 to generate high-quality blog posts that include headings, images, code blocks, and more. This makes their articles much more compelling. I'm not sure what their secret sauce is because I tried a similar process on my own, but was unable to come up with high-quality posts.",[5909,104463,104465],{"id":104464},"how-to-convert-a-video-to-a-blog-post","How to convert a video to a blog post",[651,104467,104468],{},"Using VideoTap costs $1 per minute of video you want to convert. For example, a 20 minute video would cost $20. Additionally, they offer a white glove service where they take care of the video conversion for you and send you the final results. This service costs $3 per minute of video.",[651,104470,104471],{},"Once you create a free account, you can begin the process by going to your dashboard. You can add a video by URL or connect your YouTube account. Once you add a video, the length will be determined, and you will be prompted to pay.",[651,104473,104474],{},[660,104475],{"alt":104476,"src":104477},"Ordering a new Blog Post with VideoTap","/images/blog/2023/03/31/order-new-blog-posts.png",[651,104479,104480],{},"After your payment is processed, the service will start generating a blog post. This process may take a few minutes as it involves transcribing the video, adding titles, headers, code, and formatting everything.",[651,104482,104483],{},[660,104484],{"alt":104485,"src":104486},"Generating a new blog post with VideoTap","/images/blog/2023/03/31/generating-blog-post.png",[651,104488,104489],{},"When the conversion is complete you will be taken to this screen where you can make further edits. First off I love the simplicity of this design with the image and outline on the left. The blog post isn’t perfect and this is usually where I will make my first pass on editing it.",[651,104491,104492],{},[660,104493],{"alt":104494,"src":104495},"New blog Post created by VideoTap","/images/blog/2023/03/31/new-blog-post.png",[651,104497,104498,104499,104502],{},"When the blog post is ready to export click the ",[676,104500,104501],{},"Export Blog Post"," button in the upper right hand corner. I write all of my blog posts in Markdown so the copy markdown is a great feature. It’s almost like this service was written by a developer for developers (👋🏻 Chris).",[651,104504,104505],{},[660,104506],{"alt":104507,"src":104508},"Export Blog Post from VideoTap as Markdown","/images/blog/2023/03/31/export-blog-post.png",[5909,104510,104512,104513,104517],{"id":104511},"show-me-the-money-blog-posts","Show me the ",[104514,104515,104516],"del",{},"Money"," Blog Posts",[651,104519,104520],{},"I think its easy to talk about a service but what about a real world use case. I have about 10 videos in VideoTap currently and here are a few examples of ones I have converted into blog post. At the top of each blog post you can find the YouTube video that I used to create the post.",[5316,104522,104523,104530,104535,104542],{},[5332,104524,104525],{},[812,104526,104529],{"href":104527,"rel":104528},"https://www.danvega.dev/blog/2023/03/20/graphql-mutations/",[816],"GraphQL Mutations",[5332,104531,104532],{},[812,104533,333],{"href":95535,"rel":104534},[816],[5332,104536,104537],{},[812,104538,104541],{"href":104539,"rel":104540},"https://www.danvega.dev/blog/2023/03/02/spring-shell-intro/",[816],"Building Command Line Applications with Spring",[5332,104543,104544],{},[812,104545,104548],{"href":104546,"rel":104547},"https://www.danvega.dev/blog/2023/02/03/native-images-graalvm/",[816],"Building Native Images with GraalVM",[5909,104550,104552],{"id":104551},"youtube-generator","YouTube Generator",[651,104554,104555,104556,104561],{},"They also have a free service for ",[812,104557,104560],{"href":104558,"rel":104559},"https://videotapit.com/youtube-chapters-generator?ref=dan",[816],"generating YouTube Chapters"," which I have found really useful. This is another one of those tasks during the video creation process that I find exhausting. You just spent all this time producing, editing and publishing the video and instead of moving on to the next one you need to watch it again to figure out where the chapter marks are.",[651,104563,104564],{},[660,104565],{"alt":104566,"src":104567},"YouTube Chapter Generator from VideoTap","/images/blog/2023/03/31/youtube-chapter-generator.png",[4542,104569,9042],{"id":9041},[651,104571,104572,104573,104576,104577,104580],{},"I think at this point I should be transparent and tell you that I am an affiliate for ",[812,104574,97413],{"href":104412,"rel":104575},[816],". if you know me though you know I don’t push products or services. I have almost 30k YouTube subscribers and have yet to take a single brand deal. I’m an affiliate for VideoTap because it is one of those services I absolutely love and believe in. If you wouldn’t mind using my ",[812,104578,6409],{"href":104412,"rel":104579},[816]," it will cost you nothing extra and it helps support my content creation.",[651,104582,104583,104585,41109],{},[2939,104584,41105],{},[41107,104586],{},{"title":674,"searchDepth":790,"depth":790,"links":104588},[104589,104592,104598],{"id":104417,"depth":790,"text":104418,"children":104590},[104591],{"id":104438,"depth":892,"text":104439},{"id":104454,"depth":790,"text":104455,"children":104593},[104594,104595,104597],{"id":104464,"depth":892,"text":104465},{"id":104511,"depth":892,"text":104596},"Show me the Money Blog Posts",{"id":104551,"depth":892,"text":104552},{"id":9041,"depth":790,"text":9042},"Are you a content creator who struggles to find enough time to produce written content alongside videos? Do you wish there was an easy way to transform your videos into compelling blog posts? Look no further than VideoTap!",{"slug":104601,"date":104602,"published":797,"author":798,"tags":104603,"cover":104604,"keywords":104605},"videotap","2023-03-31T16:00:00.000Z",[47833],"./video-to-blogpost.png","Content, Content Creation, VideoTop, VideoTap, Video to Blog Post, GPT-4",{"title":327,"description":104599},"blog/2023/03/31/videotap","KHZlAmTqqNYJIl-F30UBT8F7IDNh5pP4ICw4E6825NM",{"id":104610,"title":324,"body":104611,"description":849,"extension":793,"meta":104988,"navigation":797,"path":325,"seo":104995,"stem":104996,"__hash__":104997},"content/blog/2023/04/12/virtual-threads-spring.md",{"type":648,"value":104612,"toc":104980},[104613,104616,104620,104623,104632,104636,104639,104642,104645,104651,104654,104657,104663,104669,104673,104676,104690,104696,104700,104703,104709,104713,104716,104953,104956,104958,104972,104977],[651,104614,104615],{},"Welcome back, friends! Today, we're diving into Project Loom and specifically virtual threads. We'll be covering what they are, why you should care as a Spring developer, and even provide a demo on using virtual threads in a Spring application. Let’s begin!",[4542,104617,104619],{"id":104618},"what-is-project-loom","What is Project Loom?",[651,104621,104622],{},"Project Loom aims to reduce the effort of writing, maintaining, and observing high-throughput concurrent applications. It introduces virtual threads as a solution to improve performance and scalability, particularly when dealing with blocking APIs commonly found in Spring MVC applications.",[651,104624,104625,104626,104631],{},"It's been announced that ",[812,104627,104630],{"href":104628,"rel":104629},"https://openjdk.org/jeps/444",[816],"virtual threads are targeted for JDK 21",", which is exciting news as it means they might show up in Spring Framework 6.1 (also targeted for later this year). So it's about time we all learn about virtual threads and why we might care about them as Spring developers.",[4542,104633,104635],{"id":104634},"history-why-we-need-virtual-threads","History: Why We Need Virtual Threads",[651,104637,104638],{},"Let's first understand the thread-per-request model and how threads work in Java. When a client makes a request to a server, the request often involves the server to process blocking operations, such as reading or persisting from a database using JDBC or JPA, writing to a file, or communicating with another service.",[651,104640,104641],{},"This application works great on your laptop and during the initial stages of implementation. However, once news spreads about your exceptional new application, you may start experiencing a high volume of traffic that could cause it to crash.",[651,104643,104644],{},"Why is this happening? What can you do to address this?",[651,104646,104647],{},[660,104648],{"alt":104649,"src":104650},"embracing-virtual-threads.png","/images/blog/2023/04/12/embracing-virtual-threads.png",[651,104652,104653],{},"To answer these questions you must understand the thread-per-request model. When a request is handled by the web server it is using a Java Thread and that thread is tied to an operating system thread. When you make a call to a blocking operation like reading or persisting to a database that thread is blocked from doing anything else until the request is fulfilled.",[651,104655,104656],{},"The thread-per-request model ties up threads in the system, and there is a maximum number of concurrent threads allowed. When the number of maximum threads has been reached, each subsequent request will need to wait for a thread to be released to fulfill that request. This can cause slowness or even errors in the application when it experiences high traffic.",[651,104658,104659],{},[660,104660],{"alt":104661,"src":104662},"thread-per-request.png","/images/blog/2023/04/12/thread-per-request.png",[651,104664,104665],{},[660,104666],{"alt":104667,"src":104668},"java-is-made-of-threads.png","/images/blog/2023/04/12/java-is-made-of-threads.png",[4542,104670,104672],{"id":104671},"scalability-solutions","Scalability Solutions",[651,104674,104675],{},"To improve scalability, there are two main approaches that you can currently take advantage of:",[27665,104677,104678,104684],{},[5332,104679,104680,104683],{},[2939,104681,104682],{},"Scaling Hardware",": Add more memory, CPU or servers (vertical and horizontal scaling)",[5332,104685,104686,104689],{},[2939,104687,104688],{},"Asynchronous Programming",": Writing non-blocking software to optimize thread usage",[651,104691,104692],{},[660,104693],{"alt":104694,"src":104695},"thread-per-request-solutions.png","/images/blog/2023/04/12/thread-per-request-solutions.png",[4542,104697,104699],{"id":104698},"introduction-to-virtual-threads","Introduction to Virtual Threads",[651,104701,104702],{},"Virtual threads, available as a preview release in JDK 19 and 20, are lightweight, inexpensive, and easy to create. They are tied to a platform thread that is connected to the operating system thread. Consequently, we no longer tie up platform threads in our applications and can handle more concurrent requests. The most exciting aspect of virtual threads is that we can use them in our Spring applications with little or no code changes.",[651,104704,104705],{},[660,104706],{"alt":104707,"src":104708},"virtual-threads.png","/images/blog/2023/04/12/virtual-threads.png",[4542,104710,104712],{"id":104711},"demo-using-virtual-threads-in-spring-applications","Demo: Using Virtual Threads in Spring Applications",[651,104714,104715],{},"To create a new Spring project that uses virtual threads, follow these steps:",[27665,104717,104718,104725,104728,104783,104853,104856,104950],{},[5332,104719,104720,104721,104724],{},"Go to ",[812,104722,77478],{"href":7115,"rel":104723},[816]," and select Maven, Java, and the latest version of Spring Boot.",[5332,104726,104727],{},"Fill in the project metadata, choose Java 20 (or 19 if available), and select the \"Web\" dependency.",[5332,104729,104730,104731,104733,104734,104737,104738],{},"Generate the project, open it in IntelliJ, and make sure the ",[676,104732,81517],{}," file has ",[676,104735,104736],{},"maven-compiler-plugin"," configuration setup to enable the preview feature.",[669,104739,104741],{"className":9101,"code":104740,"language":9103,"meta":674,"style":674},"\u003Cplugin>\n \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n \u003CartifactId>maven-compiler-plugin\u003C/artifactId>\n \u003Cconfiguration>\n \u003CcompilerArgs>--enable-preview\u003C/compilerArgs>\n \u003Csource>19\u003C/source>\n \u003Ctarget>19\u003C/target>\n \u003C/configuration>\n\u003C/plugin>\n",[676,104742,104743,104747,104751,104756,104760,104765,104770,104775,104779],{"__ignoreMap":674},[679,104744,104745],{"class":681,"line":682},[679,104746,81581],{},[679,104748,104749],{"class":681,"line":790},[679,104750,81818],{},[679,104752,104753],{"class":681,"line":892},[679,104754,104755],{}," \u003CartifactId>maven-compiler-plugin\u003C/artifactId>\n",[679,104757,104758],{"class":681,"line":901},[679,104759,81763],{},[679,104761,104762],{"class":681,"line":909},[679,104763,104764],{}," \u003CcompilerArgs>--enable-preview\u003C/compilerArgs>\n",[679,104766,104767],{"class":681,"line":918},[679,104768,104769],{}," \u003Csource>19\u003C/source>\n",[679,104771,104772],{"class":681,"line":935},[679,104773,104774],{}," \u003Ctarget>19\u003C/target>\n",[679,104776,104777],{"class":681,"line":944},[679,104778,81778],{},[679,104780,104781],{"class":681,"line":959},[679,104782,81787],{},[5332,104784,104785,104786],{},"Create a controller mapping to the root path, and return the current thread's information as a string.",[669,104787,104789],{"className":4107,"code":104788,"language":4109,"meta":674,"style":674},"@RestController\npublic class HomeController {\n @GetMapping(\"/\")\n public String hello() {\n return Thread.currentThread().toString();\n }\n}\n",[676,104790,104791,104797,104807,104819,104829,104845,104849],{"__ignoreMap":674},[679,104792,104793,104795],{"class":681,"line":682},[679,104794,4116],{"class":693},[679,104796,9212],{"class":685},[679,104798,104799,104801,104803,104805],{"class":681,"line":790},[679,104800,6073],{"class":685},[679,104802,4512],{"class":685},[679,104804,18716],{"class":880},[679,104806,884],{"class":693},[679,104808,104809,104811,104813,104815,104817],{"class":681,"line":892},[679,104810,6872],{"class":693},[679,104812,20852],{"class":685},[679,104814,745],{"class":693},[679,104816,10032],{"class":689},[679,104818,1339],{"class":693},[679,104820,104821,104823,104825,104827],{"class":681,"line":901},[679,104822,6089],{"class":685},[679,104824,9289],{"class":693},[679,104826,72963],{"class":880},[679,104828,2667],{"class":693},[679,104830,104831,104833,104836,104839,104841,104843],{"class":681,"line":909},[679,104832,9444],{"class":685},[679,104834,104835],{"class":693}," Thread.",[679,104837,104838],{"class":880},"currentThread",[679,104840,10541],{"class":693},[679,104842,14391],{"class":880},[679,104844,9317],{"class":693},[679,104846,104847],{"class":681,"line":918},[679,104848,985],{"class":693},[679,104850,104851],{"class":681,"line":935},[679,104852,996],{"class":693},[5332,104854,104855],{},"Run the application and test it by making a request to the local server. The response should display the main thread's information.",[5332,104857,104858,104859,104861,104862,104865,104866,664,104869],{},"In your ",[676,104860,23635],{}," class, create a new Bean called ",[676,104863,104864],{},"TomcatProtocolHandlerCustomizer"," and set its executor to the new ",[676,104867,104868],{},"VirtualThreadPerTaskExecutor",[669,104870,104872],{"className":4107,"code":104871,"language":4109,"meta":674,"style":674},"@Bean\n TomcatProtocolHandlerCustomizer\u003C?> protocolHandlerVirtualThreadExecutorCustomizer() {\n return protocolHandler -> {\n log.info(\"Configuring \" + protocolHandler + \" to use VirtualThreadPerTaskExecutor\");\n protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());\n };\n }\n",[676,104873,104874,104880,104892,104903,104926,104942,104946],{"__ignoreMap":674},[679,104875,104876,104878],{"class":681,"line":682},[679,104877,4116],{"class":693},[679,104879,16929],{"class":685},[679,104881,104882,104885,104887,104890],{"class":681,"line":790},[679,104883,104884],{"class":693}," TomcatProtocolHandlerCustomizer",[679,104886,6692],{"class":685},[679,104888,104889],{"class":880}," protocolHandlerVirtualThreadExecutorCustomizer",[679,104891,2667],{"class":693},[679,104893,104894,104896,104899,104901],{"class":681,"line":892},[679,104895,9444],{"class":685},[679,104897,104898],{"class":693}," protocolHandler ",[679,104900,16955],{"class":685},[679,104902,884],{"class":693},[679,104904,104905,104908,104910,104912,104915,104917,104919,104921,104924],{"class":681,"line":901},[679,104906,104907],{"class":693}," log.",[679,104909,9415],{"class":880},[679,104911,745],{"class":693},[679,104913,104914],{"class":689},"\"Configuring \"",[679,104916,3059],{"class":685},[679,104918,104898],{"class":693},[679,104920,3065],{"class":685},[679,104922,104923],{"class":689}," \" to use VirtualThreadPerTaskExecutor\"",[679,104925,1208],{"class":693},[679,104927,104928,104931,104934,104937,104940],{"class":681,"line":909},[679,104929,104930],{"class":693}," protocolHandler.",[679,104932,104933],{"class":880},"setExecutor",[679,104935,104936],{"class":693},"(Executors.",[679,104938,104939],{"class":880},"newVirtualThreadPerTaskExecutor",[679,104941,9431],{"class":693},[679,104943,104944],{"class":681,"line":918},[679,104945,17018],{"class":693},[679,104947,104948],{"class":681,"line":935},[679,104949,985],{"class":693},[5332,104951,104952],{},"Restart your application and test it again by making another request to the local server. The response should now display the virtual thread's information.",[651,104954,104955],{},"And that's it! With just a few changes, you can start using virtual threads in your Spring application and take advantage of its performance improvements.",[4542,104957,9042],{"id":9041},[651,104959,104960,104961,104966,104967,664],{},"Virtual threads offer an exciting solution to improve performance and scalability for Spring developers. With little or no code changes required, it's worth exploring how virtual threads can benefit your applications. Be sure to check out the Spring blog posts to learn more about ",[812,104962,104965],{"href":104963,"rel":104964},"https://spring.io/blog/2022/10/11/embracing-virtual-threads",[816],"embracing virtual threads"," and their ",[812,104968,104971],{"href":104969,"rel":104970},"https://spring.io/blog/2023/02/27/web-applications-and-project-loom",[816],"performance analysis",[651,104973,104974,104975,41109],{},"Happy coding, friends!",[41107,104976],{},[786,104978,104979],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":104981},[104982,104983,104984,104985,104986,104987],{"id":104618,"depth":790,"text":104619},{"id":104634,"depth":790,"text":104635},{"id":104671,"depth":790,"text":104672},{"id":104698,"depth":790,"text":104699},{"id":104711,"depth":790,"text":104712},{"id":9041,"depth":790,"text":9042},{"slug":104989,"date":104990,"published":797,"author":798,"tags":104991,"cover":104992,"video":104993,"github":104994,"keywords":104605},"virtual-threads-spring","2023-04-12T16:00:00.000Z",[7077,99135],"./virtual-threads-spring-boot.png","https://www.youtube.com/embed/Is5HXJhC3jE","https://github.com/danvega/loom",{"title":324,"description":849},"blog/2023/04/12/virtual-threads-spring","Ke9QBIS_lvyREn2VCemvJGglALCIp3_NIDLX-ROGSPQ",{"id":104999,"title":321,"body":105000,"description":105004,"extension":793,"meta":105613,"navigation":797,"path":322,"seo":105621,"stem":105622,"__hash__":105623},"content/blog/2023/04/17/graphql-client.md",{"type":648,"value":105001,"toc":105604},[105002,105005,105009,105020,105024,105042,105047,105058,105062,105072,105133,105137,105153,105189,105193,105203,105210,105217,105239,105245,105329,105333,105340,105491,105500,105503,105512,105584,105590,105595,105598,105601],[651,105003,105004],{},"If you're working on a Spring application and you need to call another REST API, you have tools such as RestTemplate or WebClient. But what if you need to call a GraphQL API? With Spring, a GraphQL Client is available. In this tutorial, you will learn how to include it in your project and use it by building a practical example.",[4542,105006,105008],{"id":105007},"accessing-a-free-graphql-api","Accessing a Free GraphQL API",[651,105010,105011,105012,105016,105017,105019],{},"To demonstrate, we will utilize a free GraphQL API from ",[812,105013,105015],{"href":105014},"www.examplelink.com","Countries Trevorblades.com",". The query returns information such as the country's name, emoji, currency, code, and capital. Note that some fields may return ",[676,105018,1146],{}," values, so we should guard against those in our application.",[4542,105021,105023],{"id":105022},"creating-our-application","Creating Our Application",[651,105025,105026,105027,105029,105030,105033,105034,2797,105036,48406,105039,664],{},"We will generate our Spring Boot project from ",[812,105028,77478],{"href":105014},". For our application, we will use Maven as a build tool, Java as the language, and the latest version of Spring Boot. Let's name the application ",[676,105031,105032],{},"Countries",", and we will be using Java 17. For our dependencies, we require ",[676,105035,72926],{},[676,105037,105038],{},"Spring Data JDBC",[676,105040,105041],{},"H2 Database",[651,105043,105044],{},[660,105045],{"alt":7117,"src":105046},"/images/blog/2023/04/17/graphql-client-spring-init.png",[651,105048,40060,105049,105051,105052,105054,105055,105057],{},[676,105050,72926],{}," dependency enables us to build a web application. ",[676,105053,105038],{}," is crucial because it will allow us to load country data and store it in a database to avoid repeating GraphQL API calls. For our database, we will use an ",[676,105056,101023],{}," in-memory database for simplicity.",[4542,105059,105061],{"id":105060},"setting-up-our-country-record","Setting Up Our Country Record",[651,105063,105064,105065,105067,105068,105071],{},"Having generated the application, let's move to the IDE and start coding. We will create a ",[676,105066,29450],{}," package and within it a record named ",[676,105069,105070],{},"Country"," to represent a country based on the fields from the GraphQL API.",[669,105073,105075],{"className":4107,"code":105074,"language":4109,"meta":674,"style":674},"public record Country(\n @Id\n Integer id,\n String name,\n String emoji,\n String currency,\n String code,\n String capital\n) {\n}\n",[676,105076,105077,105088,105095,105100,105105,105110,105115,105120,105125,105129],{"__ignoreMap":674},[679,105078,105079,105081,105083,105086],{"class":681,"line":682},[679,105080,6073],{"class":685},[679,105082,86928],{"class":685},[679,105084,105085],{"class":880}," Country",[679,105087,21337],{"class":693},[679,105089,105090,105093],{"class":681,"line":790},[679,105091,105092],{"class":693}," @",[679,105094,33530],{"class":685},[679,105096,105097],{"class":681,"line":892},[679,105098,105099],{"class":693}," Integer id,\n",[679,105101,105102],{"class":681,"line":901},[679,105103,105104],{"class":693}," String name,\n",[679,105106,105107],{"class":681,"line":909},[679,105108,105109],{"class":693}," String emoji,\n",[679,105111,105112],{"class":681,"line":918},[679,105113,105114],{"class":693}," String currency,\n",[679,105116,105117],{"class":681,"line":935},[679,105118,105119],{"class":693}," String code,\n",[679,105121,105122],{"class":681,"line":944},[679,105123,105124],{"class":693}," String capital\n",[679,105126,105127],{"class":681,"line":959},[679,105128,4390],{"class":693},[679,105130,105131],{"class":681,"line":964},[679,105132,996],{"class":693},[4542,105134,105136],{"id":105135},"building-our-country-repository","Building Our Country Repository",[651,105138,105139,105140,103168,105143,105145,105146,105149,105150,105152],{},"We'll then build a ",[676,105141,105142],{},"CountryRepository",[676,105144,16596],{}," package that extends ",[676,105147,105148],{},"CrudRepository\u003CCountry, Integer>",". This will provide basic CRUD functionality for the ",[676,105151,105070],{}," entity.",[669,105154,105156],{"className":4107,"code":105155,"language":4109,"meta":674,"style":674},"public interface CountryRepository extends ListCrudRepository\u003CCountry,Integer> {\n\n}\n",[676,105157,105158,105181,105185],{"__ignoreMap":674},[679,105159,105160,105162,105164,105167,105169,105171,105173,105175,105177,105179],{"class":681,"line":682},[679,105161,6073],{"class":685},[679,105163,6994],{"class":685},[679,105165,105166],{"class":880}," CountryRepository",[679,105168,2767],{"class":685},[679,105170,97778],{"class":880},[679,105172,4505],{"class":693},[679,105174,105070],{"class":685},[679,105176,1202],{"class":693},[679,105178,1083],{"class":685},[679,105180,16397],{"class":693},[679,105182,105183],{"class":681,"line":790},[679,105184,889],{"emptyLinePlaceholder":797},[679,105186,105187],{"class":681,"line":892},[679,105188,996],{"class":693},[4542,105190,105192],{"id":105191},"constructing-the-country-service","Constructing The Country Service",[651,105194,105195,105196,89334,105199,105202],{},"Next, let's create a ",[676,105197,105198],{},"CountryService",[676,105200,105201],{},"Service"," package. This service will connect with the GraphQL API and return the list of countries.",[651,105204,105205,105206,105209],{},"In the service, we can use a standard WebClient, but to simplify the process, we will utilize the ",[676,105207,105208],{},"HttpGraphQLClient"," provided by Spring for GraphQL.",[651,105211,105212,105213,105216],{},"To use this client, we will add another dependency, ",[676,105214,105215],{},"spring-boot-starter-graphql",". If you already selected Spring for GraphQL from the Spring Initializr you can skip this step.",[669,105218,105220],{"className":9101,"code":105219,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-graphql\u003C/artifactId>\n\u003C/dependency>\n",[676,105221,105222,105226,105230,105235],{"__ignoreMap":674},[679,105223,105224],{"class":681,"line":682},[679,105225,9110],{},[679,105227,105228],{"class":681,"line":790},[679,105229,40818],{},[679,105231,105232],{"class":681,"line":892},[679,105233,105234],{}," \u003CartifactId>spring-boot-starter-graphql\u003C/artifactId>\n",[679,105236,105237],{"class":681,"line":901},[679,105238,9125],{},[651,105240,105241,105242,664],{},"Then, we instantiate a ",[676,105243,105244],{},"GraphQLClient",[669,105246,105248],{"className":4107,"code":105247,"language":4109,"meta":674,"style":674},"private final HttpGraphQlClient graphQlClient;\n\npublic CountryService() {\n WebClient client = WebClient.builder()\n .baseUrl(\"https://countries.trevorblades.com\")\n .build();\n graphQlClient = HttpGraphQlClient.builder(client).build();\n}\n",[676,105249,105250,105259,105263,105272,105285,105298,105306,105325],{"__ignoreMap":674},[679,105251,105252,105254,105256],{"class":681,"line":682},[679,105253,21301],{"class":685},[679,105255,12768],{"class":685},[679,105257,105258],{"class":693}," HttpGraphQlClient graphQlClient;\n",[679,105260,105261],{"class":681,"line":790},[679,105262,889],{"emptyLinePlaceholder":797},[679,105264,105265,105267,105270],{"class":681,"line":892},[679,105266,6073],{"class":685},[679,105268,105269],{"class":880}," CountryService",[679,105271,2667],{"class":693},[679,105273,105274,105277,105279,105281,105283],{"class":681,"line":901},[679,105275,105276],{"class":693}," WebClient client ",[679,105278,686],{"class":685},[679,105280,100517],{"class":693},[679,105282,90934],{"class":880},[679,105284,17545],{"class":693},[679,105286,105287,105289,105291,105293,105296],{"class":681,"line":909},[679,105288,40148],{"class":693},[679,105290,100528],{"class":880},[679,105292,745],{"class":693},[679,105294,105295],{"class":689},"\"https://countries.trevorblades.com\"",[679,105297,1339],{"class":693},[679,105299,105300,105302,105304],{"class":681,"line":918},[679,105301,40148],{"class":693},[679,105303,23612],{"class":880},[679,105305,9317],{"class":693},[679,105307,105308,105311,105313,105316,105318,105321,105323],{"class":681,"line":935},[679,105309,105310],{"class":693}," graphQlClient ",[679,105312,686],{"class":685},[679,105314,105315],{"class":693}," HttpGraphQlClient.",[679,105317,90934],{"class":880},[679,105319,105320],{"class":693},"(client).",[679,105322,23612],{"class":880},[679,105324,9317],{"class":693},[679,105326,105327],{"class":681,"line":944},[679,105328,996],{"class":693},[4542,105330,105332],{"id":105331},"calling-the-graphql-api","Calling The GraphQL API",[651,105334,105335,105336,105339],{},"Now that our GraphQL client is ready, we can create a method, ",[676,105337,105338],{},"getCountries()",", to make the API call to retrieve a list of countries.",[669,105341,105343],{"className":4107,"code":105342,"language":4109,"meta":674,"style":674}," public Mono\u003CList\u003CCountry>> getCountries() {\n String document = \"\"\"\n query {\n countries {\n name\n emoji\n currency\n code\n capital\n }\n }\n \"\"\";\n\n Mono\u003CList\u003CCountry>> countries = graphQlClient.document(document)\n .retrieve(\"countries\")\n .toEntityList(Country.class);\n\n return countries;\n }\n",[676,105344,105345,105367,105377,105382,105387,105392,105397,105402,105407,105412,105416,105420,105427,105431,105452,105466,105476,105480,105487],{"__ignoreMap":674},[679,105346,105347,105349,105352,105354,105356,105358,105360,105362,105365],{"class":681,"line":682},[679,105348,6089],{"class":685},[679,105350,105351],{"class":693}," Mono",[679,105353,4505],{"class":685},[679,105355,19754],{"class":693},[679,105357,4505],{"class":685},[679,105359,105070],{"class":693},[679,105361,96110],{"class":685},[679,105363,105364],{"class":880}," getCountries",[679,105366,2667],{"class":693},[679,105368,105369,105372,105374],{"class":681,"line":790},[679,105370,105371],{"class":693}," String document ",[679,105373,686],{"class":685},[679,105375,105376],{"class":689}," \"\"\"\n",[679,105378,105379],{"class":681,"line":892},[679,105380,105381],{"class":689}," query {\n",[679,105383,105384],{"class":681,"line":901},[679,105385,105386],{"class":689}," countries {\n",[679,105388,105389],{"class":681,"line":909},[679,105390,105391],{"class":689}," name\n",[679,105393,105394],{"class":681,"line":918},[679,105395,105396],{"class":689}," emoji\n",[679,105398,105399],{"class":681,"line":935},[679,105400,105401],{"class":689}," currency\n",[679,105403,105404],{"class":681,"line":944},[679,105405,105406],{"class":689}," code\n",[679,105408,105409],{"class":681,"line":959},[679,105410,105411],{"class":689}," capital\n",[679,105413,105414],{"class":681,"line":964},[679,105415,25517],{"class":689},[679,105417,105418],{"class":681,"line":977},[679,105419,1290],{"class":689},[679,105421,105422,105425],{"class":681,"line":982},[679,105423,105424],{"class":689}," \"\"\"",[679,105426,1186],{"class":693},[679,105428,105429],{"class":681,"line":988},[679,105430,889],{"emptyLinePlaceholder":797},[679,105432,105433,105436,105438,105441,105443,105446,105449],{"class":681,"line":993},[679,105434,105435],{"class":693}," Mono\u003CList\u003C",[679,105437,105070],{"class":685},[679,105439,105440],{"class":693},">> countries ",[679,105442,686],{"class":685},[679,105444,105445],{"class":693}," graphQlClient.",[679,105447,105448],{"class":880},"document",[679,105450,105451],{"class":693},"(document)\n",[679,105453,105454,105456,105459,105461,105464],{"class":681,"line":2129},[679,105455,73482],{"class":693},[679,105457,105458],{"class":880},"retrieve",[679,105460,745],{"class":693},[679,105462,105463],{"class":689},"\"countries\"",[679,105465,1339],{"class":693},[679,105467,105468,105470,105473],{"class":681,"line":2140},[679,105469,73482],{"class":693},[679,105471,105472],{"class":880},"toEntityList",[679,105474,105475],{"class":693},"(Country.class);\n",[679,105477,105478],{"class":681,"line":2145},[679,105479,889],{"emptyLinePlaceholder":797},[679,105481,105482,105484],{"class":681,"line":2154},[679,105483,9444],{"class":685},[679,105485,105486],{"class":693}," countries;\n",[679,105488,105489],{"class":681,"line":2159},[679,105490,985],{"class":693},[651,105492,40060,105493,105496,105497,105499],{},[676,105494,105495],{},"graphQLClient.query(document).retrieve().asEntityList(Country.class)"," sequence initiates the API call, retrieves the result, and maps it to a list of ",[676,105498,105070],{}," objects.",[4542,105501,105502],{"id":7309},"Running The Application",[651,105504,105505,105506,105509,105510,664],{},"To run the application, we need to add a CommandLineRunner in the main application class that will call ",[676,105507,105508],{},"getCountries"," and save the list of countries in the database using our ",[676,105511,105142],{},[669,105513,105515],{"className":4107,"code":105514,"language":4109,"meta":674,"style":674},"@Bean\nCommandLineRunner commandLineRunner(CountryService service, CountryRepository repository) {\n return args -> {\n Mono\u003CList\u003CCountry>> countries = service.getCountries();\n countries.subscribe(repository::saveAll);\n };\n}\n",[676,105516,105517,105523,105533,105543,105561,105576,105580],{"__ignoreMap":674},[679,105518,105519,105521],{"class":681,"line":682},[679,105520,4116],{"class":693},[679,105522,16929],{"class":685},[679,105524,105525,105528,105530],{"class":681,"line":790},[679,105526,105527],{"class":693},"CommandLineRunner ",[679,105529,23712],{"class":880},[679,105531,105532],{"class":693},"(CountryService service, CountryRepository repository) {\n",[679,105534,105535,105537,105539,105541],{"class":681,"line":892},[679,105536,44767],{"class":685},[679,105538,16952],{"class":693},[679,105540,16955],{"class":685},[679,105542,884],{"class":693},[679,105544,105545,105548,105550,105552,105554,105557,105559],{"class":681,"line":901},[679,105546,105547],{"class":693}," Mono\u003CList\u003C",[679,105549,105070],{"class":685},[679,105551,105440],{"class":693},[679,105553,686],{"class":685},[679,105555,105556],{"class":693}," service.",[679,105558,105508],{"class":880},[679,105560,9317],{"class":693},[679,105562,105563,105566,105568,105571,105573],{"class":681,"line":909},[679,105564,105565],{"class":693}," countries.",[679,105567,21334],{"class":880},[679,105569,105570],{"class":693},"(repository",[679,105572,90007],{"class":685},[679,105574,105575],{"class":693},"saveAll);\n",[679,105577,105578],{"class":681,"line":918},[679,105579,53075],{"class":693},[679,105581,105582],{"class":681,"line":935},[679,105583,996],{"class":693},[651,105585,105586,105587,664],{},"After running the application and no exceptions are thrown, we can confirm that our country data has been saved in the database. You can check this by accessing the H2 Console and running a ",[676,105588,105589],{},"SELECT * FROM country;",[651,105591,105592],{},[660,105593],{"alt":16741,"src":105594},"/images/blog/2023/04/17/h2-select-countries.png",[651,105596,105597],{},"That's all there is to connecting to a GraphQL API in Spring Boot! You now have the tools needed to tackle any GraphQL requests in your projects, courtesy of Spring for GraphQL.",[651,105599,105600],{},"If you found this post helpful, please give it a thumbs up, subscribe to the channel, and, as always, happy coding!",[786,105602,105603],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":105605},[105606,105607,105608,105609,105610,105611,105612],{"id":105007,"depth":790,"text":105008},{"id":105022,"depth":790,"text":105023},{"id":105060,"depth":790,"text":105061},{"id":105135,"depth":790,"text":105136},{"id":105191,"depth":790,"text":105192},{"id":105331,"depth":790,"text":105332},{"id":7309,"depth":790,"text":105502},{"slug":105614,"date":105615,"published":797,"author":798,"tags":105616,"cover":105617,"video":105618,"github":105619,"keywords":105620},"graphql-client","2023-04-17T08:00:00.000Z",[7077,99135],"./graphql-client-thumbnail.png","https://www.youtube.com/embed/BuPItqaVeGo","https://github.com/danvega/countries","Spring Boot, Spring Framework, Spring for GraphQL, GraphQL Client, GraphQL Java, Java, GraphQL",{"title":321,"description":105004},"blog/2023/04/17/graphql-client","Utsf6-hVhQS2ImJE6GCgFKUjWi4W_ltkW2rvwD0-Plw",{"id":105625,"title":318,"body":105626,"description":106809,"extension":793,"meta":106810,"navigation":797,"path":319,"seo":106818,"stem":106819,"__hash__":106820},"content/blog/2023/04/20/multiple-spring-security-configs.md",{"type":648,"value":105627,"toc":106797},[105628,105631,105633,105639,105650,105653,105664,105670,105674,105692,105720,105727,105762,105765,105772,105875,105890,106012,106028,106116,106140,106146,106150,106153,106175,106178,106182,106185,106196,106213,106248,106252,106266,106276,106398,106402,106415,106427,106580,106584,106592,106598,106726,106730,106737,106757,106760,106780,106783,106785,106793,106795],[651,105629,105630],{},"Security is an important aspect of any application, and Spring Security makes it secure by default. In this tutorial, we will discuss how to create multiple Spring Security configurations and why you might want to do so. We'll cover everything from setting up a new project, to customizing security configurations, and exploring multiple security filter chains. So let's dive in!",[4542,105632,95749],{"id":54715},[651,105634,105635,105636,92917],{},"First, head over to ",[812,105637,77478],{"href":51358,"rel":105638},[816],[5316,105640,105641,105644,105646,105648],{},[5332,105642,105643],{},"Build Tool: Maven",[5332,105645,102049],{},[5332,105647,102052],{},[5332,105649,102055],{},[651,105651,105652],{},"Next, add the following dependencies:",[5316,105654,105655,105657,105660,105662],{},[5332,105656,72926],{},[5332,105658,105659],{},"Spring Security (leave it off for now, we'll add it later)",[5332,105661,105038],{},[5332,105663,105041],{},[651,105665,105666,105669],{},[660,105667],{"alt":7117,"src":105668},"/images/blog/2023/04/20/start-spring-io.png","Generate the project and open it up in your favorite IDE.",[4542,105671,105673],{"id":105672},"setting-up-the-application","Setting up the Application",[651,105675,105676,105677,105679,105680,2797,105682,48406,105684,105686,105687,105689,105690,2391],{},"First, create a simple domain model called ",[676,105678,5105],{}," with some fields like ",[676,105681,11341],{},[676,105683,11750],{},[676,105685,47833],{},". Annotate the ",[676,105688,11341],{}," field with ",[676,105691,97614],{},[669,105693,105695],{"className":4107,"code":105694,"language":4109,"meta":674,"style":674},"public record Post(@Id Integer id, String title, String content) {\n\n}\n",[676,105696,105697,105712,105716],{"__ignoreMap":674},[679,105698,105699,105701,105703,105705,105707,105709],{"class":681,"line":682},[679,105700,6073],{"class":685},[679,105702,86928],{"class":685},[679,105704,4658],{"class":880},[679,105706,73246],{"class":693},[679,105708,11256],{"class":685},[679,105710,105711],{"class":693}," Integer id, String title, String content) {\n",[679,105713,105714],{"class":681,"line":790},[679,105715,889],{"emptyLinePlaceholder":797},[679,105717,105718],{"class":681,"line":892},[679,105719,996],{"class":693},[651,105721,93176,105722,105724,105725,664],{},[676,105723,23351],{}," interface that extends ",[676,105726,16365],{},[669,105728,105730],{"className":4107,"code":105729,"language":4109,"meta":674,"style":674},"public interface PostRepository extends ListCrudRepository\u003CPost,Integer> {\n\n}\n",[676,105731,105732,105754,105758],{"__ignoreMap":674},[679,105733,105734,105736,105738,105740,105742,105744,105746,105748,105750,105752],{"class":681,"line":682},[679,105735,6073],{"class":685},[679,105737,6994],{"class":685},[679,105739,93243],{"class":880},[679,105741,2767],{"class":685},[679,105743,97778],{"class":880},[679,105745,4505],{"class":693},[679,105747,5105],{"class":685},[679,105749,1202],{"class":693},[679,105751,1083],{"class":685},[679,105753,16397],{"class":693},[679,105755,105756],{"class":681,"line":790},[679,105757,889],{"emptyLinePlaceholder":797},[679,105759,105760],{"class":681,"line":892},[679,105761,996],{"class":693},[651,105763,105764],{},"Then, create two controllers:",[27665,105766,105767],{},[5332,105768,105769,105771],{},[676,105770,89333],{}," with two mappings for the root and private URLs, returning strings \"hello home\" and \"secured\" respectively.",[669,105773,105775],{"className":4107,"code":105774,"language":4109,"meta":674,"style":674},"@RestController\npublic class HomeController {\n\n @GetMapping(\"/\")\n public String home() {\n return \"Hello, World!\";\n }\n\n @GetMapping(\"/private\")\n public String secure() {\n return \"secured\";\n }\n}\n",[676,105776,105777,105783,105793,105797,105809,105819,105827,105831,105835,105848,105858,105867,105871],{"__ignoreMap":674},[679,105778,105779,105781],{"class":681,"line":682},[679,105780,4116],{"class":693},[679,105782,9212],{"class":685},[679,105784,105785,105787,105789,105791],{"class":681,"line":790},[679,105786,6073],{"class":685},[679,105788,4512],{"class":685},[679,105790,18716],{"class":880},[679,105792,884],{"class":693},[679,105794,105795],{"class":681,"line":892},[679,105796,889],{"emptyLinePlaceholder":797},[679,105798,105799,105801,105803,105805,105807],{"class":681,"line":901},[679,105800,6872],{"class":693},[679,105802,20852],{"class":685},[679,105804,745],{"class":693},[679,105806,10032],{"class":689},[679,105808,1339],{"class":693},[679,105810,105811,105813,105815,105817],{"class":681,"line":909},[679,105812,6089],{"class":685},[679,105814,9289],{"class":693},[679,105816,12642],{"class":880},[679,105818,2667],{"class":693},[679,105820,105821,105823,105825],{"class":681,"line":918},[679,105822,9444],{"class":685},[679,105824,32904],{"class":689},[679,105826,1186],{"class":693},[679,105828,105829],{"class":681,"line":935},[679,105830,985],{"class":693},[679,105832,105833],{"class":681,"line":944},[679,105834,889],{"emptyLinePlaceholder":797},[679,105836,105837,105839,105841,105843,105846],{"class":681,"line":959},[679,105838,6872],{"class":693},[679,105840,20852],{"class":685},[679,105842,745],{"class":693},[679,105844,105845],{"class":689},"\"/private\"",[679,105847,1339],{"class":693},[679,105849,105850,105852,105854,105856],{"class":681,"line":964},[679,105851,6089],{"class":685},[679,105853,9289],{"class":693},[679,105855,74196],{"class":880},[679,105857,2667],{"class":693},[679,105859,105860,105862,105865],{"class":681,"line":977},[679,105861,9444],{"class":685},[679,105863,105864],{"class":689}," \"secured\"",[679,105866,1186],{"class":693},[679,105868,105869],{"class":681,"line":982},[679,105870,985],{"class":693},[679,105872,105873],{"class":681,"line":988},[679,105874,996],{"class":693},[27665,105876,105877],{"start":790},[5332,105878,105879,105881,105882,105884,105885,105887,105888,664],{},[676,105880,93270],{}," with a request mapping for ",[676,105883,93279],{}," and a method ",[676,105886,34142],{}," that returns all the posts using the ",[676,105889,23351],{},[669,105891,105893],{"className":4107,"code":105892,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private final PostRepository repository;\n\n public PostController(PostRepository repository) {\n this.repository = repository;\n }\n\n @GetMapping\n public List\u003CPost> findAll() {\n return repository.findAll();\n }\n\n}\n",[676,105894,105895,105901,105913,105923,105927,105936,105940,105952,105962,105966,105970,105976,105990,106000,106004,106008],{"__ignoreMap":674},[679,105896,105897,105899],{"class":681,"line":682},[679,105898,4116],{"class":693},[679,105900,9212],{"class":685},[679,105902,105903,105905,105907,105909,105911],{"class":681,"line":790},[679,105904,4116],{"class":693},[679,105906,9275],{"class":685},[679,105908,745],{"class":693},[679,105910,23141],{"class":689},[679,105912,1339],{"class":693},[679,105914,105915,105917,105919,105921],{"class":681,"line":892},[679,105916,6073],{"class":685},[679,105918,4512],{"class":685},[679,105920,23152],{"class":880},[679,105922,884],{"class":693},[679,105924,105925],{"class":681,"line":901},[679,105926,889],{"emptyLinePlaceholder":797},[679,105928,105929,105931,105933],{"class":681,"line":909},[679,105930,9232],{"class":685},[679,105932,12768],{"class":685},[679,105934,105935],{"class":693}," PostRepository repository;\n",[679,105937,105938],{"class":681,"line":918},[679,105939,889],{"emptyLinePlaceholder":797},[679,105941,105942,105944,105946,105948,105950],{"class":681,"line":935},[679,105943,6089],{"class":685},[679,105945,23152],{"class":880},[679,105947,23405],{"class":693},[679,105949,16596],{"class":2099},[679,105951,4390],{"class":693},[679,105953,105954,105956,105958,105960],{"class":681,"line":944},[679,105955,7862],{"class":931},[679,105957,16605],{"class":693},[679,105959,686],{"class":685},[679,105961,16610],{"class":693},[679,105963,105964],{"class":681,"line":959},[679,105965,985],{"class":693},[679,105967,105968],{"class":681,"line":964},[679,105969,889],{"emptyLinePlaceholder":797},[679,105971,105972,105974],{"class":681,"line":977},[679,105973,6872],{"class":693},[679,105975,80415],{"class":685},[679,105977,105978,105980,105982,105984,105986,105988],{"class":681,"line":982},[679,105979,6089],{"class":685},[679,105981,87217],{"class":693},[679,105983,5105],{"class":685},[679,105985,20881],{"class":693},[679,105987,34142],{"class":880},[679,105989,2667],{"class":693},[679,105991,105992,105994,105996,105998],{"class":681,"line":988},[679,105993,9444],{"class":685},[679,105995,85606],{"class":693},[679,105997,34142],{"class":880},[679,105999,9317],{"class":693},[679,106001,106002],{"class":681,"line":993},[679,106003,985],{"class":693},[679,106005,106006],{"class":681,"line":2129},[679,106007,889],{"emptyLinePlaceholder":797},[679,106009,106010],{"class":681,"line":2140},[679,106011,996],{"class":693},[651,106013,106014,106015,106018,106019,106021,106022,106024,106025,106027],{},"After this, create a ",[676,106016,106017],{},"schema.sql"," file in the ",[676,106020,21930],{}," folder with SQL to create a ",[676,106023,93349],{}," table and insert a single row. Also, update your ",[676,106026,16242],{}," file to set the data source name to \"blog\" and enable the H2 console.",[669,106029,106031],{"className":671,"code":106030,"language":673,"meta":674,"style":674},"create table Post (\n id int auto_increment primary key ,\n title varchar(255) not null,\n content text not null\n);\nINSERT INTO POST(title,content) VALUES ('Hello, World!','My First Blog Post');\n",[676,106032,106033,106044,106060,106079,106089,106093],{"__ignoreMap":674},[679,106034,106035,106037,106040,106042],{"class":681,"line":682},[679,106036,5697],{"class":685},[679,106038,106039],{"class":685}," table",[679,106041,4658],{"class":880},[679,106043,59384],{"class":693},[679,106045,106046,106049,106051,106054,106057],{"class":681,"line":790},[679,106047,106048],{"class":693}," id ",[679,106050,1078],{"class":685},[679,106052,106053],{"class":693}," auto_increment ",[679,106055,106056],{"class":685},"primary key",[679,106058,106059],{"class":693}," ,\n",[679,106061,106062,106065,106067,106069,106072,106074,106077],{"class":681,"line":892},[679,106063,106064],{"class":693}," title ",[679,106066,7302],{"class":685},[679,106068,745],{"class":693},[679,106070,106071],{"class":931},"255",[679,106073,2378],{"class":693},[679,106075,106076],{"class":685},"not null",[679,106078,12083],{"class":693},[679,106080,106081,106084,106086],{"class":681,"line":901},[679,106082,106083],{"class":693}," content ",[679,106085,11464],{"class":685},[679,106087,106088],{"class":685}," not null\n",[679,106090,106091],{"class":681,"line":909},[679,106092,1208],{"class":693},[679,106094,106095,106098,106101,106104,106106,106109,106111,106114],{"class":681,"line":918},[679,106096,106097],{"class":685},"INSERT INTO",[679,106099,106100],{"class":693}," POST(title,content) ",[679,106102,106103],{"class":685},"VALUES",[679,106105,4193],{"class":693},[679,106107,106108],{"class":689},"'Hello, World!'",[679,106110,1202],{"class":693},[679,106112,106113],{"class":689},"'My First Blog Post'",[679,106115,1208],{"class":693},[669,106117,106119],{"className":76589,"code":106118,"language":35538,"meta":674,"style":674},"spring.datasource.generate-unique-name=false\nspring.datasource.name=blog\nspring.h2.console.enabled=true\n",[676,106120,106121,106127,106134],{"__ignoreMap":674},[679,106122,106123,106125],{"class":681,"line":682},[679,106124,83875],{"class":685},[679,106126,103241],{"class":693},[679,106128,106129,106131],{"class":681,"line":790},[679,106130,27252],{"class":685},[679,106132,106133],{"class":693},"=blog\n",[679,106135,106136,106138],{"class":681,"line":892},[679,106137,7323],{"class":685},[679,106139,93485],{"class":693},[651,106141,106142,106143,664],{},"Now, run the application and you should be able to access the H2 console at ",[676,106144,106145],{},"localhost:8080/h2-console",[4542,106147,106149],{"id":106148},"adding-spring-security","Adding Spring Security",[651,106151,106152],{},"Add the Spring Security dependency manually to your project configuration:",[669,106154,106156],{"className":9101,"code":106155,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-security\u003C/artifactId>\n\u003C/dependency>\n\n",[676,106157,106158,106162,106166,106171],{"__ignoreMap":674},[679,106159,106160],{"class":681,"line":682},[679,106161,9110],{},[679,106163,106164],{"class":681,"line":790},[679,106165,9115],{},[679,106167,106168],{"class":681,"line":892},[679,106169,106170],{}," \u003CartifactId>spring-boot-starter-security\u003C/artifactId>\n",[679,106172,106173],{"class":681,"line":901},[679,106174,9125],{},[651,106176,106177],{},"Refresh your Maven dependencies, then restart the application. You'll notice you now need to authenticate to access the H2 console (with a randomly generated password and the username \"user\").",[4542,106179,106181],{"id":106180},"configuring-spring-security","Configuring Spring Security",[651,106183,106184],{},"In this tutorial, we'll create multiple Spring Security configurations for different authorization scenarios:",[27665,106186,106187,106190,106193],{},[5332,106188,106189],{},"Configuration for the H2 console",[5332,106191,106192],{},"Configuration for securing the API with HTTP Basic authentication",[5332,106194,106195],{},"Configuration for securing the private URL with form login",[651,106197,106198,106199,106201,106202,106204,106205,23212,106207,106209,106210,106212],{},"Let's start by creating a ",[676,106200,89455],{}," class within a ",[676,106203,64430],{}," package, and annotating it with ",[676,106206,40076],{},[676,106208,6139],{},". We will define beans of type ",[676,106211,95305],{}," for each scenario.",[669,106214,106216],{"className":4107,"code":106215,"language":4109,"meta":674,"style":674},"@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n}\n",[676,106217,106218,106224,106230,106240,106244],{"__ignoreMap":674},[679,106219,106220,106222],{"class":681,"line":682},[679,106221,4116],{"class":693},[679,106223,6212],{"class":685},[679,106225,106226,106228],{"class":681,"line":790},[679,106227,4116],{"class":693},[679,106229,89474],{"class":685},[679,106231,106232,106234,106236,106238],{"class":681,"line":892},[679,106233,6073],{"class":685},[679,106235,4512],{"class":685},[679,106237,89483],{"class":880},[679,106239,884],{"class":693},[679,106241,106242],{"class":681,"line":901},[679,106243,889],{"emptyLinePlaceholder":797},[679,106245,106246],{"class":681,"line":909},[679,106247,996],{"class":693},[5909,106249,106251],{"id":106250},"api-security-filter-chain","API Security Filter Chain",[651,106253,106254,106255,106258,106259,106261,106262,106265],{},"Create an ",[676,106256,106257],{},"apiSecurityFilterChain"," bean that also takes in an ",[676,106260,95396],{}," object. This time, the configuration should ensure that any request for ",[676,106263,106264],{},"/api/**"," requires authentication, with HTTP basic authentication and session management disabled.",[651,106267,106268,106269,106272,106273,664],{},"Remember to add another ",[676,106270,106271],{},"@Order"," annotation here, for example ",[676,106274,106275],{},"@Order(1)",[669,106277,106279],{"className":4107,"code":106278,"language":4109,"meta":674,"style":674},"@Bean\n@Order(1)\nSecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {\n return http\n .securityMatcher(\"/api/**\")\n .authorizeHttpRequests(auth -> {\n auth.anyRequest().authenticated();\n })\n .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))\n .httpBasic(withDefaults())\n .build();\n}\n",[676,106280,106281,106287,106299,106308,106314,106328,106340,106353,106358,106374,106386,106394],{"__ignoreMap":674},[679,106282,106283,106285],{"class":681,"line":682},[679,106284,4116],{"class":693},[679,106286,16929],{"class":685},[679,106288,106289,106291,106293,106295,106297],{"class":681,"line":790},[679,106290,4116],{"class":693},[679,106292,17055],{"class":685},[679,106294,745],{"class":693},[679,106296,1557],{"class":931},[679,106298,1339],{"class":693},[679,106300,106301,106304,106306],{"class":681,"line":892},[679,106302,106303],{"class":693},"SecurityFilterChain ",[679,106305,106257],{"class":880},[679,106307,89943],{"class":693},[679,106309,106310,106312],{"class":681,"line":901},[679,106311,21478],{"class":685},[679,106313,89524],{"class":693},[679,106315,106316,106318,106321,106323,106326],{"class":681,"line":909},[679,106317,40148],{"class":693},[679,106319,106320],{"class":880},"securityMatcher",[679,106322,745],{"class":693},[679,106324,106325],{"class":689},"\"/api/**\"",[679,106327,1339],{"class":693},[679,106329,106330,106332,106334,106336,106338],{"class":681,"line":918},[679,106331,40148],{"class":693},[679,106333,95392],{"class":880},[679,106335,95465],{"class":693},[679,106337,16955],{"class":685},[679,106339,884],{"class":693},[679,106341,106342,106345,106347,106349,106351],{"class":681,"line":935},[679,106343,106344],{"class":693}," auth.",[679,106346,89569],{"class":880},[679,106348,10541],{"class":693},[679,106350,89574],{"class":880},[679,106352,9317],{"class":693},[679,106354,106355],{"class":681,"line":944},[679,106356,106357],{"class":693}," })\n",[679,106359,106360,106362,106364,106366,106368,106370,106372],{"class":681,"line":959},[679,106361,40148],{"class":693},[679,106363,89591],{"class":880},[679,106365,89594],{"class":693},[679,106367,16955],{"class":685},[679,106369,89599],{"class":693},[679,106371,89602],{"class":880},[679,106373,90027],{"class":693},[679,106375,106376,106378,106380,106382,106384],{"class":681,"line":964},[679,106377,40148],{"class":693},[679,106379,89615],{"class":880},[679,106381,745],{"class":693},[679,106383,89621],{"class":880},[679,106385,40172],{"class":693},[679,106387,106388,106390,106392],{"class":681,"line":977},[679,106389,40148],{"class":693},[679,106391,23612],{"class":880},[679,106393,9317],{"class":693},[679,106395,106396],{"class":681,"line":982},[679,106397,996],{"class":693},[5909,106399,106401],{"id":106400},"h2-console-security-filter-chain","H2 Console Security Filter Chain",[651,106403,77744,106404,106407,106408,106410,106411,106414],{},[676,106405,106406],{},"h2ConsoleSecurityFilterChain"," bean that takes in an ",[676,106409,95396],{}," object. Ensure that any request for ",[676,106412,106413],{},"/h2-console/**"," is permitted, CSRF protection is ignored for H2 console, and frame options are disabled.",[651,106416,106417,106418,106420,106421,106424,106425,78287],{},"You'll also need to add an ",[676,106419,106271],{}," annotation here to ensure that this security configuration is processed in the correct order. For example, add ",[676,106422,106423],{},"@Order(2)"," as an annotation on your ",[676,106426,106406],{},[669,106428,106430],{"className":4107,"code":106429,"language":4109,"meta":674,"style":674},"@Bean\n@Order(2)\nSecurityFilterChain h2ConsoleSecurityFilterChain(HttpSecurity http) throws Exception {\n return http\n .securityMatcher(AntPathRequestMatcher.antMatcher(\"/h2-console/**\"))\n .authorizeHttpRequests( auth -> {\n auth.requestMatchers(AntPathRequestMatcher.antMatcher(\"/h2-console/**\")).permitAll();\n })\n .csrf(csrf -> csrf.ignoringRequestMatchers(AntPathRequestMatcher.antMatcher(\"/h2-console/**\")))\n .headers(headers -> headers.frameOptions().disable())\n .build();\n}\n",[676,106431,106432,106438,106450,106458,106464,106483,106495,106515,106519,106544,106568,106576],{"__ignoreMap":674},[679,106433,106434,106436],{"class":681,"line":682},[679,106435,4116],{"class":693},[679,106437,16929],{"class":685},[679,106439,106440,106442,106444,106446,106448],{"class":681,"line":790},[679,106441,4116],{"class":693},[679,106443,17055],{"class":685},[679,106445,745],{"class":693},[679,106447,17262],{"class":931},[679,106449,1339],{"class":693},[679,106451,106452,106454,106456],{"class":681,"line":892},[679,106453,106303],{"class":693},[679,106455,106406],{"class":880},[679,106457,89943],{"class":693},[679,106459,106460,106462],{"class":681,"line":901},[679,106461,21478],{"class":685},[679,106463,89524],{"class":693},[679,106465,106466,106468,106470,106473,106476,106478,106481],{"class":681,"line":909},[679,106467,40148],{"class":693},[679,106469,106320],{"class":880},[679,106471,106472],{"class":693},"(AntPathRequestMatcher.",[679,106474,106475],{"class":880},"antMatcher",[679,106477,745],{"class":693},[679,106479,106480],{"class":689},"\"/h2-console/**\"",[679,106482,40143],{"class":693},[679,106484,106485,106487,106489,106491,106493],{"class":681,"line":918},[679,106486,40148],{"class":693},[679,106488,95392],{"class":880},[679,106490,89556],{"class":693},[679,106492,16955],{"class":685},[679,106494,884],{"class":693},[679,106496,106497,106499,106501,106503,106505,106507,106509,106511,106513],{"class":681,"line":935},[679,106498,106344],{"class":693},[679,106500,40124],{"class":880},[679,106502,106472],{"class":693},[679,106504,106475],{"class":880},[679,106506,745],{"class":693},[679,106508,106480],{"class":689},[679,106510,65060],{"class":693},[679,106512,40151],{"class":880},[679,106514,9317],{"class":693},[679,106516,106517],{"class":681,"line":944},[679,106518,106357],{"class":693},[679,106520,106521,106523,106525,106527,106529,106531,106534,106536,106538,106540,106542],{"class":681,"line":959},[679,106522,40148],{"class":693},[679,106524,89531],{"class":880},[679,106526,89534],{"class":693},[679,106528,16955],{"class":685},[679,106530,89539],{"class":693},[679,106532,106533],{"class":880},"ignoringRequestMatchers",[679,106535,106472],{"class":693},[679,106537,106475],{"class":880},[679,106539,745],{"class":693},[679,106541,106480],{"class":689},[679,106543,91607],{"class":693},[679,106545,106546,106548,106551,106554,106556,106559,106562,106564,106566],{"class":681,"line":964},[679,106547,40148],{"class":693},[679,106549,106550],{"class":880},"headers",[679,106552,106553],{"class":693},"(headers ",[679,106555,16955],{"class":685},[679,106557,106558],{"class":693}," headers.",[679,106560,106561],{"class":880},"frameOptions",[679,106563,10541],{"class":693},[679,106565,89542],{"class":880},[679,106567,40172],{"class":693},[679,106569,106570,106572,106574],{"class":681,"line":977},[679,106571,40148],{"class":693},[679,106573,23612],{"class":880},[679,106575,9317],{"class":693},[679,106577,106578],{"class":681,"line":982},[679,106579,996],{"class":693},[5909,106581,106583],{"id":106582},"form-login-security-filter-chain","Form Login Security Filter Chain",[651,106585,93264,106586,106407,106589,106591],{},[676,106587,106588],{},"formLoginSecurityFilterChain",[676,106590,95396],{}," object. In this configuration, permit access to the root URL (\"/\") without authentication, and authenticate any other request with form login.",[651,106593,77499,106594,106597],{},[676,106595,106596],{},"@Order(3)"," annotation to ensure that this security configuration is processed last.",[669,106599,106601],{"className":4107,"code":106600,"language":4109,"meta":674,"style":674},"@Bean\n@Order(3)\nSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http\n .authorizeHttpRequests(auth -> {\n auth.requestMatchers(\"/\").permitAll();\n auth.requestMatchers(\"/error\").permitAll();\n auth.anyRequest().authenticated();\n }\n )\n .formLogin(withDefaults())\n .build();\n}\n",[676,106602,106603,106609,106621,106629,106635,106647,106664,106681,106693,106698,106702,106714,106722],{"__ignoreMap":674},[679,106604,106605,106607],{"class":681,"line":682},[679,106606,4116],{"class":693},[679,106608,16929],{"class":685},[679,106610,106611,106613,106615,106617,106619],{"class":681,"line":790},[679,106612,4116],{"class":693},[679,106614,17055],{"class":685},[679,106616,745],{"class":693},[679,106618,66599],{"class":931},[679,106620,1339],{"class":693},[679,106622,106623,106625,106627],{"class":681,"line":892},[679,106624,106303],{"class":693},[679,106626,89505],{"class":880},[679,106628,89943],{"class":693},[679,106630,106631,106633],{"class":681,"line":901},[679,106632,21478],{"class":685},[679,106634,89524],{"class":693},[679,106636,106637,106639,106641,106643,106645],{"class":681,"line":909},[679,106638,40148],{"class":693},[679,106640,95392],{"class":880},[679,106642,95465],{"class":693},[679,106644,16955],{"class":685},[679,106646,884],{"class":693},[679,106648,106649,106652,106654,106656,106658,106660,106662],{"class":681,"line":918},[679,106650,106651],{"class":693}," auth.",[679,106653,40124],{"class":880},[679,106655,745],{"class":693},[679,106657,10032],{"class":689},[679,106659,47213],{"class":693},[679,106661,40151],{"class":880},[679,106663,9317],{"class":693},[679,106665,106666,106668,106670,106672,106675,106677,106679],{"class":681,"line":935},[679,106667,106651],{"class":693},[679,106669,40124],{"class":880},[679,106671,745],{"class":693},[679,106673,106674],{"class":689},"\"/error\"",[679,106676,47213],{"class":693},[679,106678,40151],{"class":880},[679,106680,9317],{"class":693},[679,106682,106683,106685,106687,106689,106691],{"class":681,"line":944},[679,106684,106651],{"class":693},[679,106686,89569],{"class":880},[679,106688,10541],{"class":693},[679,106690,89574],{"class":880},[679,106692,9317],{"class":693},[679,106694,106695],{"class":681,"line":959},[679,106696,106697],{"class":693}," }\n",[679,106699,106700],{"class":681,"line":964},[679,106701,89994],{"class":693},[679,106703,106704,106706,106708,106710,106712],{"class":681,"line":977},[679,106705,40148],{"class":693},[679,106707,95511],{"class":880},[679,106709,745],{"class":693},[679,106711,89621],{"class":880},[679,106713,40172],{"class":693},[679,106715,106716,106718,106720],{"class":681,"line":982},[679,106717,40148],{"class":693},[679,106719,23612],{"class":880},[679,106721,9317],{"class":693},[679,106723,106724],{"class":681,"line":988},[679,106725,996],{"class":693},[5909,106727,106729],{"id":106728},"adding-security-matchers","Adding Security Matchers",[651,106731,106732,106733,106736],{},"To ensure that each security filter chain is only invoked for the matching pattern, add an ",[676,106734,106735],{},"@SecurityMatcher"," annotation to each bean:",[5316,106738,106739,106745,106751],{},[5332,106740,106741,106742],{},"For the H2 Console filter chain, use an Ant matcher: ",[676,106743,106744],{},"@SecurityMatcher(AntPathRequestMatcher(\"/h2-console/**\"))",[5332,106746,106747,106748],{},"For the API filter chain, use an MVC matcher: ",[676,106749,106750],{},"@SecurityMatcher(MvcRequestMatcher(\"/api/**\"))",[5332,106752,106753,106754,106756],{},"For the Form Login filter chain, you can omit the ",[676,106755,106735],{}," as it will only be invoked if none of the other matchers apply.",[651,106758,106759],{},"Now, run the application again and test each scenario:",[27665,106761,106762,106767,106773],{},[5332,106763,106764,106765],{},"Access the H2 console without authentication: ",[676,106766,16733],{},[5332,106768,106769,106770,106772],{},"Call the ",[676,106771,93279],{}," endpoint and authenticate using HTTP Basic",[5332,106774,106775,106776,106779],{},"Access the ",[676,106777,106778],{},"/private"," URL and authenticate using Form login",[651,106781,106782],{},"You should find that each security configuration works as expected.",[4542,106784,9042],{"id":9041},[651,106786,106787,106788,2797,106790,106792],{},"In this tutorial, we covered how to create multiple Spring Security configurations for different authorization scenarios. This approach can help keep your security configurations clean and focused, making them easier to understand and maintain. By leveraging Spring Security's ",[676,106789,106271],{},[676,106791,106735],{},", and other features, you can easily build complex and secure applications.",[651,106794,78024],{},[786,106796,99666],{},{"title":674,"searchDepth":790,"depth":790,"links":106798},[106799,106800,106801,106802,106808],{"id":54715,"depth":790,"text":95749},{"id":105672,"depth":790,"text":105673},{"id":106148,"depth":790,"text":106149},{"id":106180,"depth":790,"text":106181,"children":106803},[106804,106805,106806,106807],{"id":106250,"depth":892,"text":106251},{"id":106400,"depth":892,"text":106401},{"id":106582,"depth":892,"text":106583},{"id":106728,"depth":892,"text":106729},{"id":9041,"depth":790,"text":9042},"In this tutorial, we will discuss how to create multiple Spring Security configurations and why you might want to do so.",{"slug":106811,"date":106812,"published":797,"author":798,"tags":106813,"cover":106814,"video":106815,"github":106816,"keywords":106817},"multiple-spring-security-configs","2023-04-20T08:00:00.000Z",[23988],"./spring-security-multiple-configurations.png","https://www.youtube.com/embed/PczgM2L3w60","https://github.com/danvega/ssc","Spring Security, Spring Boot, Java, Spring Security Config, Spring Security Multiple Configurations",{"title":318,"description":106809},"blog/2023/04/20/multiple-spring-security-configs","r_adtSfRZAtBDdMVgLj_c8ezcP4AYNZbzNtCxy_anVI",{"id":106822,"title":315,"body":106823,"description":107344,"extension":793,"meta":107345,"navigation":797,"path":316,"seo":107353,"stem":107354,"__hash__":107355},"content/blog/2023/04/26/spring-boot-docker-compose.md",{"type":648,"value":106824,"toc":107338},[106825,106828,106833,106836,106840,106847,106861,106866,106869,106873,106879,106887,106964,106974,107009,107017,107144,107148,107151,107157,107261,107264,107273,107300,107306,107319,107325,107327,107330,107333,107335],[651,106826,106827],{},"In this article we're talking all about Spring Boot 3.1 RC1, which has just been released with many great features. In particular, we're going to focus on the ability to use Docker Compose files directly with Spring Boot applications.",[651,106829,106830,106831,664],{},"This isn't a new feature per se, but it comes with some significant improvements over previous versions. One of the major enhancements is that the Spring Boot Docker Compose Module will start and stop your containers automatically. Additionally, you no longer need to duplicate properties between your Docker Compose file and ",[676,106832,16242],{},[651,106834,106835],{},"In this blog post, I'll show you how to get started with this exciting new feature in Spring Boot 3.1 RC1. Let's begin with a demo!",[4542,106837,106839],{"id":106838},"setting-up-a-spring-boot-31-project","Setting up a Spring Boot 3.1 Project",[651,106841,106842,106843,106846],{},"First, let's set up a basic Spring Boot 3.1 project using ",[812,106844,7117],{"href":7115,"rel":106845},[816],". Here's a quick overview of the options we're going to select:",[5316,106848,106849,106852,106855,106858],{},[5332,106850,106851],{},"Maven Project",[5332,106853,106854],{},"3.1.0 RC1 version",[5332,106856,106857],{},"Java 17",[5332,106859,106860],{},"Dependencies: Web, Spring Data JPA, PostgreSQL Driver",[651,106862,106863],{},[660,106864],{"alt":7117,"src":106865},"/images/blog/2023/04/26/start-spring-io.png",[651,106867,106868],{},"With these options selected, click \"Generate\" and extract the downloaded zip file. Open the extracted project in your favorite IDE or editor (I'm using IntelliJ IDEA Ultimate Edition).",[4542,106870,106872],{"id":106871},"creating-a-simple-blog-application","Creating a Simple Blog Application",[651,106874,106875,106876,106878],{},"For this demo, let's create a simple blog application with a single ",[676,106877,5105],{}," entity and a RESTful API to fetch all posts.",[651,106880,106881,106882,89334,106884,106886],{},"Start by creating a Java class called ",[676,106883,5105],{},[676,106885,10048],{}," package:",[669,106888,106890],{"className":4107,"code":106889,"language":4109,"meta":674,"style":674},"@Entity\npublic class Post {\n\n @Id\n @GeneratedValue\n private Integer id;\n\n private String title;\n\n private String body;\n\n // Constructors, getters, setters, and toString() methods go here\n}\n",[676,106891,106892,106898,106908,106912,106918,106924,106930,106934,106940,106944,106951,106955,106960],{"__ignoreMap":674},[679,106893,106894,106896],{"class":681,"line":682},[679,106895,4116],{"class":693},[679,106897,11234],{"class":685},[679,106899,106900,106902,106904,106906],{"class":681,"line":790},[679,106901,6073],{"class":685},[679,106903,4512],{"class":685},[679,106905,4658],{"class":880},[679,106907,884],{"class":693},[679,106909,106910],{"class":681,"line":892},[679,106911,889],{"emptyLinePlaceholder":797},[679,106913,106914,106916],{"class":681,"line":901},[679,106915,6872],{"class":693},[679,106917,33530],{"class":685},[679,106919,106920,106922],{"class":681,"line":909},[679,106921,6872],{"class":693},[679,106923,11261],{"class":685},[679,106925,106926,106928],{"class":681,"line":918},[679,106927,9232],{"class":685},[679,106929,83952],{"class":693},[679,106931,106932],{"class":681,"line":935},[679,106933,889],{"emptyLinePlaceholder":797},[679,106935,106936,106938],{"class":681,"line":944},[679,106937,9232],{"class":685},[679,106939,93032],{"class":693},[679,106941,106942],{"class":681,"line":959},[679,106943,889],{"emptyLinePlaceholder":797},[679,106945,106946,106948],{"class":681,"line":964},[679,106947,9232],{"class":685},[679,106949,106950],{"class":693}," String body;\n",[679,106952,106953],{"class":681,"line":977},[679,106954,889],{"emptyLinePlaceholder":797},[679,106956,106957],{"class":681,"line":982},[679,106958,106959],{"class":1400}," // Constructors, getters, setters, and toString() methods go here\n",[679,106961,106962],{"class":681,"line":988},[679,106963,996],{"class":693},[651,106965,106966,106967,89334,106969,105145,106971,2391],{},"Next, create a new interface called ",[676,106968,23351],{},[676,106970,16596],{},[676,106972,106973],{},"CrudRepository\u003CPost, Integer>",[669,106975,106977],{"className":4107,"code":106976,"language":4109,"meta":674,"style":674},"public interface PostRepository extends CrudRepository\u003CPost, Integer> {\n\n}\n",[676,106978,106979,107001,107005],{"__ignoreMap":674},[679,106980,106981,106983,106985,106987,106989,106991,106993,106995,106997,106999],{"class":681,"line":682},[679,106982,6073],{"class":685},[679,106984,6994],{"class":685},[679,106986,93243],{"class":880},[679,106988,2767],{"class":685},[679,106990,16385],{"class":880},[679,106992,4505],{"class":693},[679,106994,5105],{"class":685},[679,106996,2797],{"class":693},[679,106998,1083],{"class":685},[679,107000,16397],{"class":693},[679,107002,107003],{"class":681,"line":790},[679,107004,889],{"emptyLinePlaceholder":797},[679,107006,107007],{"class":681,"line":892},[679,107008,996],{"class":693},[651,107010,107011,107012,102926,107014,107016],{},"Now, let's create a ",[676,107013,93270],{},[676,107015,4279],{}," package with a single GET endpoint to fetch all posts:",[669,107018,107020],{"className":4107,"code":107019,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private final PostRepository repository;\n\n public PostController(PostRepository repository) {\n this.repository = repository;\n }\n\n @GetMapping\n public List\u003CPost> findAll() {\n return (List\u003CPost>) repository.findAll();\n }\n}\n",[676,107021,107022,107028,107040,107050,107054,107062,107066,107078,107088,107092,107096,107102,107116,107136,107140],{"__ignoreMap":674},[679,107023,107024,107026],{"class":681,"line":682},[679,107025,4116],{"class":693},[679,107027,9212],{"class":685},[679,107029,107030,107032,107034,107036,107038],{"class":681,"line":790},[679,107031,4116],{"class":693},[679,107033,9275],{"class":685},[679,107035,745],{"class":693},[679,107037,23141],{"class":689},[679,107039,1339],{"class":693},[679,107041,107042,107044,107046,107048],{"class":681,"line":892},[679,107043,6073],{"class":685},[679,107045,4512],{"class":685},[679,107047,23152],{"class":880},[679,107049,884],{"class":693},[679,107051,107052],{"class":681,"line":901},[679,107053,889],{"emptyLinePlaceholder":797},[679,107055,107056,107058,107060],{"class":681,"line":909},[679,107057,9232],{"class":685},[679,107059,12768],{"class":685},[679,107061,105935],{"class":693},[679,107063,107064],{"class":681,"line":918},[679,107065,889],{"emptyLinePlaceholder":797},[679,107067,107068,107070,107072,107074,107076],{"class":681,"line":935},[679,107069,6089],{"class":685},[679,107071,23152],{"class":880},[679,107073,23405],{"class":693},[679,107075,16596],{"class":2099},[679,107077,4390],{"class":693},[679,107079,107080,107082,107084,107086],{"class":681,"line":944},[679,107081,7862],{"class":931},[679,107083,16605],{"class":693},[679,107085,686],{"class":685},[679,107087,16610],{"class":693},[679,107089,107090],{"class":681,"line":959},[679,107091,985],{"class":693},[679,107093,107094],{"class":681,"line":964},[679,107095,889],{"emptyLinePlaceholder":797},[679,107097,107098,107100],{"class":681,"line":977},[679,107099,6872],{"class":693},[679,107101,80415],{"class":685},[679,107103,107104,107106,107108,107110,107112,107114],{"class":681,"line":982},[679,107105,6089],{"class":685},[679,107107,87217],{"class":693},[679,107109,5105],{"class":685},[679,107111,20881],{"class":693},[679,107113,34142],{"class":880},[679,107115,2667],{"class":693},[679,107117,107118,107120,107123,107125,107127,107129,107132,107134],{"class":681,"line":988},[679,107119,9444],{"class":685},[679,107121,107122],{"class":693}," (List",[679,107124,4505],{"class":685},[679,107126,5105],{"class":693},[679,107128,5860],{"class":685},[679,107130,107131],{"class":693},") repository.",[679,107133,34142],{"class":880},[679,107135,9317],{"class":693},[679,107137,107138],{"class":681,"line":993},[679,107139,985],{"class":693},[679,107141,107142],{"class":681,"line":2129},[679,107143,996],{"class":693},[4542,107145,107147],{"id":107146},"adding-docker-compose","Adding Docker Compose",[651,107149,107150],{},"At this point, we've got a basic application with a single REST endpoint to fetch all posts. Now, let's add Docker Compose for spinning up a PostgreSQL container as our database.",[651,107152,75826,107153,107156],{},[676,107154,107155],{},"docker-compose.yml"," in the project's root directory:",[669,107158,107160],{"className":67476,"code":107159,"language":56308,"meta":674,"style":674},"version: \"3.1\"\n\nservices:\n db:\n image: postgres\n restart: always\n ports:\n - \"5432:5432\"\n environment:\n POSTGRES_USER: postgres\n POSTGRES_PASSWORD: password\n POSTGRES_DB: blog\n",[676,107161,107162,107172,107176,107183,107190,107200,107210,107217,107225,107232,107241,107251],{"__ignoreMap":674},[679,107163,107164,107167,107169],{"class":681,"line":682},[679,107165,107166],{"class":4508},"version",[679,107168,4282],{"class":693},[679,107170,107171],{"class":689},"\"3.1\"\n",[679,107173,107174],{"class":681,"line":790},[679,107175,889],{"emptyLinePlaceholder":797},[679,107177,107178,107181],{"class":681,"line":892},[679,107179,107180],{"class":4508},"services",[679,107182,3327],{"class":693},[679,107184,107185,107188],{"class":681,"line":901},[679,107186,107187],{"class":4508}," db",[679,107189,3327],{"class":693},[679,107191,107192,107195,107197],{"class":681,"line":909},[679,107193,107194],{"class":4508}," image",[679,107196,4282],{"class":693},[679,107198,107199],{"class":689},"postgres\n",[679,107201,107202,107205,107207],{"class":681,"line":918},[679,107203,107204],{"class":4508}," restart",[679,107206,4282],{"class":693},[679,107208,107209],{"class":689},"always\n",[679,107211,107212,107215],{"class":681,"line":935},[679,107213,107214],{"class":4508}," ports",[679,107216,3327],{"class":693},[679,107218,107219,107222],{"class":681,"line":944},[679,107220,107221],{"class":693}," - ",[679,107223,107224],{"class":689},"\"5432:5432\"\n",[679,107226,107227,107230],{"class":681,"line":959},[679,107228,107229],{"class":4508}," environment",[679,107231,3327],{"class":693},[679,107233,107234,107237,107239],{"class":681,"line":964},[679,107235,107236],{"class":4508}," POSTGRES_USER",[679,107238,4282],{"class":693},[679,107240,107199],{"class":689},[679,107242,107243,107246,107248],{"class":681,"line":977},[679,107244,107245],{"class":4508}," POSTGRES_PASSWORD",[679,107247,4282],{"class":693},[679,107249,107250],{"class":689},"password\n",[679,107252,107253,107256,107258],{"class":681,"line":982},[679,107254,107255],{"class":4508}," POSTGRES_DB",[679,107257,4282],{"class":693},[679,107259,107260],{"class":689},"blog\n",[651,107262,107263],{},"Now that we have a Docker Compose file in place, we need to make a couple of changes to enable auto-start/stop of containers and avoid duplicating properties.",[651,107265,107266,107267,107270,107271,2391],{},"First, add the ",[676,107268,107269],{},"spring-boot-starter-docker-compose"," dependency in your ",[676,107272,81517],{},[669,107274,107276],{"className":9101,"code":107275,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-docker-compose\u003C/artifactId>\n \u003Cversion>3.1.0-RC1\u003C/version>\n\u003C/dependency>\n\n",[676,107277,107278,107282,107286,107291,107296],{"__ignoreMap":674},[679,107279,107280],{"class":681,"line":682},[679,107281,9110],{},[679,107283,107284],{"class":681,"line":790},[679,107285,9115],{},[679,107287,107288],{"class":681,"line":892},[679,107289,107290],{}," \u003CartifactId>spring-boot-starter-docker-compose\u003C/artifactId>\n",[679,107292,107293],{"class":681,"line":901},[679,107294,107295],{}," \u003Cversion>3.1.0-RC1\u003C/version>\n",[679,107297,107298],{"class":681,"line":909},[679,107299,9125],{},[651,107301,107302,107303,107305],{},"Then, in your ",[676,107304,16242],{}," file, add the following line to enable auto-generation of DDL statements for JPA:",[669,107307,107309],{"className":76589,"code":107308,"language":35538,"meta":674,"style":674},"spring.jpa.hibernate.ddl-auto=create\n",[676,107310,107311],{"__ignoreMap":674},[679,107312,107313,107316],{"class":681,"line":682},[679,107314,107315],{"class":685},"spring.jpa.hibernate.ddl-auto",[679,107317,107318],{"class":693},"=create\n",[651,107320,107321,107322,107324],{},"Now, you're ready to run your application! When you start the Spring Boot application, it will automatically create the PostgreSQL container and set up the connection using the properties defined in the ",[676,107323,107155],{}," file. Additionally, no duplicated properties are required, making this a much cleaner solution.",[4542,107326,9042],{"id":9041},[651,107328,107329],{},"In this blog post, we've taken a look at one of the exciting new features in Spring Boot 3.1 RC1 – simplified Docker Compose usage. This new feature allows developers to auto-start/stop containers and avoid duplicating properties, which makes for a better overall development experience.",[651,107331,107332],{},"If you found value in this tutorial, please consider subscribing to my newsletter for more content like this. As always…",[651,107334,44143],{},[786,107336,107337],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":107339},[107340,107341,107342,107343],{"id":106838,"depth":790,"text":106839},{"id":106871,"depth":790,"text":106872},{"id":107146,"depth":790,"text":107147},{"id":9041,"depth":790,"text":9042},"In this tutorial you will learn about the new Spring Boot Docker Compose Module. This module allows you to use Docker Compose files directly with Spring Boot applications.",{"slug":107346,"date":107347,"published":797,"author":798,"tags":107348,"cover":107349,"video":107350,"github":107351,"keywords":107352},"spring-boot-docker-compose","2023-04-26T08:00:00.000Z",[7077],"./spring-boot-docker-compose.png","https://www.youtube.com/embed/lS1GwdIfk0c","https://github.com/danvega/dc-demo","Spring Boot Docker Compose, Spring Boot 3.1, Spring Boot, Java, Docker Compose",{"title":315,"description":107344},"blog/2023/04/26/spring-boot-docker-compose","x02EQbHKpnpF4Fz2mmeOIuRc6ZXD-XReCa2-jjSM8YE",{"id":107357,"title":312,"body":107358,"description":107919,"extension":793,"meta":107920,"navigation":797,"path":313,"seo":107928,"stem":107929,"__hash__":107930},"content/blog/2023/04/28/spring-security-oauth2-login.md",{"type":648,"value":107359,"toc":107907},[107360,107363,107365,107368,107371,107408,107413,107416,107420,107426,107531,107534,107536,107543,107710,107716,107720,107726,107794,107810,107814,107838,107842,107874,107876,107883,107891,107893,107896,107904],[651,107361,107362],{},"In this tutorial, we will discuss how to set up OAuth 2 logins in Spring Security with Spring Boot. OAuth 2 logins are a convenient way to let your users log in via social media (GitHub, Google, Twitter) without needing to register on your system.",[4542,107364,44199],{"id":44198},[651,107366,107367],{},"You should be familiar with Java, Spring Boot, and Spring Security. Optionally, you should know how to use IntelliJ IDEA, but you can use any IDE of your choice.",[4542,107369,107370],{"id":94280},"Setting up the Project",[27665,107372,107373,107379,107382,107387,107393,107396,107405],{},[5332,107374,107375,107376,99709],{},"Visit ",[812,107377,77478],{"href":7115,"rel":107378},[816],[5332,107380,107381],{},"Select Maven as your build tool and Java as your language.",[5332,107383,107384,107385,664],{},"Change the group to something meaningful. For example, ",[676,107386,92925],{},[5332,107388,107389,107390,664],{},"Name your project, like ",[676,107391,107392],{},"SocialLogin",[5332,107394,107395],{},"Choose JDK 17 (or the latest available).",[5332,107397,107398,107399,23212,107401,107404],{},"Add dependencies: ",[676,107400,92944],{},[676,107402,107403],{},"oauth2-client",". Spring Security will be added as a transitive dependency.",[5332,107406,107407],{},"Generate and download the project. This will provide a zip file.",[651,107409,107410],{},[660,107411],{"alt":94303,"src":107412},"/images/blog/2023/04/28/start-spring-io.png",[651,107414,107415],{},"Unzip the project and open it in your favorite IDE or text editor. I'll use IntelliJ IDEA Ultimate.",[4542,107417,107419],{"id":107418},"creating-a-controller","Creating a Controller",[651,107421,107422,107423,107425],{},"Start by creating a ",[676,107424,89333],{}," class, where we'll set up both public and private routes.",[669,107427,107429],{"className":4107,"code":107428,"language":4109,"meta":674,"style":674},"@RestController\npublic class HomeController {\n\n @GetMapping(\"/\")\n public String home() {\n return \"Hello, home!\";\n }\n\n @GetMapping(\"/secured\")\n public String secured() {\n return \"Hello, secured!\";\n }\n}\n",[676,107430,107431,107437,107447,107451,107463,107473,107482,107486,107490,107503,107514,107523,107527],{"__ignoreMap":674},[679,107432,107433,107435],{"class":681,"line":682},[679,107434,4116],{"class":693},[679,107436,9212],{"class":685},[679,107438,107439,107441,107443,107445],{"class":681,"line":790},[679,107440,6073],{"class":685},[679,107442,4512],{"class":685},[679,107444,18716],{"class":880},[679,107446,884],{"class":693},[679,107448,107449],{"class":681,"line":892},[679,107450,889],{"emptyLinePlaceholder":797},[679,107452,107453,107455,107457,107459,107461],{"class":681,"line":901},[679,107454,6872],{"class":693},[679,107456,20852],{"class":685},[679,107458,745],{"class":693},[679,107460,10032],{"class":689},[679,107462,1339],{"class":693},[679,107464,107465,107467,107469,107471],{"class":681,"line":909},[679,107466,6089],{"class":685},[679,107468,9289],{"class":693},[679,107470,12642],{"class":880},[679,107472,2667],{"class":693},[679,107474,107475,107477,107480],{"class":681,"line":918},[679,107476,9444],{"class":685},[679,107478,107479],{"class":689}," \"Hello, home!\"",[679,107481,1186],{"class":693},[679,107483,107484],{"class":681,"line":935},[679,107485,985],{"class":693},[679,107487,107488],{"class":681,"line":944},[679,107489,889],{"emptyLinePlaceholder":797},[679,107491,107492,107494,107496,107498,107501],{"class":681,"line":959},[679,107493,6872],{"class":693},[679,107495,20852],{"class":685},[679,107497,745],{"class":693},[679,107499,107500],{"class":689},"\"/secured\"",[679,107502,1339],{"class":693},[679,107504,107505,107507,107509,107512],{"class":681,"line":964},[679,107506,6089],{"class":685},[679,107508,9289],{"class":693},[679,107510,107511],{"class":880},"secured",[679,107513,2667],{"class":693},[679,107515,107516,107518,107521],{"class":681,"line":977},[679,107517,9444],{"class":685},[679,107519,107520],{"class":689}," \"Hello, secured!\"",[679,107522,1186],{"class":693},[679,107524,107525],{"class":681,"line":982},[679,107526,985],{"class":693},[679,107528,107529],{"class":681,"line":988},[679,107530,996],{"class":693},[651,107532,107533],{},"With these routes in place, we can now set up our security configuration.",[4542,107535,106181],{"id":106180},[651,107537,107538,107539,102926,107541,106886],{},"First, create a new ",[676,107540,89455],{},[676,107542,64430],{},[669,107544,107546],{"className":4107,"code":107545,"language":4109,"meta":674,"style":674},"@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n @Bean\n SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http\n .authorizeHttpRequests(auth -> {\n auth.requestMatchers(\"/\").permitAll();\n auth.requestMatchers(\"/favicon.ico\").permitAll();\n auth.anyRequest().authenticated();\n })\n .oauth2Login(withDefaults())\n .formLogin(withDefaults())\n .build();\n }\n\n}\n",[676,107547,107548,107554,107560,107570,107574,107580,107597,107603,107615,107631,107648,107660,107665,107678,107690,107698,107702,107706],{"__ignoreMap":674},[679,107549,107550,107552],{"class":681,"line":682},[679,107551,4116],{"class":693},[679,107553,6212],{"class":685},[679,107555,107556,107558],{"class":681,"line":790},[679,107557,4116],{"class":693},[679,107559,89474],{"class":685},[679,107561,107562,107564,107566,107568],{"class":681,"line":892},[679,107563,6073],{"class":685},[679,107565,4512],{"class":685},[679,107567,89483],{"class":880},[679,107569,884],{"class":693},[679,107571,107572],{"class":681,"line":901},[679,107573,889],{"emptyLinePlaceholder":797},[679,107575,107576,107578],{"class":681,"line":909},[679,107577,6872],{"class":693},[679,107579,16929],{"class":685},[679,107581,107582,107585,107587,107589,107591,107593,107595],{"class":681,"line":918},[679,107583,107584],{"class":693}," SecurityFilterChain ",[679,107586,89505],{"class":880},[679,107588,89508],{"class":693},[679,107590,89511],{"class":2099},[679,107592,2378],{"class":693},[679,107594,9580],{"class":685},[679,107596,10466],{"class":693},[679,107598,107599,107601],{"class":681,"line":935},[679,107600,9444],{"class":685},[679,107602,89524],{"class":693},[679,107604,107605,107607,107609,107611,107613],{"class":681,"line":944},[679,107606,73482],{"class":693},[679,107608,95392],{"class":880},[679,107610,95465],{"class":693},[679,107612,16955],{"class":685},[679,107614,884],{"class":693},[679,107616,107617,107619,107621,107623,107625,107627,107629],{"class":681,"line":959},[679,107618,106651],{"class":693},[679,107620,40124],{"class":880},[679,107622,745],{"class":693},[679,107624,10032],{"class":689},[679,107626,47213],{"class":693},[679,107628,40151],{"class":880},[679,107630,9317],{"class":693},[679,107632,107633,107635,107637,107639,107642,107644,107646],{"class":681,"line":964},[679,107634,106651],{"class":693},[679,107636,40124],{"class":880},[679,107638,745],{"class":693},[679,107640,107641],{"class":689},"\"/favicon.ico\"",[679,107643,47213],{"class":693},[679,107645,40151],{"class":880},[679,107647,9317],{"class":693},[679,107649,107650,107652,107654,107656,107658],{"class":681,"line":977},[679,107651,106651],{"class":693},[679,107653,89569],{"class":880},[679,107655,10541],{"class":693},[679,107657,89574],{"class":880},[679,107659,9317],{"class":693},[679,107661,107662],{"class":681,"line":982},[679,107663,107664],{"class":693}," })\n",[679,107666,107667,107669,107672,107674,107676],{"class":681,"line":988},[679,107668,73482],{"class":693},[679,107670,107671],{"class":880},"oauth2Login",[679,107673,745],{"class":693},[679,107675,89621],{"class":880},[679,107677,40172],{"class":693},[679,107679,107680,107682,107684,107686,107688],{"class":681,"line":993},[679,107681,73482],{"class":693},[679,107683,95511],{"class":880},[679,107685,745],{"class":693},[679,107687,89621],{"class":880},[679,107689,40172],{"class":693},[679,107691,107692,107694,107696],{"class":681,"line":2129},[679,107693,73482],{"class":693},[679,107695,23612],{"class":880},[679,107697,9317],{"class":693},[679,107699,107700],{"class":681,"line":2140},[679,107701,985],{"class":693},[679,107703,107704],{"class":681,"line":2145},[679,107705,889],{"emptyLinePlaceholder":797},[679,107707,107708],{"class":681,"line":2154},[679,107709,996],{"class":693},[651,107711,107712,107713,107715],{},"Here, we configured Spring Security to allow anyone to access the ",[676,107714,4408],{}," route, but require authentication for any other route. We've enabled both form login and OAuth2 login as authentication methods.",[4542,107717,107719],{"id":107718},"setting-up-oauth2-providers","Setting up OAuth2 Providers",[651,107721,107722,107723,107725],{},"To set up OAuth2 providers, add the following properties to your ",[676,107724,16242],{}," file.",[669,107727,107729],{"className":76589,"code":107728,"language":35538,"meta":674,"style":674},"# Logging level\nlogging.level.org.springframework.security=TRACE\n\n# GitHub\nspring.security.oauth2.client.registration.github.client-id=\u003Cyour-github-client-id>\nspring.security.oauth2.client.registration.github.client-secret=\u003Cyour-github-client-secret>\n\n# Google\nspring.security.oauth2.client.registration.google.client-id=\u003Cyour-google-client-id>\nspring.security.oauth2.client.registration.google.client-secret=\u003Cyour-google-client-secret>\n\n",[676,107730,107731,107736,107744,107748,107753,107761,107769,107773,107778,107786],{"__ignoreMap":674},[679,107732,107733],{"class":681,"line":682},[679,107734,107735],{"class":1400},"# Logging level\n",[679,107737,107738,107741],{"class":681,"line":790},[679,107739,107740],{"class":685},"logging.level.org.springframework.security",[679,107742,107743],{"class":693},"=TRACE\n",[679,107745,107746],{"class":681,"line":892},[679,107747,889],{"emptyLinePlaceholder":797},[679,107749,107750],{"class":681,"line":901},[679,107751,107752],{"class":1400},"# GitHub\n",[679,107754,107755,107758],{"class":681,"line":909},[679,107756,107757],{"class":685},"spring.security.oauth2.client.registration.github.client-id",[679,107759,107760],{"class":693},"=\u003Cyour-github-client-id>\n",[679,107762,107763,107766],{"class":681,"line":918},[679,107764,107765],{"class":685},"spring.security.oauth2.client.registration.github.client-secret",[679,107767,107768],{"class":693},"=\u003Cyour-github-client-secret>\n",[679,107770,107771],{"class":681,"line":935},[679,107772,889],{"emptyLinePlaceholder":797},[679,107774,107775],{"class":681,"line":944},[679,107776,107777],{"class":1400},"# Google\n",[679,107779,107780,107783],{"class":681,"line":959},[679,107781,107782],{"class":685},"spring.security.oauth2.client.registration.google.client-id",[679,107784,107785],{"class":693},"=\u003Cyour-google-client-id>\n",[679,107787,107788,107791],{"class":681,"line":964},[679,107789,107790],{"class":685},"spring.security.oauth2.client.registration.google.client-secret",[679,107792,107793],{"class":693},"=\u003Cyour-google-client-secret>\n",[651,107795,107796,107797,2797,107800,2797,107803,48406,107806,107809],{},"Note: You'll need to replace ",[676,107798,107799],{},"\u003Cyour-github-client-id>",[676,107801,107802],{},"\u003Cyour-github-client-secret>",[676,107804,107805],{},"\u003Cyour-google-client-id>",[676,107807,107808],{},"\u003Cyour-google-client-secret>"," with the corresponding values from your OAuth providers.",[5909,107811,107813],{"id":107812},"setting-up-github-as-an-oauth-provider","Setting up GitHub as an OAuth Provider",[27665,107815,107816,107819,107822,107828],{},[5332,107817,107818],{},"Go to your GitHub account settings, and navigate to Developer settings > OAuth Apps.",[5332,107820,107821],{},"Click \"New OAuth App\" and fill in the required fields.",[5332,107823,107824,107825,664],{},"For the \"Authorization callback URL,\" use: ",[676,107826,107827],{},"http://localhost:8080/login/oauth2/code/github",[5332,107829,107830,107831,23212,107834,107837],{},"Register the application, and you'll receive a ",[676,107832,107833],{},"client_id",[676,107835,107836],{},"client_secret"," for your properties file.",[5909,107839,107841],{"id":107840},"setting-up-google-as-an-oauth-provider","Setting up Google as an OAuth Provider",[27665,107843,107844,107852,107855,107858,107861,107867],{},[5332,107845,107846,107847,664],{},"Visit the Google Cloud Console at ",[812,107848,107851],{"href":107849,"rel":107850},"https://console.cloud.google.com/",[816],"console.cloud.google.com",[5332,107853,107854],{},"Set up the OAuth 2 consent screen if you haven't done so already.",[5332,107856,107857],{},"Go to the Credentials section and create a new OAuth 2 client ID.",[5332,107859,107860],{},"Select \"Web application\" as the type, and enter a name for the application.",[5332,107862,107863,107864,664],{},"Add an authorized redirect URI: ",[676,107865,107866],{},"http://localhost:8080/login/oauth2/code/google",[5332,107868,107869,107870,23212,107872,107837],{},"Click \"Create\" to obtain your ",[676,107871,107833],{},[676,107873,107836],{},[4542,107875,93754],{"id":93753},[651,107877,107878,107879,107882],{},"Run your Spring Boot application and visit ",[812,107880,32924],{"href":32924,"rel":107881},[816]," in your browser. You should see the public \"Hello, home!\" message.",[651,107884,107885,107886,107890],{},"Now, navigate to ",[812,107887,107888],{"href":107888,"rel":107889},"http://localhost:8080/secured",[816],", and you will be prompted to log in via form login or the OAuth providers you configured (GitHub and Google). Log in using one of those providers, and you will be redirected to the secured \"Hello, secured!\" page.",[4542,107892,9042],{"id":9041},[651,107894,107895],{},"In this blog post, we demonstrated how easy it is to set up OAuth 2 logins in Spring Security with Spring Boot, allowing your users to authenticate via social media platforms like GitHub and Google without having to register on your system.",[651,107897,107898,107899,664],{},"If you're interested in customizing the login page or want further information about configuring OAuth2 properties, you can refer to the ",[812,107900,107903],{"href":107901,"rel":107902},"https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2",[816],"Spring Security documentation",[786,107905,107906],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":107908},[107909,107910,107911,107912,107913,107917,107918],{"id":44198,"depth":790,"text":44199},{"id":94280,"depth":790,"text":107370},{"id":107418,"depth":790,"text":107419},{"id":106180,"depth":790,"text":106181},{"id":107718,"depth":790,"text":107719,"children":107914},[107915,107916],{"id":107812,"depth":892,"text":107813},{"id":107840,"depth":892,"text":107841},{"id":93753,"depth":790,"text":93754},{"id":9041,"depth":790,"text":9042},"In this tutorial, we will discuss how to set up OAuth 2 logins in Spring Security with Spring Boot.",{"slug":107921,"date":107922,"published":797,"author":798,"tags":107923,"cover":107924,"video":107925,"github":107926,"keywords":107927},"spring-security-oauth2-login","2023-04-28T08:00:00.000Z",[23988],"./spring-security-oauth2-login.png","https://www.youtube.com/embed/us0VjFiHogo","https://github.com/danvega/spring-security-social-login","Spring Boot, Spring Security, OAuth2, OAuth2 Login, OAuth2 Login, Spring Boot OAuth2",{"title":312,"description":107919},"blog/2023/04/28/spring-security-oauth2-login","aTYQ4w732b0VUJ7xDQCW33yh7P8AxKhGG7dwrGeuQjo",{"id":107932,"title":309,"body":107933,"description":108514,"extension":793,"meta":108515,"navigation":797,"path":310,"seo":108523,"stem":108524,"__hash__":108525},"content/blog/2023/05/03/spring-session-introduction.md",{"type":648,"value":107934,"toc":108507},[107935,107939,107942,107953,107956,107963,107967,107973,107987,107992,107995,107999,108007,108090,108097,108100,108103,108106,108110,108113,108116,108161,108164,108212,108215,108237,108243,108483,108486,108488,108491,108494,108502,108504],[4542,107936,107938],{"id":107937},"understanding-spring-session-and-its-benefits","Understanding Spring Session and its Benefits",[651,107940,107941],{},"In this tutorial, we're going to discuss:",[5316,107943,107944,107947,107950],{},[5332,107945,107946],{},"What is a session on the web?",[5332,107948,107949],{},"What is Spring Session?",[5332,107951,107952],{},"Why might you want to use it in your next Spring Boot application?",[651,107954,107955],{},"This discussion was prompted by a recent episode of the Spring Office Hours podcast, where Spring Session was the featured topic. In this blog post, we'll explore some of the benefits that Spring Session brings to the table.",[651,107957,107958,107959,107962],{},"To understand Spring Session better, let's create a new Spring Boot application using ",[812,107960,7117],{"href":51358,"rel":107961},[816],". We'll begin with a simple application that features web security and gradually include additional dependencies such as Spring Session and Redis support.",[4542,107964,107966],{"id":107965},"creating-a-spring-boot-application","Creating a Spring Boot Application",[651,107968,107969,107970,107972],{},"Start by creating a new project at ",[676,107971,77478],{},". Choose Java as the language, Maven as the build tool, and Spring Boot 3.1 (or any later version). Next, add the following dependencies:",[5316,107974,107975,107978,107980,107982,107984],{},[5332,107976,107977],{},"Spring Boot 3.1",[5332,107979,72926],{},[5332,107981,23988],{},[5332,107983,24011],{},[5332,107985,107986],{},"Spring Data Redis",[651,107988,107989],{},[660,107990],{"alt":94303,"src":107991},"/images/blog/2023/05/03/start-spring-io.png",[651,107993,107994],{},"Before diving into how Spring Session can help with managing user session information, let's comment out the Spring Session and Spring Data references and build a basic application without them.",[4542,107996,107998],{"id":107997},"default-behavior-of-spring-security","Default Behavior of Spring Security",[651,108000,77744,108001,108003,108004,108006],{},[676,108002,89333],{}," with a ",[676,108005,83485],{}," method that simply returns a message containing the name of the currently logged-in user.",[669,108008,108010],{"className":4107,"code":108009,"language":4109,"meta":674,"style":674},"@RestController\npublic class HomeController {\n\n @GetMapping(\"/\")\n public String home(Principal principal, HttpSession session) {\n return \"Hello, \" + principal.getName();\n }\n\n}\n",[676,108011,108012,108018,108028,108032,108044,108064,108078,108082,108086],{"__ignoreMap":674},[679,108013,108014,108016],{"class":681,"line":682},[679,108015,4116],{"class":693},[679,108017,9212],{"class":685},[679,108019,108020,108022,108024,108026],{"class":681,"line":790},[679,108021,6073],{"class":685},[679,108023,4512],{"class":685},[679,108025,18716],{"class":880},[679,108027,884],{"class":693},[679,108029,108030],{"class":681,"line":892},[679,108031,889],{"emptyLinePlaceholder":797},[679,108033,108034,108036,108038,108040,108042],{"class":681,"line":901},[679,108035,6872],{"class":693},[679,108037,20852],{"class":685},[679,108039,745],{"class":693},[679,108041,10032],{"class":689},[679,108043,1339],{"class":693},[679,108045,108046,108048,108050,108052,108054,108056,108059,108062],{"class":681,"line":909},[679,108047,6089],{"class":685},[679,108049,9289],{"class":693},[679,108051,12642],{"class":880},[679,108053,89393],{"class":693},[679,108055,89396],{"class":2099},[679,108057,108058],{"class":693},", HttpSession ",[679,108060,108061],{"class":2099},"session",[679,108063,4390],{"class":693},[679,108065,108066,108068,108070,108072,108074,108076],{"class":681,"line":918},[679,108067,9444],{"class":685},[679,108069,89405],{"class":689},[679,108071,3059],{"class":685},[679,108073,89410],{"class":693},[679,108075,10577],{"class":880},[679,108077,9317],{"class":693},[679,108079,108080],{"class":681,"line":935},[679,108081,985],{"class":693},[679,108083,108084],{"class":681,"line":944},[679,108085,889],{"emptyLinePlaceholder":797},[679,108087,108088],{"class":681,"line":959},[679,108089,996],{"class":693},[651,108091,108092,108093,108096],{},"Once the application is running, navigate to ",[812,108094,32924],{"href":32924,"rel":108095},[816],". You'll be presented with a form login, which is the default security behavior provided by Spring Security.",[651,108098,108099],{},"However, there is a problem with the default session handling. When the application is restarted, you will have to log in again, as the previous session data is lost. This is because the session is tied to the application server (in our case, Tomcat) and is not persisted across server restarts.",[651,108101,108102],{},"This can be even more problematic in a horizontally scaled environment where multiple instances of an application are running. In such cases, a user's session might not be available if they're connected to a different instance of the application.",[651,108104,108105],{},"This is where Spring Session comes into play.",[4542,108107,108109],{"id":108108},"introducing-spring-session-and-redis","Introducing Spring Session and Redis",[651,108111,108112],{},"Spring Session provides a solution for managing user session information in a way that is not tied to the application server. In our example, we'll use Redis as a data store for session management.",[651,108114,108115],{},"First, uncomment the lines that include the Spring Session and Spring Data Redis dependencies:",[669,108117,108119],{"className":9101,"code":108118,"language":9103,"meta":674,"style":674},"\u003C!-- Spring Session -->\n\u003Cdependency>\n \u003CgroupId>org.springframework.session\u003C/groupId>\n \u003CartifactId>spring-session-data-redis\u003C/artifactId>\n\u003C/dependency>\n\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-data-redis\u003C/artifactId>\n\u003C/dependency>\n\n",[676,108120,108121,108126,108130,108135,108140,108144,108148,108152,108157],{"__ignoreMap":674},[679,108122,108123],{"class":681,"line":682},[679,108124,108125],{},"\u003C!-- Spring Session -->\n",[679,108127,108128],{"class":681,"line":790},[679,108129,9110],{},[679,108131,108132],{"class":681,"line":892},[679,108133,108134],{}," \u003CgroupId>org.springframework.session\u003C/groupId>\n",[679,108136,108137],{"class":681,"line":901},[679,108138,108139],{}," \u003CartifactId>spring-session-data-redis\u003C/artifactId>\n",[679,108141,108142],{"class":681,"line":909},[679,108143,9125],{},[679,108145,108146],{"class":681,"line":918},[679,108147,9110],{},[679,108149,108150],{"class":681,"line":935},[679,108151,9115],{},[679,108153,108154],{"class":681,"line":944},[679,108155,108156],{}," \u003CartifactId>spring-boot-starter-data-redis\u003C/artifactId>\n",[679,108158,108159],{"class":681,"line":959},[679,108160,9125],{},[651,108162,108163],{},"Next, we'll set up a Docker Compose file to run a Redis container:",[669,108165,108167],{"className":67476,"code":108166,"language":56308,"meta":674,"style":674},"version: \"3.1\"\nservices:\n redis:\n image: redis:alpine\n ports:\n - \"6379:6379\"\n",[676,108168,108169,108177,108183,108190,108199,108205],{"__ignoreMap":674},[679,108170,108171,108173,108175],{"class":681,"line":682},[679,108172,107166],{"class":4508},[679,108174,4282],{"class":693},[679,108176,107171],{"class":689},[679,108178,108179,108181],{"class":681,"line":790},[679,108180,107180],{"class":4508},[679,108182,3327],{"class":693},[679,108184,108185,108188],{"class":681,"line":892},[679,108186,108187],{"class":4508}," redis",[679,108189,3327],{"class":693},[679,108191,108192,108194,108196],{"class":681,"line":901},[679,108193,107194],{"class":4508},[679,108195,4282],{"class":693},[679,108197,108198],{"class":689},"redis:alpine\n",[679,108200,108201,108203],{"class":681,"line":909},[679,108202,107214],{"class":4508},[679,108204,3327],{"class":693},[679,108206,108207,108209],{"class":681,"line":918},[679,108208,107221],{"class":693},[679,108210,108211],{"class":689},"\"6379:6379\"\n",[651,108213,108214],{},"With this configuration in place, our Spring Boot application will automatically start the Redis container when it starts up. This allows us to persist session data across application restarts and share it across multiple instances.Depending on the version of Spring Boot you're on you might need to add the following dependency.",[669,108216,108218],{"className":9101,"code":108217,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-docker-compose\u003C/artifactId>\n\u003C/dependency>\n",[676,108219,108220,108224,108228,108233],{"__ignoreMap":674},[679,108221,108222],{"class":681,"line":682},[679,108223,9110],{},[679,108225,108226],{"class":681,"line":790},[679,108227,40818],{},[679,108229,108230],{"class":681,"line":892},[679,108231,108232],{}," \u003CartifactId>spring-boot-docker-compose\u003C/artifactId>\n",[679,108234,108235],{"class":681,"line":901},[679,108236,9125],{},[651,108238,108239,108240,108242],{},"Modify the ",[676,108241,89333],{}," to track a user's session and display a view counter that increments each time the user accesses the home page. The view counter's value should be stored as a session attribute.",[669,108244,108246],{"className":4107,"code":108245,"language":4109,"meta":674,"style":674},"@RestController\npublic class HomeController {\n\n private final String HOME_VIEW_COUNT = \"home_view_count\";\n\n @GetMapping(\"/\")\n public String home(Principal principal, HttpSession session) {\n incrementCount(session,HOME_VIEW_COUNT);\n return \"Hello, \" + principal.getName();\n }\n\n @GetMapping(\"/count\")\n public String count(HttpSession session) {\n return \"HOME_VIEW_COUNT: \" + session.getAttribute(\"HOME_VIEW_COUNT\");\n }\n\n private void incrementCount(HttpSession session, String attr) {\n var homeViewCount = session.getAttribute(HOME_VIEW_COUNT) == null ? 0 : (Integer) session.getAttribute(HOME_VIEW_COUNT);\n session.setAttribute(attr,homeViewCount + 1);\n }\n}\n",[676,108247,108248,108254,108264,108268,108284,108288,108300,108318,108326,108340,108344,108348,108361,108377,108397,108401,108405,108425,108459,108475,108479],{"__ignoreMap":674},[679,108249,108250,108252],{"class":681,"line":682},[679,108251,4116],{"class":693},[679,108253,9212],{"class":685},[679,108255,108256,108258,108260,108262],{"class":681,"line":790},[679,108257,6073],{"class":685},[679,108259,4512],{"class":685},[679,108261,18716],{"class":880},[679,108263,884],{"class":693},[679,108265,108266],{"class":681,"line":892},[679,108267,889],{"emptyLinePlaceholder":797},[679,108269,108270,108272,108274,108277,108279,108282],{"class":681,"line":901},[679,108271,9232],{"class":685},[679,108273,12768],{"class":685},[679,108275,108276],{"class":693}," String HOME_VIEW_COUNT ",[679,108278,686],{"class":685},[679,108280,108281],{"class":689}," \"home_view_count\"",[679,108283,1186],{"class":693},[679,108285,108286],{"class":681,"line":909},[679,108287,889],{"emptyLinePlaceholder":797},[679,108289,108290,108292,108294,108296,108298],{"class":681,"line":918},[679,108291,6872],{"class":693},[679,108293,20852],{"class":685},[679,108295,745],{"class":693},[679,108297,10032],{"class":689},[679,108299,1339],{"class":693},[679,108301,108302,108304,108306,108308,108310,108312,108314,108316],{"class":681,"line":935},[679,108303,6089],{"class":685},[679,108305,9289],{"class":693},[679,108307,12642],{"class":880},[679,108309,89393],{"class":693},[679,108311,89396],{"class":2099},[679,108313,108058],{"class":693},[679,108315,108061],{"class":2099},[679,108317,4390],{"class":693},[679,108319,108320,108323],{"class":681,"line":944},[679,108321,108322],{"class":880}," incrementCount",[679,108324,108325],{"class":693},"(session,HOME_VIEW_COUNT);\n",[679,108327,108328,108330,108332,108334,108336,108338],{"class":681,"line":959},[679,108329,9444],{"class":685},[679,108331,89405],{"class":689},[679,108333,3059],{"class":685},[679,108335,89410],{"class":693},[679,108337,10577],{"class":880},[679,108339,9317],{"class":693},[679,108341,108342],{"class":681,"line":964},[679,108343,985],{"class":693},[679,108345,108346],{"class":681,"line":977},[679,108347,889],{"emptyLinePlaceholder":797},[679,108349,108350,108352,108354,108356,108359],{"class":681,"line":982},[679,108351,6872],{"class":693},[679,108353,20852],{"class":685},[679,108355,745],{"class":693},[679,108357,108358],{"class":689},"\"/count\"",[679,108360,1339],{"class":693},[679,108362,108363,108365,108367,108370,108373,108375],{"class":681,"line":988},[679,108364,6089],{"class":685},[679,108366,9289],{"class":693},[679,108368,108369],{"class":880},"count",[679,108371,108372],{"class":693},"(HttpSession ",[679,108374,108061],{"class":2099},[679,108376,4390],{"class":693},[679,108378,108379,108381,108384,108386,108388,108390,108392,108395],{"class":681,"line":993},[679,108380,9444],{"class":685},[679,108382,108383],{"class":689}," \"HOME_VIEW_COUNT: \"",[679,108385,3059],{"class":685},[679,108387,89599],{"class":693},[679,108389,73950],{"class":880},[679,108391,745],{"class":693},[679,108393,108394],{"class":689},"\"HOME_VIEW_COUNT\"",[679,108396,1208],{"class":693},[679,108398,108399],{"class":681,"line":2129},[679,108400,985],{"class":693},[679,108402,108403],{"class":681,"line":2140},[679,108404,889],{"emptyLinePlaceholder":797},[679,108406,108407,108409,108411,108414,108416,108418,108420,108423],{"class":681,"line":2145},[679,108408,9232],{"class":685},[679,108410,6095],{"class":685},[679,108412,108413],{"class":880}," incrementCount",[679,108415,108372],{"class":693},[679,108417,108061],{"class":2099},[679,108419,20006],{"class":693},[679,108421,108422],{"class":2099},"attr",[679,108424,4390],{"class":693},[679,108426,108427,108429,108432,108434,108436,108438,108441,108443,108445,108447,108449,108451,108454,108456],{"class":681,"line":2154},[679,108428,94003],{"class":685},[679,108430,108431],{"class":693}," homeViewCount ",[679,108433,686],{"class":685},[679,108435,89599],{"class":693},[679,108437,73950],{"class":880},[679,108439,108440],{"class":693},"(HOME_VIEW_COUNT) ",[679,108442,2304],{"class":685},[679,108444,2307],{"class":931},[679,108446,14615],{"class":685},[679,108448,14987],{"class":931},[679,108450,3033],{"class":685},[679,108452,108453],{"class":693}," (Integer) session.",[679,108455,73950],{"class":880},[679,108457,108458],{"class":693},"(HOME_VIEW_COUNT);\n",[679,108460,108461,108464,108466,108469,108471,108473],{"class":681,"line":2159},[679,108462,108463],{"class":693}," session.",[679,108465,47216],{"class":880},[679,108467,108468],{"class":693},"(attr,homeViewCount ",[679,108470,3065],{"class":685},[679,108472,48606],{"class":931},[679,108474,1208],{"class":693},[679,108476,108477],{"class":681,"line":2164},[679,108478,985],{"class":693},[679,108480,108481],{"class":681,"line":3134},[679,108482,996],{"class":693},[651,108484,108485],{},"Now, run the application, log in, and observe that the view counter correctly increments and that the session persists across application restarts.",[4542,108487,78006],{"id":78005},[651,108489,108490],{},"Spring Session provides a powerful way to manage user session information in Spring Boot applications, allowing for more scalable and resilient applications. In our example, we used Redis as the data store, but other options such as JDBC and Hazelcast are also supported.",[651,108492,108493],{},"If you're using Spring Security in your application, it's worth considering integrating Spring Session to improve the overall user experience. However, if you're building a stateless application (such as a REST API), Spring Session might not be necessary.",[651,108495,108496,108497,664],{},"To learn more about Spring Session, take a look at the ",[812,108498,108501],{"href":108499,"rel":108500},"https://docs.spring.io/spring-session/docs/current/reference/html5/",[816],"official documentation",[651,108503,78024],{},[786,108505,108506],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":108508},[108509,108510,108511,108512,108513],{"id":107937,"depth":790,"text":107938},{"id":107965,"depth":790,"text":107966},{"id":107997,"depth":790,"text":107998},{"id":108108,"depth":790,"text":108109},{"id":78005,"depth":790,"text":78006},"In this tutorial, we'll explore the powerful features of Spring Session and learn how to effectively manage user sessions in your Java web applications.",{"slug":108516,"date":108517,"published":797,"author":798,"tags":108518,"cover":108519,"video":108520,"github":108521,"keywords":108522},"spring-session-introduction","2023-05-03T08:00:00.000Z",[23988],"./spring-session-thumbnail.png","https://www.youtube.com/embed/k62bO-W6Sb0","https://github.com/danvega/session","Spring Boot, Spring Security, Spring Session",{"title":309,"description":108514},"blog/2023/05/03/spring-session-introduction","vBPxWn8PLjisKfbedhwex7lzpocLZt--EA1efKESzv8",{"id":108527,"title":306,"body":108528,"description":109063,"extension":793,"meta":109064,"navigation":797,"path":307,"seo":109071,"stem":109072,"__hash__":109073},"content/blog/2023/05/19/aws-lambda-java-17.md",{"type":648,"value":108529,"toc":109057},[108530,108533,108536,108540,108543,108546,108552,108555,108558,108573,108576,108612,108615,108758,108764,108852,108864,108873,108877,108880,108886,108889,108892,108898,108901,108904,108908,108916,108919,108924,108933,108988,108996,109019,109028,109030,109033,109049,109055],[651,108531,108532],{},"AWS has made a big announcement recently: Java 17 runtime is now supported on AWS Lambda. What this means is, you now have the freedom to write your lambda functions using Java in Spring and specifically target JDK 17.",[651,108534,108535],{},"This announcement comes with a significant improvement as the base requirement for Spring Boot 3 is at least Java 17. So, prior to this, we couldn't use Spring Boot 3 for writing our serverless functions. But now, we can! Let's get started with writing some code!",[4542,108537,108539],{"id":108538},"creating-a-new-project-using-java","Creating a New Project Using Java",[651,108541,108542],{},"I will first show you how to develop a new project utilizing Java only. Let's use the AWS Core Lambda library to create a new AWS Lambda function in Java and send the code to AWS Lambda. Fair warning, Java 17 works beautifully with AWS Lambda, but let's see it in action.",[651,108544,108545],{},"First, I will create a new project in my IntelliJ IDE (although, you can work with any IDE or text editor of your choice). This new project will be named 'Hello, Java 17' and I will save it in my downloads folder.",[651,108547,108548],{},[660,108549],{"alt":108550,"src":108551},"Hello Java 17 Project in IntelliJ","/images/blog/2023/05/19/hello-java-17-pom.png",[651,108553,108554],{},"This Java project is a Maven project that uses the Coretto 17 version of JDK. My group ID will be 'dev.danvega' and the artifact will be 'hello-java-17'. On creating this project, I will open it in IntelliJ.",[651,108556,108557],{},"Here is a cool thing. IntelliJ IDE automatically sets the Maven compiler source and target to Java 17. This is a nice a time-saver.",[669,108559,108561],{"className":9101,"code":108560,"language":9103,"meta":674,"style":674},"\u003Cmaven.compiler.source>17\u003C/maven.compiler.source>\n\u003Cmaven.compiler.target>17\u003C/maven.compiler.target>\n",[676,108562,108563,108568],{"__ignoreMap":674},[679,108564,108565],{"class":681,"line":682},[679,108566,108567],{},"\u003Cmaven.compiler.source>17\u003C/maven.compiler.source>\n",[679,108569,108570],{"class":681,"line":790},[679,108571,108572],{},"\u003Cmaven.compiler.target>17\u003C/maven.compiler.target>\n",[651,108574,108575],{},"Next, let's add a dependency block. This block will come from AWS Lambda Java Core.",[669,108577,108579],{"className":9101,"code":108578,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>com.amazonaws\u003C/groupId>\n \u003CartifactId>aws-lambda-java-core\u003C/artifactId>\n \u003Cversion>1.2.1\u003C/version>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,108580,108581,108585,108589,108594,108599,108604,108608],{"__ignoreMap":674},[679,108582,108583],{"class":681,"line":682},[679,108584,19199],{},[679,108586,108587],{"class":681,"line":790},[679,108588,19204],{},[679,108590,108591],{"class":681,"line":892},[679,108592,108593],{}," \u003CgroupId>com.amazonaws\u003C/groupId>\n",[679,108595,108596],{"class":681,"line":901},[679,108597,108598],{}," \u003CartifactId>aws-lambda-java-core\u003C/artifactId>\n",[679,108600,108601],{"class":681,"line":909},[679,108602,108603],{}," \u003Cversion>1.2.1\u003C/version>\n",[679,108605,108606],{"class":681,"line":918},[679,108607,19219],{},[679,108609,108610],{"class":681,"line":935},[679,108611,19363],{},[651,108613,108614],{},"On refreshing Maven, you will notice that everything is now in place and we're ready to write some code. We can create a new Java class named 'SimpleHandler'. Here, I will implement the request handler from AWS and create methods to get the inputs.",[669,108616,108618],{"className":4107,"code":108617,"language":4109,"meta":674,"style":674},"package dev.danvega.hj17;\n\nimport com.amazonaws.services.lambda.runtime.Context;\nimport com.amazonaws.services.lambda.runtime.LambdaLogger;\nimport com.amazonaws.services.lambda.runtime.RequestHandler;\n\npublic class SimpleHandler implements RequestHandler\u003CString,String> {\n\n public String handleRequest(String input, Context context) {\n LambdaLogger logger = context.getLogger();\n logger.log(\"JDK Version: \" + System.getProperty(\"java.version\"));\n return input.toUpperCase();\n }\n\n}\n",[676,108619,108620,108627,108631,108637,108643,108649,108653,108675,108679,108697,108710,108736,108746,108750,108754],{"__ignoreMap":674},[679,108621,108622,108624],{"class":681,"line":682},[679,108623,2543],{"class":685},[679,108625,108626],{"class":693}," dev.danvega.hj17;\n",[679,108628,108629],{"class":681,"line":790},[679,108630,889],{"emptyLinePlaceholder":797},[679,108632,108633,108635],{"class":681,"line":892},[679,108634,1999],{"class":685},[679,108636,94421],{"class":693},[679,108638,108639,108641],{"class":681,"line":901},[679,108640,1999],{"class":685},[679,108642,94711],{"class":693},[679,108644,108645,108647],{"class":681,"line":909},[679,108646,1999],{"class":685},[679,108648,94428],{"class":693},[679,108650,108651],{"class":681,"line":918},[679,108652,889],{"emptyLinePlaceholder":797},[679,108654,108655,108657,108659,108661,108663,108665,108667,108669,108671,108673],{"class":681,"line":935},[679,108656,6073],{"class":685},[679,108658,4512],{"class":685},[679,108660,94441],{"class":880},[679,108662,4661],{"class":685},[679,108664,94446],{"class":880},[679,108666,4505],{"class":693},[679,108668,4758],{"class":685},[679,108670,1202],{"class":693},[679,108672,4758],{"class":685},[679,108674,16397],{"class":693},[679,108676,108677],{"class":681,"line":944},[679,108678,889],{"emptyLinePlaceholder":797},[679,108680,108681,108683,108685,108687,108689,108691,108693,108695],{"class":681,"line":959},[679,108682,6089],{"class":685},[679,108684,9289],{"class":693},[679,108686,93867],{"class":880},[679,108688,11400],{"class":693},[679,108690,27722],{"class":2099},[679,108692,94481],{"class":693},[679,108694,94484],{"class":2099},[679,108696,4390],{"class":693},[679,108698,108699,108702,108704,108706,108708],{"class":681,"line":964},[679,108700,108701],{"class":693}," LambdaLogger logger ",[679,108703,686],{"class":685},[679,108705,94587],{"class":693},[679,108707,9243],{"class":880},[679,108709,9317],{"class":693},[679,108711,108712,108714,108716,108718,108721,108723,108726,108729,108731,108734],{"class":681,"line":977},[679,108713,12851],{"class":693},[679,108715,21374],{"class":880},[679,108717,745],{"class":693},[679,108719,108720],{"class":689},"\"JDK Version: \"",[679,108722,3059],{"class":685},[679,108724,108725],{"class":693}," System.",[679,108727,108728],{"class":880},"getProperty",[679,108730,745],{"class":693},[679,108732,108733],{"class":689},"\"java.version\"",[679,108735,1669],{"class":693},[679,108737,108738,108740,108742,108744],{"class":681,"line":982},[679,108739,9444],{"class":685},[679,108741,15632],{"class":693},[679,108743,94628],{"class":880},[679,108745,9317],{"class":693},[679,108747,108748],{"class":681,"line":988},[679,108749,985],{"class":693},[679,108751,108752],{"class":681,"line":993},[679,108753,889],{"emptyLinePlaceholder":797},[679,108755,108756],{"class":681,"line":2129},[679,108757,996],{"class":693},[651,108759,108760,108761,664],{},"With the SimpleHandler created, we can use Maven to create the Java Archive (.jar) that we will then upload to AWS as a Lambda function. To do that, build the Maven Shade plugin by inputting the command - ",[676,108762,108763],{},"clean package",[669,108765,108767],{"className":9101,"code":108766,"language":9103,"meta":674,"style":674},"\u003Cbuild>\n \u003Cplugins>\n \u003Cplugin>\n \u003CgroupId>org.apache.maven.plugins\u003C/groupId>\n \u003CartifactId>maven-shade-plugin\u003C/artifactId>\n \u003Cversion>3.2.4\u003C/version>\n \u003Cconfiguration>\n \u003CcreateDependencyReducedPom>false\u003C/createDependencyReducedPom>\n \u003C/configuration>\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cphase>package\u003C/phase>\n \u003Cgoals>\n \u003Cgoal>shade\u003C/goal>\n \u003C/goals>\n \u003C/execution>\n \u003C/executions>\n \u003C/plugin>\n \u003C/plugins>\n\u003C/build>\n",[676,108768,108769,108773,108777,108781,108785,108789,108793,108798,108803,108808,108812,108816,108820,108824,108828,108832,108836,108840,108844,108848],{"__ignoreMap":674},[679,108770,108771],{"class":681,"line":682},[679,108772,40689],{},[679,108774,108775],{"class":681,"line":790},[679,108776,95155],{},[679,108778,108779],{"class":681,"line":892},[679,108780,40699],{},[679,108782,108783],{"class":681,"line":901},[679,108784,95164],{},[679,108786,108787],{"class":681,"line":909},[679,108788,95169],{},[679,108790,108791],{"class":681,"line":918},[679,108792,95174],{},[679,108794,108795],{"class":681,"line":935},[679,108796,108797],{}," \u003Cconfiguration>\n",[679,108799,108800],{"class":681,"line":944},[679,108801,108802],{}," \u003CcreateDependencyReducedPom>false\u003C/createDependencyReducedPom>\n",[679,108804,108805],{"class":681,"line":959},[679,108806,108807],{}," \u003C/configuration>\n",[679,108809,108810],{"class":681,"line":964},[679,108811,95179],{},[679,108813,108814],{"class":681,"line":977},[679,108815,81610],{},[679,108817,108818],{"class":681,"line":982},[679,108819,95188],{},[679,108821,108822],{"class":681,"line":988},[679,108823,95193],{},[679,108825,108826],{"class":681,"line":993},[679,108827,95198],{},[679,108829,108830],{"class":681,"line":2129},[679,108831,95203],{},[679,108833,108834],{"class":681,"line":2140},[679,108835,81660],{},[679,108837,108838],{"class":681,"line":2145},[679,108839,95212],{},[679,108841,108842],{"class":681,"line":2154},[679,108843,40713],{},[679,108845,108846],{"class":681,"line":2159},[679,108847,95221],{},[679,108849,108850],{"class":681,"line":2164},[679,108851,40776],{},[669,108853,108854],{"className":5851,"code":81915,"language":5853,"meta":674,"style":674},[676,108855,108856],{"__ignoreMap":674},[679,108857,108858,108860,108862],{"class":681,"line":682},[679,108859,81922],{"class":880},[679,108861,81925],{"class":689},[679,108863,32496],{"class":689},[651,108865,40060,108866,108869,108870,108872],{},[676,108867,108868],{},"clean"," command cleans the Maven project by deleting the target directory. The ",[676,108871,2543],{}," command, on the other hand, packages compiled source code into its distributable format. The jar will be saved in the target directory as 'hello-java-17-1.0-SNAPSHOT.jar'.",[4542,108874,108876],{"id":108875},"uploading-the-jar-to-aws-lambda","Uploading the Jar to AWS Lambda",[651,108878,108879],{},"Now that we have our jar ready, we can go ahead and upload it to AWS Lambda. Let's create a new function from scratch and name it 'Hello, Java 17'. The exciting part here is the runtime selection. Here, you can choose Java 17.",[651,108881,108882],{},[660,108883],{"alt":108884,"src":108885},"AWS Lambda Create Function","/images/blog/2023/05/19/aws-create-function.png",[651,108887,108888],{},"For the purposes of this tutorial, I'll keep the architecture at X86 - this is because if you want to use something like SnapStart, you’ll need to use X86 as it currently does not support ARM64. Having selected the runtime, create the function.",[651,108890,108891],{},"Next, let's upload the jar file using the 'Upload from .zip or .jar file' option and save it. Now, edit the runtime settings. Here, you will need to provide the handler as 'package name.class name::method name'.",[669,108893,108896],{"className":108894,"code":108895,"language":11464},[16247],"dev.danvega.HelloJava17.SimpleHandler::handleRequest\n",[676,108897,108895],{"__ignoreMap":674},[651,108899,108900],{},"Save the settings and test the code. Create a test event with a string (e.g., 'hello Java 17'). You should see your string returned in uppercase, proving that the function works and that yes, we are indeed running on Java 17.",[651,108902,108903],{},"With the simple Java application created successfully, let's try using Spring Boot.",[4542,108905,108907],{"id":108906},"creating-a-project-using-spring-boot","Creating a Project Using Spring Boot",[651,108909,108910,108911,108915],{},"Next, is to create a new project using Spring Boot 3. To do so, we will head over to ",[812,108912,108914],{"href":7115,"rel":108913},[816],"'start.spring.io'",", a handy website for quickly setting up a Spring Boot Project.",[651,108917,108918],{},"On the website, we can select the necessary configurations for our project. For this project, we'll be utilizing Java 17 and Spring Boot 3. For dependencies, select 'Web' and 'Spring Cloud Function'.",[651,108920,108921],{},[660,108922],{"alt":7117,"src":108923},"/images/blog/2023/05/19/scf-spring-init.png",[651,108925,108926,108927,108929,108930,108932],{},"Once the project parameters are set up, you can generate the project. The resulting .zip file can be opened in your preferred IDE (for this project, we are using IntelliJ IDEA). In the application, the ",[676,108928,95826],{}," function simply reverses the given input string. Here is the code for the ",[676,108931,95826],{}," function:",[669,108934,108936],{"className":4107,"code":108935,"language":4109,"meta":674,"style":674},"@Bean\npublic Function\u003CString, String> reverse() {\n return input -> new StringBuilder(input).reverse().toString();\n}\n",[676,108937,108938,108944,108960,108984],{"__ignoreMap":674},[679,108939,108940,108942],{"class":681,"line":682},[679,108941,4116],{"class":693},[679,108943,16929],{"class":685},[679,108945,108946,108948,108950,108952,108954,108956,108958],{"class":681,"line":790},[679,108947,6073],{"class":685},[679,108949,95795],{"class":693},[679,108951,4505],{"class":685},[679,108953,95800],{"class":693},[679,108955,5860],{"class":685},[679,108957,95805],{"class":880},[679,108959,2667],{"class":693},[679,108961,108962,108964,108967,108969,108971,108973,108976,108978,108980,108982],{"class":681,"line":892},[679,108963,21478],{"class":685},[679,108965,108966],{"class":693}," input ",[679,108968,16955],{"class":685},[679,108970,2054],{"class":685},[679,108972,95820],{"class":880},[679,108974,108975],{"class":693},"(input).",[679,108977,95826],{"class":880},[679,108979,10541],{"class":693},[679,108981,14391],{"class":880},[679,108983,9317],{"class":693},[679,108985,108986],{"class":681,"line":901},[679,108987,996],{"class":693},[651,108989,108990,108991,104136,108994,94061],{},"This function creates a new string builder with the input string, reverses the string, and then converts it back to a string. Next, you need to build an artifact and target the AWS Lambda runtime. To do so, include the ",[676,108992,108993],{},"spring-cloud-function-adapter-aws",[676,108995,81517],{},[669,108997,108999],{"className":9101,"code":108998,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.cloud\u003C/groupId>\n \u003CartifactId>spring-cloud-function-adapter-aws\u003C/artifactId>\n\u003C/dependency>\n",[676,109000,109001,109005,109010,109015],{"__ignoreMap":674},[679,109002,109003],{"class":681,"line":682},[679,109004,9110],{},[679,109006,109007],{"class":681,"line":790},[679,109008,109009],{}," \u003CgroupId>org.springframework.cloud\u003C/groupId>\n",[679,109011,109012],{"class":681,"line":892},[679,109013,109014],{}," \u003CartifactId>spring-cloud-function-adapter-aws\u003C/artifactId>\n",[679,109016,109017],{"class":681,"line":901},[679,109018,9125],{},[651,109020,109021,109022,109024,109025,109027],{},"With the Maven Shade plugin in place, you can run the ",[676,109023,95230],{}," command to build the JAR file. When this is complete, you should see a new JAR file with ",[676,109026,79470],{}," in the name, which is what you can upload to AWS. The process is the same as before, so we won't repeat it here.",[4542,109029,9042],{"id":9041},[651,109031,109032],{},"If you're looking for the source code for both of these demos you can find them below:",[5316,109034,109035,109042],{},[5332,109036,109037],{},[812,109038,109041],{"href":109039,"rel":109040},"https://github.com/danvega/hello-java-17",[816],"GitHub Repo - Java 17",[5332,109043,109044],{},[812,109045,109048],{"href":109046,"rel":109047},"https://github.com/danvega/scf-17",[816],"GitHub Repo - Spring Cloud Function",[651,109050,109051,109052,1223],{},"To sum it all up, the support for Java 17 on AWS Lambda opens up new opportunities for both Java and Spring development. It's really exciting to see what we will be able to build with this at our disposal. As always, ",[2939,109053,109054],{},"Happy Coding Friends",[786,109056,32530],{},{"title":674,"searchDepth":790,"depth":790,"links":109058},[109059,109060,109061,109062],{"id":108538,"depth":790,"text":108539},{"id":108875,"depth":790,"text":108876},{"id":108906,"depth":790,"text":108907},{"id":9041,"depth":790,"text":9042},"AWS has made a big announcement recently - Java 17 runtime is now supported on AWS Lambda. What this means is, you now have the freedom to write your lambda functions using Java in Spring and specifically target JDK 17.",{"slug":109065,"date":109066,"published":797,"author":798,"tags":109067,"cover":109068,"video":109069,"github":109046,"keywords":109070},"aws-lambda-java-17","2023-05-19T08:00:00.000Z",[7077,79471,36422],"./aws-lambda-spring-boot-3-thumbnail.png","https://www.youtube.com/embed/bxK4GscuVgs","Spring Framework, Spring Boot, Spring Cloud, Spring Cloud Function, Spring AWS, AWS Lambda, Java, Java 17",{"title":306,"description":109063},"blog/2023/05/19/aws-lambda-java-17","O9hVmTwG6YbjmFPwGSA6jfFr7qnMfr_F9xdeGb_SO0w",{"id":109075,"title":303,"body":109076,"description":109244,"extension":793,"meta":109245,"navigation":797,"path":304,"seo":109252,"stem":109253,"__hash__":109254},"content/blog/2023/06/12/spring-http-interfaces-headers.md",{"type":648,"value":109077,"toc":109237},[109078,109089,109098,109102,109109,109112,109122,109127,109131,109134,109148,109154,109160,109164,109172,109176,109179,109185,109194,109197,109200,109206,109209,109211,109218,109229,109234],[651,109079,109080,109081,109084,109085,109088],{},"In this article, we'll be exploring how to customize ",[2939,109082,109083],{},"HTTP interfaces in Spring Boot 3",". This stemmed from a question that popped up during a recent presentation I gave: ",[7300,109086,109087],{},"How do you add headers to requests?"," Be it for a single request, where you may want to add one header and its value, or for multiple or all requests sent to a certain service – how do you accomplish that? This post aims to dive right into these questions!",[651,109090,109091,109092,109097],{},"If you’re new to HTTP Interfaces check out ",[812,109093,109096],{"href":109094,"rel":109095},"https://www.danvega.dev/blog/2023/06/14/spring-http-interfaces/",[816],"this article"," for an introduction to what they are and how to get started with them.",[4542,109099,109101],{"id":109100},"building-a-demo-project","Building a Demo Project",[651,109103,109104,109105,109108],{},"Primarily, our goal here is to have a service within a Spring application, reaching out to another service in your network, be it a Microservice or a public API. Another question that typically arises in this context is: ",[7300,109106,109107],{},"How do you monitor which headers are being transmitted?"," In the case of a browser application, the DevTools allow us to see the request and response. We'll attempt to tackle this conundrum as well in our session.",[651,109110,109111],{},"Let's dive straight into the code.",[651,109113,109114,109115,109121],{},"Our project was initiated on ",[812,109116,109118],{"href":7115,"rel":109117},[816],[7300,109119,109120],{},"Start Spring IO"," with Maven as the build tool, Java as the language, and the most current version of Spring Boot (3.1.0). Our project name for the purpose of guiding you through this post is \"headers\". Our language of choice for this project is Java 17. The dependencies we have selected for the demo are Spring Web and Web Flux. Potentially, you might select Spring Web for constructing a REST API, but here we turn to Web Flux albeit not constructing a reactive stack as HTTP interfaces utilize the reactive web client under the hood in Spring Boot. This feature does not necessarily have to be blocking or non-blocking; we'll explore an example further down. We'll now generate our project and launch it in IntelliJ IDEA to get things started.",[651,109123,109124],{},[660,109125],{"alt":7117,"src":109126},"/images/blog/2023/06/14/request-headers-start-spring-io.png",[4542,109128,109130],{"id":109129},"creating-a-model","Creating a Model",[651,109132,109133],{},"We'll be developing an application within this demo and for that, we need a model. We'll craft a new Java class named ‘Post’, serving as a record in the model package. We’re modelling a blog post here, which includes:",[5316,109135,109136,109139,109142,109145],{},[5332,109137,109138],{},"Integer ID",[5332,109140,109141],{},"Integer user ID",[5332,109143,109144],{},"String title",[5332,109146,109147],{},"String body",[651,109149,109150,109151,109153],{},"The model was designed this way because the service we would be calling adheres to this context. Following the model creation, a new service package was generated. To further illustrate, our service in question is called ",[7300,109152,33254],{},". This API provides a collection of resources, one of which has a “/posts” endpoint that syncs well with our established record.",[651,109155,109156,109157,664],{},"Now we’re all set to formulate a ",[7300,109158,109159],{},"JSON Placeholder Service",[4542,109161,109163],{"id":109162},"initiation-of-jsonplaceholderservice","Initiation of JSONPlaceholderService",[651,109165,109166,109167,109171],{},"To get started, we created a Java class and named the interface as JSONPlaceholderService. The main base URL to make a call is \"",[812,109168,109169],{"href":109169,"rel":109170},"http://jsonplaceholder.typicode.com",[816],"\" and the endpoint to fetch posts is \"/posts\". In our simulation, we want to retrieve all posts. Hence, we establish a GET exchange to Posts, and based on the return type provided, we can decide whether our call is \"blocking\" or \"non-blocking\". Here, we don't have to be bothered with blocking or non-blocking status of calls. We set this up by providing a method that simply returns the list of posts when called \"FindAll\". But, the question arises here, how to send a specific header just for this method?",[4542,109173,109175],{"id":109174},"including-request-headers","Including Request Headers",[651,109177,109178],{},"There are two possible scenarios; either you may want to send a request header alone with the request, or send a header and a value for all requests. To add a request header to the FindAll method, we need to add a request header annotation to the method and provide the headers that need to be sent with the request. We did that by creating a map of headers and passing it to the FindAll method.",[669,109180,109183],{"className":109181,"code":109182,"language":11464},[16247],"@GetExchange(\"/posts\")\nList\u003CPost> findAll(@RequestHeader Map\u003CString, String> headers);\n",[676,109184,109182],{"__ignoreMap":674},[1004,109186,109187],{},[651,109188,109189,109190,109193],{},"Blockquote: The ",[676,109191,109192],{},"@RequestHeader"," annotation indicates that a method parameter should be bound to a Web request header. The method parameter can be a Map\u003CString, String>, MultiValueMap\u003CString, String>, or HttpHeaders and then the map is populated with all header names and values.",[651,109195,109196],{},"Upon running the application, as we can see in the logs, the headers are getting passed to the request. Voila!",[651,109198,109199],{},"You might wonder, what if we want to pass some headers for all requests? The response to that is pretty simple. We just need to set a default header in the client configuration.",[669,109201,109204],{"className":109202,"code":109203,"language":11464},[16247],".defaultHeaders(headers -> headers.add(\"Spring-Boot-Version\",\"3 10\"))\n",[676,109205,109203],{"__ignoreMap":674},[651,109207,109208],{},"Upon re-running the application, as is evident in the logs, we are able to pass the header with request to all methods.",[4542,109210,9042],{"id":9041},[651,109212,109213,109214,109217],{},"In this article we've looked into adding headers to requests, and more specifically, how to do it with ",[2939,109215,109216],{},"HTTP clients interface in Spring Boot 3",". The key takeaways are:",[27665,109219,109220,109223,109226],{},[5332,109221,109222],{},"To add a header to each request, you can add a default header in client configuration.",[5332,109224,109225],{},"To add a header to a specific request, you can add a request header annotation to the method along with the headers.",[5332,109227,109228],{},"To monitor headers in your logs, change the logging level for the web to 'Trace'.",[651,109230,109231],{},[7300,109232,109233],{},"Note: Please note that the logging level should be reverted back for production.",[651,109235,109236],{},"Hope this post has been of assistance to you. Keep an eye out for more such informative posts. Until next time, happy coding!",{"title":674,"searchDepth":790,"depth":790,"links":109238},[109239,109240,109241,109242,109243],{"id":109100,"depth":790,"text":109101},{"id":109129,"depth":790,"text":109130},{"id":109162,"depth":790,"text":109163},{"id":109174,"depth":790,"text":109175},{"id":9041,"depth":790,"text":9042},"In this tutorial you will learn how to add a request header when using HTTP Interfaces in Spring Boot 3. This can be useful when you need to add a header to a single request or every single request. You will also learn how to view the headers that are sent to make sure everything is working properly.",{"slug":109246,"date":109247,"published":797,"author":798,"tags":109248,"cover":109249,"video":109250,"keywords":109251},"spring-http-interfaces-headers","2023-06-14T08:00:00.000Z",[100892,7077],"./http-interface-request-headers.png","https://www.youtube.com/embed/AOJzm7yFOl0","Spring Boot, Spring Framework, Spring Boot 3, Spring Framework 6, REST Template, Web Client, HTTP Client, Spring HTTP Client, HTTP Interfaces",{"title":303,"description":109244},"blog/2023/06/12/spring-http-interfaces-headers","ETtTEA1baIjd2_Gi5Cq56IXakxx77NBBrl6wSLOv-9s",{"id":109256,"title":138,"body":109257,"description":110246,"extension":793,"meta":110247,"navigation":797,"path":139,"seo":110254,"stem":110255,"__hash__":110256},"content/blog/2023/06/14/spring-http-interfaces.md",{"type":648,"value":109258,"toc":110238},[109259,109262,109264,109277,109279,109282,109288,109291,109297,109300,109303,109320,109327,109354,109360,109387,109884,109887,109890,109893,109943,109959,109964,110160,110163,110166,110172,110175,110186,110189,110220,110227,110230,110232,110235],[651,109260,109261],{},"In this blog post, we will explore the concept of HTTP interface clients in the Spring Framework 6 and Spring Boot 3. We will go through the process of creating a full REST API in Spring MVC and then create a new service to consume that API using Spring's HTTP interface clients. This blog post will provide a step-by-step guide and example code snippets to help you understand the implementation details. So, let's get started!",[4542,109263,80648],{"id":80647},[5316,109265,109266,109268,109271,109274],{},[5332,109267,107370],{},[5332,109269,109270],{},"Creating the REST API in the Article Service",[5332,109272,109273],{},"Creating the Article Client in the Content Service",[5332,109275,109276],{},"Testing the Implemented API",[4542,109278,107370],{"id":94280},[651,109280,109281],{},"Before we dive into the implementation details, we need to set up the project structure. The project will be a Maven multi-module project consisting of two modules: the Article Service and the Content Service. We will create an empty Maven project and add the necessary submodules to it. Let's start by setting up the project in IntelliJ IDEA, although you can use any IDE or text editor of your choice.",[651,109283,109284],{},[660,109285],{"alt":109286,"src":109287},"Creating an empty Maven project","/images/blog/2023/06/14/empty-project.png",[651,109289,109290],{},"Once you have created an empty Maven project, go ahead and create the Article Service and the Content Service as submodules. You can choose the appropriate settings, such as Java version and group ID, according to your preferences. Once the project is set up, you can move on to creating the REST API in the Article Service.",[651,109292,109293],{},[660,109294],{"alt":109295,"src":109296},"Maven multi module project","/images/blog/2023/06/14/multi-module-project.png",[4542,109298,109270],{"id":109299},"creating-the-rest-api-in-the-article-service",[651,109301,109302],{},"In the Article Service, we will create a REST API using Spring MVC. We will define the necessary CRUD (Create, Read, Update, Delete) operations for managing articles. Let's start by creating the Article class, which will represent the structure of an article in our system. We will define the ID, title, and body fields for the article.",[669,109304,109306],{"className":4107,"code":109305,"language":4109,"meta":674,"style":674},"public record Article(Integer id, String title, String body) {}\n",[676,109307,109308],{"__ignoreMap":674},[679,109309,109310,109312,109314,109317],{"class":681,"line":682},[679,109311,6073],{"class":685},[679,109313,86928],{"class":685},[679,109315,109316],{"class":880}," Article",[679,109318,109319],{"class":693},"(Integer id, String title, String body) {}\n",[651,109321,109322,109323,109326],{},"Next, we will create the ",[676,109324,109325],{},"ArticleController"," class, which will be responsible for handling the HTTP requests related to articles. We will define the necessary methods to handle the CRUD operations. Here's an overview of the methods we will create:",[5316,109328,109329,109334,109339,109344,109349],{},[5332,109330,109331,109333],{},[676,109332,34142],{},": This method will retrieve all the articles from the service.",[5332,109335,109336,109338],{},[676,109337,87658],{},": This method will retrieve a specific article based on the provided ID.",[5332,109340,109341,109343],{},[676,109342,5697],{},": This method will create a new article.",[5332,109345,109346,109348],{},[676,109347,82237],{},": This method will update an existing article.",[5332,109350,109351,109353],{},[676,109352,7632],{},": This method will delete an article.",[651,109355,109356,109357,109359],{},"To create the ",[676,109358,109325],{}," class, follow these steps:",[27665,109361,109362,109369,109378,109384],{},[5332,109363,109364,109365,89334,109367,6156],{},"Create a new Java class called ",[676,109366,109325],{},[676,109368,4279],{},[5332,109370,109371,109372,109374,109375,664],{},"Annotate the class with ",[676,109373,12329],{}," and specify the request mapping for the API, e.g., ",[676,109376,109377],{},"@RequestMapping(\"/api/articles\")",[5332,109379,109380,109381,664],{},"Define a constructor for the class and inject the necessary dependencies, such as the ",[676,109382,109383],{},"ArticleRepository",[5332,109385,109386],{},"Implement the CRUD methods mentioned above.",[669,109388,109390],{"className":4107,"code":109389,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/articles\")\npublic class ArticleController {\n\n private final List\u003CArticle> articles = new ArrayList\u003C>();\n\n @GetMapping\n public List\u003CArticle> findAll() throws InterruptedException {\n Thread.sleep(6000);\n return articles;\n }\n\n @GetMapping(\"/{id}\")\n public Optional\u003CArticle> findById(@PathVariable Integer id) {\n return articles.stream().filter(article -> article.id().equals(id)).findFirst();\n }\n\n @PostMapping\n @ResponseStatus(HttpStatus.CREATED)\n public void create(@RequestBody Article article) {\n this.articles.add(article);\n }\n\n @PutMapping(\"/{id}\")\n @ResponseStatus(HttpStatus.NO_CONTENT)\n public void update(@RequestBody Article article, @PathVariable Integer id) {\n var currentArticle = articles.stream().filter(a -> a.id().equals(id)).findFirst();\n currentArticle.ifPresent(value -> this.articles.set(articles.indexOf(value), article));\n }\n\n @DeleteMapping(\"/{id}\")\n @ResponseStatus(HttpStatus.NO_CONTENT)\n public void delete(@PathVariable Integer id) {\n this.articles.removeIf(a -> a.id().equals(id));\n }\n\n @PostConstruct\n private void init() {\n Article article = new Article(1,\"Hello, World!\",\"This is my first post\");\n this.articles.add(article);\n }\n\n}\n",[676,109391,109392,109398,109411,109422,109426,109446,109450,109456,109474,109487,109494,109498,109502,109514,109537,109571,109575,109579,109585,109593,109612,109624,109628,109632,109645,109654,109680,109716,109744,109748,109752,109764,109772,109790,109812,109816,109820,109826,109836,109862,109872,109876,109880],{"__ignoreMap":674},[679,109393,109394,109396],{"class":681,"line":682},[679,109395,4116],{"class":693},[679,109397,9212],{"class":685},[679,109399,109400,109402,109404,109406,109409],{"class":681,"line":790},[679,109401,4116],{"class":693},[679,109403,9275],{"class":685},[679,109405,745],{"class":693},[679,109407,109408],{"class":689},"\"/api/articles\"",[679,109410,1339],{"class":693},[679,109412,109413,109415,109417,109420],{"class":681,"line":892},[679,109414,6073],{"class":685},[679,109416,4512],{"class":685},[679,109418,109419],{"class":880}," ArticleController",[679,109421,884],{"class":693},[679,109423,109424],{"class":681,"line":901},[679,109425,889],{"emptyLinePlaceholder":797},[679,109427,109428,109430,109432,109434,109437,109440,109442,109444],{"class":681,"line":909},[679,109429,9232],{"class":685},[679,109431,12768],{"class":685},[679,109433,87217],{"class":693},[679,109435,109436],{"class":685},"Article",[679,109438,109439],{"class":693},"> articles ",[679,109441,686],{"class":685},[679,109443,2054],{"class":685},[679,109445,87229],{"class":693},[679,109447,109448],{"class":681,"line":918},[679,109449,889],{"emptyLinePlaceholder":797},[679,109451,109452,109454],{"class":681,"line":935},[679,109453,6872],{"class":693},[679,109455,80415],{"class":685},[679,109457,109458,109460,109462,109464,109466,109468,109470,109472],{"class":681,"line":944},[679,109459,6089],{"class":685},[679,109461,87217],{"class":693},[679,109463,109436],{"class":685},[679,109465,20881],{"class":693},[679,109467,34142],{"class":880},[679,109469,6700],{"class":693},[679,109471,9580],{"class":685},[679,109473,80430],{"class":693},[679,109475,109476,109478,109480,109482,109485],{"class":681,"line":959},[679,109477,9606],{"class":693},[679,109479,9609],{"class":880},[679,109481,745],{"class":693},[679,109483,109484],{"class":931},"6000",[679,109486,1208],{"class":693},[679,109488,109489,109491],{"class":681,"line":964},[679,109490,9444],{"class":685},[679,109492,109493],{"class":693}," articles;\n",[679,109495,109496],{"class":681,"line":977},[679,109497,985],{"class":693},[679,109499,109500],{"class":681,"line":982},[679,109501,889],{"emptyLinePlaceholder":797},[679,109503,109504,109506,109508,109510,109512],{"class":681,"line":988},[679,109505,6872],{"class":693},[679,109507,20852],{"class":685},[679,109509,745],{"class":693},[679,109511,92261],{"class":689},[679,109513,1339],{"class":693},[679,109515,109516,109518,109521,109523,109525,109527,109529,109531,109533,109535],{"class":681,"line":993},[679,109517,6089],{"class":685},[679,109519,109520],{"class":693}," Optional\u003C",[679,109522,109436],{"class":685},[679,109524,20881],{"class":693},[679,109526,87658],{"class":880},[679,109528,73246],{"class":693},[679,109530,92277],{"class":685},[679,109532,88766],{"class":693},[679,109534,11341],{"class":2099},[679,109536,4390],{"class":693},[679,109538,109539,109541,109544,109546,109548,109550,109553,109555,109558,109560,109562,109564,109567,109569],{"class":681,"line":2129},[679,109540,9444],{"class":685},[679,109542,109543],{"class":693}," articles.",[679,109545,87323],{"class":880},[679,109547,10541],{"class":693},[679,109549,65029],{"class":880},[679,109551,109552],{"class":693},"(article ",[679,109554,16955],{"class":685},[679,109556,109557],{"class":693}," article.",[679,109559,11341],{"class":880},[679,109561,10541],{"class":693},[679,109563,14592],{"class":880},[679,109565,109566],{"class":693},"(id)).",[679,109568,87355],{"class":880},[679,109570,9317],{"class":693},[679,109572,109573],{"class":681,"line":2140},[679,109574,985],{"class":693},[679,109576,109577],{"class":681,"line":2145},[679,109578,889],{"emptyLinePlaceholder":797},[679,109580,109581,109583],{"class":681,"line":2154},[679,109582,6872],{"class":693},[679,109584,96257],{"class":685},[679,109586,109587,109589,109591],{"class":681,"line":2159},[679,109588,6872],{"class":693},[679,109590,96264],{"class":685},[679,109592,96267],{"class":693},[679,109594,109595,109597,109599,109601,109603,109605,109608,109610],{"class":681,"line":2164},[679,109596,6089],{"class":685},[679,109598,6095],{"class":685},[679,109600,49468],{"class":880},[679,109602,73246],{"class":693},[679,109604,96282],{"class":685},[679,109606,109607],{"class":693}," Article ",[679,109609,52469],{"class":2099},[679,109611,4390],{"class":693},[679,109613,109614,109616,109619,109621],{"class":681,"line":3134},[679,109615,7862],{"class":931},[679,109617,109618],{"class":693},".articles.",[679,109620,12952],{"class":880},[679,109622,109623],{"class":693},"(article);\n",[679,109625,109626],{"class":681,"line":3139},[679,109627,985],{"class":693},[679,109629,109630],{"class":681,"line":3144},[679,109631,889],{"emptyLinePlaceholder":797},[679,109633,109634,109636,109639,109641,109643],{"class":681,"line":3149},[679,109635,6872],{"class":693},[679,109637,109638],{"class":685},"PutMapping",[679,109640,745],{"class":693},[679,109642,92261],{"class":689},[679,109644,1339],{"class":693},[679,109646,109647,109649,109651],{"class":681,"line":3169},[679,109648,6872],{"class":693},[679,109650,96264],{"class":685},[679,109652,109653],{"class":693},"(HttpStatus.NO_CONTENT)\n",[679,109655,109656,109658,109660,109662,109664,109666,109668,109670,109672,109674,109676,109678],{"class":681,"line":3185},[679,109657,6089],{"class":685},[679,109659,6095],{"class":685},[679,109661,72244],{"class":880},[679,109663,73246],{"class":693},[679,109665,96282],{"class":685},[679,109667,109607],{"class":693},[679,109669,52469],{"class":2099},[679,109671,85575],{"class":693},[679,109673,92277],{"class":685},[679,109675,88766],{"class":693},[679,109677,11341],{"class":2099},[679,109679,4390],{"class":693},[679,109681,109682,109684,109687,109689,109691,109693,109695,109697,109700,109702,109704,109706,109708,109710,109712,109714],{"class":681,"line":3194},[679,109683,94003],{"class":685},[679,109685,109686],{"class":693}," currentArticle ",[679,109688,686],{"class":685},[679,109690,109543],{"class":693},[679,109692,87323],{"class":880},[679,109694,10541],{"class":693},[679,109696,65029],{"class":880},[679,109698,109699],{"class":693},"(a ",[679,109701,16955],{"class":685},[679,109703,15153],{"class":693},[679,109705,11341],{"class":880},[679,109707,10541],{"class":693},[679,109709,14592],{"class":880},[679,109711,109566],{"class":693},[679,109713,87355],{"class":880},[679,109715,9317],{"class":693},[679,109717,109718,109721,109724,109727,109729,109731,109733,109735,109738,109741],{"class":681,"line":3199},[679,109719,109720],{"class":693}," currentArticle.",[679,109722,109723],{"class":880},"ifPresent",[679,109725,109726],{"class":693},"(value ",[679,109728,16955],{"class":685},[679,109730,21353],{"class":931},[679,109732,109618],{"class":693},[679,109734,64453],{"class":880},[679,109736,109737],{"class":693},"(articles.",[679,109739,109740],{"class":880},"indexOf",[679,109742,109743],{"class":693},"(value), article));\n",[679,109745,109746],{"class":681,"line":3212},[679,109747,985],{"class":693},[679,109749,109750],{"class":681,"line":3217},[679,109751,889],{"emptyLinePlaceholder":797},[679,109753,109754,109756,109758,109760,109762],{"class":681,"line":3222},[679,109755,6872],{"class":693},[679,109757,92256],{"class":685},[679,109759,745],{"class":693},[679,109761,92261],{"class":689},[679,109763,1339],{"class":693},[679,109765,109766,109768,109770],{"class":681,"line":3227},[679,109767,6872],{"class":693},[679,109769,96264],{"class":685},[679,109771,109653],{"class":693},[679,109773,109774,109776,109778,109780,109782,109784,109786,109788],{"class":681,"line":3232},[679,109775,6089],{"class":685},[679,109777,6095],{"class":685},[679,109779,92272],{"class":880},[679,109781,73246],{"class":693},[679,109783,92277],{"class":685},[679,109785,88766],{"class":693},[679,109787,11341],{"class":2099},[679,109789,4390],{"class":693},[679,109791,109792,109794,109796,109798,109800,109802,109804,109806,109808,109810],{"class":681,"line":3499},[679,109793,7862],{"class":931},[679,109795,109618],{"class":693},[679,109797,92291],{"class":880},[679,109799,109699],{"class":693},[679,109801,16955],{"class":685},[679,109803,15153],{"class":693},[679,109805,11341],{"class":880},[679,109807,10541],{"class":693},[679,109809,14592],{"class":880},[679,109811,92307],{"class":693},[679,109813,109814],{"class":681,"line":3509},[679,109815,985],{"class":693},[679,109817,109818],{"class":681,"line":3516},[679,109819,889],{"emptyLinePlaceholder":797},[679,109821,109822,109824],{"class":681,"line":3531},[679,109823,6872],{"class":693},[679,109825,87391],{"class":685},[679,109827,109828,109830,109832,109834],{"class":681,"line":3536},[679,109829,9232],{"class":685},[679,109831,6095],{"class":685},[679,109833,36742],{"class":880},[679,109835,2667],{"class":693},[679,109837,109838,109841,109843,109845,109847,109849,109851,109853,109855,109857,109860],{"class":681,"line":3541},[679,109839,109840],{"class":693}," Article article ",[679,109842,686],{"class":685},[679,109844,2054],{"class":685},[679,109846,109316],{"class":880},[679,109848,745],{"class":693},[679,109850,1557],{"class":931},[679,109852,1202],{"class":693},[679,109854,71855],{"class":689},[679,109856,1202],{"class":693},[679,109858,109859],{"class":689},"\"This is my first post\"",[679,109861,1208],{"class":693},[679,109863,109864,109866,109868,109870],{"class":681,"line":3546},[679,109865,7862],{"class":931},[679,109867,109618],{"class":693},[679,109869,12952],{"class":880},[679,109871,109623],{"class":693},[679,109873,109874],{"class":681,"line":3551},[679,109875,985],{"class":693},[679,109877,109878],{"class":681,"line":3557},[679,109879,889],{"emptyLinePlaceholder":797},[679,109881,109882],{"class":681,"line":3567},[679,109883,996],{"class":693},[651,109885,109886],{},"That's it! You have successfully created the REST API in the Article Service. You can test the API endpoints using tools like curl or Postman. Now, let's move on to creating the Article Client in the Content Service.",[4542,109888,109273],{"id":109889},"creating-the-article-client-in-the-content-service",[651,109891,109892],{},"In the Content Service, we will create an Article Client using Spring's HTTP interface clients to consume the REST API provided by the Article Service. The Article Client will be responsible for making HTTP requests to the API endpoints of the Article Service and retrieving the necessary data. Let's start by creating the necessary configuration and interface definition.",[27665,109894,109895,109900,109911,109922,109930],{},[5332,109896,99748,109897,109899],{},[676,109898,27529],{}," in the Content Service.",[5332,109901,109902,109903,89334,109906,109908,109909,664],{},"Create a new class called ",[676,109904,109905],{},"ArticleClientConfig",[676,109907,64430],{}," package. Annotate it with ",[676,109910,6139],{},[5332,109912,109913,109914,109917,109918,109921],{},"Define a bean for the ",[676,109915,109916],{},"ArticleClient"," using the ",[676,109919,109920],{},"WebClient"," builder and specify the base URL of the Article Service API.",[5332,109923,109924,109925,89334,109927,109929],{},"Create an interface called ",[676,109926,109916],{},[676,109928,27529],{}," package. This interface will define the methods for interacting with the Article Service API.",[5332,109931,109932,109933,109935,109936,109939,109940,664],{},"Annotate the ",[676,109934,109916],{}," interface with ",[676,109937,109938],{},"@FeignClient"," and specify the name of the client, e.g., ",[676,109941,109942],{},"@FeignClient(\"article-service\")",[651,109944,40060,109945,109947,109948,2797,109950,2797,109952,109955,109956,109958],{},[676,109946,109916],{}," interface will contain the necessary method declarations for interacting with the Article Service API. The methods will have ",[676,109949,73296],{},[676,109951,88878],{},[676,109953,109954],{},"@PutMapping",", or ",[676,109957,92093],{}," annotations to specify the corresponding HTTP method and URL. Each method will have a returning type according to the expected response from the Article Service API.",[651,109960,109961,109962,94409],{},"Here's an example of the ",[676,109963,109916],{},[669,109965,109967],{"className":4107,"code":109966,"language":4109,"meta":674,"style":674},"public interface ArticleClient {\n\n @GetExchange(\"/articles\")\n ResponseEntity\u003CList\u003CArticle>> findAll();\n\n @GetExchange(\"/articles/{id}\")\n Optional\u003CArticle> findOne(@PathVariable Integer id);\n\n @PostExchange(\"/articles\")\n void create(@RequestBody Article article);\n\n @PutExchange(\"/articles/{id}\")\n void update(@RequestBody Article article, @PathVariable Integer id);\n\n @DeleteExchange(\"/articles/{id}\")\n void delete(@PathVariable Integer id);\n}\n",[676,109968,109969,109980,109984,109997,110011,110015,110028,110049,110053,110066,110082,110086,110099,110123,110127,110140,110156],{"__ignoreMap":674},[679,109970,109971,109973,109975,109978],{"class":681,"line":682},[679,109972,6073],{"class":685},[679,109974,6994],{"class":685},[679,109976,109977],{"class":880}," ArticleClient",[679,109979,884],{"class":693},[679,109981,109982],{"class":681,"line":790},[679,109983,889],{"emptyLinePlaceholder":797},[679,109985,109986,109988,109990,109992,109995],{"class":681,"line":892},[679,109987,6872],{"class":693},[679,109989,100228],{"class":685},[679,109991,745],{"class":693},[679,109993,109994],{"class":689},"\"/articles\"",[679,109996,1339],{"class":693},[679,109998,109999,110002,110004,110007,110009],{"class":681,"line":901},[679,110000,110001],{"class":693}," ResponseEntity\u003CList\u003C",[679,110003,109436],{"class":685},[679,110005,110006],{"class":693},">> ",[679,110008,34142],{"class":880},[679,110010,9317],{"class":693},[679,110012,110013],{"class":681,"line":909},[679,110014,889],{"emptyLinePlaceholder":797},[679,110016,110017,110019,110021,110023,110026],{"class":681,"line":918},[679,110018,6872],{"class":693},[679,110020,100228],{"class":685},[679,110022,745],{"class":693},[679,110024,110025],{"class":689},"\"/articles/{id}\"",[679,110027,1339],{"class":693},[679,110029,110030,110033,110035,110037,110039,110041,110043,110045,110047],{"class":681,"line":935},[679,110031,110032],{"class":693}," Optional\u003C",[679,110034,109436],{"class":685},[679,110036,20881],{"class":693},[679,110038,86714],{"class":880},[679,110040,73246],{"class":693},[679,110042,92277],{"class":685},[679,110044,88766],{"class":693},[679,110046,11341],{"class":2099},[679,110048,1208],{"class":693},[679,110050,110051],{"class":681,"line":944},[679,110052,889],{"emptyLinePlaceholder":797},[679,110054,110055,110057,110060,110062,110064],{"class":681,"line":959},[679,110056,6872],{"class":693},[679,110058,110059],{"class":685},"PostExchange",[679,110061,745],{"class":693},[679,110063,109994],{"class":689},[679,110065,1339],{"class":693},[679,110067,110068,110070,110072,110074,110076,110078,110080],{"class":681,"line":964},[679,110069,3314],{"class":685},[679,110071,49468],{"class":880},[679,110073,73246],{"class":693},[679,110075,96282],{"class":685},[679,110077,109607],{"class":693},[679,110079,52469],{"class":2099},[679,110081,1208],{"class":693},[679,110083,110084],{"class":681,"line":977},[679,110085,889],{"emptyLinePlaceholder":797},[679,110087,110088,110090,110093,110095,110097],{"class":681,"line":982},[679,110089,6872],{"class":693},[679,110091,110092],{"class":685},"PutExchange",[679,110094,745],{"class":693},[679,110096,110025],{"class":689},[679,110098,1339],{"class":693},[679,110100,110101,110103,110105,110107,110109,110111,110113,110115,110117,110119,110121],{"class":681,"line":988},[679,110102,3314],{"class":685},[679,110104,72244],{"class":880},[679,110106,73246],{"class":693},[679,110108,96282],{"class":685},[679,110110,109607],{"class":693},[679,110112,52469],{"class":2099},[679,110114,85575],{"class":693},[679,110116,92277],{"class":685},[679,110118,88766],{"class":693},[679,110120,11341],{"class":2099},[679,110122,1208],{"class":693},[679,110124,110125],{"class":681,"line":993},[679,110126,889],{"emptyLinePlaceholder":797},[679,110128,110129,110131,110134,110136,110138],{"class":681,"line":2129},[679,110130,6872],{"class":693},[679,110132,110133],{"class":685},"DeleteExchange",[679,110135,745],{"class":693},[679,110137,110025],{"class":689},[679,110139,1339],{"class":693},[679,110141,110142,110144,110146,110148,110150,110152,110154],{"class":681,"line":2140},[679,110143,3314],{"class":685},[679,110145,92272],{"class":880},[679,110147,73246],{"class":693},[679,110149,92277],{"class":685},[679,110151,88766],{"class":693},[679,110153,11341],{"class":2099},[679,110155,1208],{"class":693},[679,110157,110158],{"class":681,"line":2145},[679,110159,996],{"class":693},[651,110161,110162],{},"That's it! You have created the Article Client in the Content Service. The Article Client will utilize the Feign client under the hood to handle the HTTP requests and responses.",[4542,110164,109276],{"id":110165},"testing-the-implemented-api",[651,110167,110168],{},[660,110169],{"alt":110170,"src":110171},"IntelliJ HTTP Client","/images/blog/2023/06/14/intellij-http-client.png",[651,110173,110174],{},"Now that we have implemented the REST API in the Article Service and created the Article Client in the Content Service, we can test the API endpoints to ensure they work as expected. To test the API, follow these steps:",[27665,110176,110177,110180,110183],{},[5332,110178,110179],{},"Start both the Article Service and the Content Service applications.",[5332,110181,110182],{},"Use tools like curl or Postman to make HTTP requests to the API endpoints of the Content Service.",[5332,110184,110185],{},"Use the Article Client methods in the Content Service to retrieve, create, update, or delete articles in the Article Service.",[651,110187,110188],{},"For example, you can use the following HTTP requests to test the API endpoints:",[5316,110190,110191,110197,110203,110209,110215],{},[5332,110192,110193,110194],{},"To retrieve all articles: GET ",[676,110195,110196],{},"http://localhost:8081/api/content/articles",[5332,110198,110199,110200],{},"To retrieve a specific article: GET ",[676,110201,110202],{},"http://localhost:8081/api/content/articles/{id}",[5332,110204,110205,110206,110208],{},"To create a new article: POST ",[676,110207,110196],{}," with the article data in the request body",[5332,110210,110211,110212,110214],{},"To update an existing article: PUT ",[676,110213,110202],{}," with the updated article data in the request body",[5332,110216,110217,110218],{},"To delete an article: DELETE ",[676,110219,110202],{},[651,110221,110222,110223,110226],{},"Make sure to replace ",[676,110224,110225],{},"{id}"," with the actual ID of the article you want to retrieve, update, or delete.",[651,110228,110229],{},"That's it! You have successfully implemented the REST API using HTTP interface clients in the Spring Framework 6 and Spring Boot 3. You now have a fully functional API that can be consumed by other services through the Article Client. This approach helps reduce boilerplate code and simplifies the process of calling external services.",[4542,110231,9042],{"id":9041},[651,110233,110234],{},"In this blog post, we covered the concept of HTTP interface clients in the Spring Framework 6 and Spring Boot 3. We explored the process of creating a REST API in the Article Service and consuming it through an Article Client in the Content Service. The use of HTTP interface clients simplifies the process of making HTTP requests to external services and reduces the amount of low-level code that needs to be written. By following the steps and code snippets provided in this blog post, you should now have a good understanding of how to create and utilize HTTP interface clients in your Spring applications.",[786,110236,110237],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":110239},[110240,110241,110242,110243,110244,110245],{"id":80647,"depth":790,"text":80648},{"id":94280,"depth":790,"text":107370},{"id":109299,"depth":790,"text":109270},{"id":109889,"depth":790,"text":109273},{"id":110165,"depth":790,"text":109276},{"id":9041,"depth":790,"text":9042},"Learn all about the new HTTP Interfaces in Spring Framework 6 and Spring Boot 3 by building out a real world example.",{"slug":110248,"date":109247,"published":797,"author":798,"tags":110249,"cover":110250,"video":110251,"github":110252,"keywords":110253},"spring-http-interfaces",[100892,7077],"./http-interfaces-crud.png","https://www.youtube.com/embed/4U0hUyktpvg","https://github.com/danvega/http-interfaces","Spring Boot, Spring Framework, Spring Boot 3, Spring Framework 6, REST Template, Web Client, HTTP Client, Spring HTTP Client",{"title":138,"description":110246},"blog/2023/06/14/spring-http-interfaces","kW6PItyrlkDg84OT46ouhKP-OFoD5K8LXMCPCRj02eg",{"id":110258,"title":135,"body":110259,"description":111383,"extension":793,"meta":111384,"navigation":797,"path":136,"seo":111390,"stem":111391,"__hash__":111392},"content/blog/2023/06/30/aws-lambda-spring-boot-3.md",{"type":648,"value":110260,"toc":111375},[110261,110264,110268,110271,110274,110278,110281,110284,110287,110291,110294,110302,110305,110311,110366,110383,110386,110398,110422,110434,110852,110859,110913,111064,111264,111267,111271,111274,111286,111292,111295,111301,111304,111307,111317,111320,111323,111326,111330,111333,111336,111343,111349,111352,111355,111358,111360,111363,111366,111369,111372],[651,110262,110263],{},"Welcome to this detailed and comprehensive blog post about deploying Spring Boot applications to AWS Lambda! If you've been searching for a solution to hosting your personal Spring Boot projects without breaking the bank, you're in the right place. In this blog post, we'll cover everything you need to know about deploying your Spring Boot apps to AWS Lambda, including why you might want to do it, the benefits of serverless computing, and a step-by-step guide on how to get started.",[4542,110265,110267],{"id":110266},"why-deploy-to-aws-lambda","Why Deploy to AWS Lambda?",[651,110269,110270],{},"Before we dive into the details of deploying to AWS Lambda, let's take a moment to discuss why you might want to choose this approach. Traditionally, when deploying a Spring Boot application, you would package it as a JAR file and deploy it to a server or containerized it using Docker and deploy it to Kubernetes. While this approach works well, it comes with the responsibility of managing and maintaining your own servers, including patching, security, and scaling.",[651,110272,110273],{},"AWS Lambda, on the other hand, is a serverless compute service that allows you to run code without the need to provision or manage servers. With AWS Lambda, you only pay for the time that you consume, which makes it an ideal solution for personal projects or applications with variable traffic that can scale to zero. By deploying your Spring Boot applications to AWS Lambda, you can offload the burden of server management and focus on developing and delivering your applications.",[4542,110275,110277],{"id":110276},"the-solution-aws-lambda","The Solution: AWS Lambda",[651,110279,110280],{},"So, what is the solution to your hosting problem? The answer lies in AWS Lambda. In this blog post, we'll be exploring how to deploy entire Spring Boot applications to AWS Lambda, not just individual functions as service, which we've covered in the past.",[651,110282,110283],{},"By leveraging AWS Lambda, you can deploy your Spring Boot applications as serverless functions, taking advantage of the benefits of serverless computing, such as automatic scaling, high availability, and reduced costs. As we mentioned earlier, AWS Lambda is a pay-per-use service, and even better, the free tier includes 1 million free requests per month. This means that for most personal projects, you can deploy them to AWS Lambda without incurring any additional costs.",[651,110285,110286],{},"It's important to note that while we'll be focusing on personal projects in this blog post, AWS Lambda is a versatile solution that can be used for a variety of use cases. Any application with variable traffic that can scale to zero is a great candidate for AWS Lambda.",[4542,110288,110290],{"id":110289},"getting-started-with-aws-lambda-and-spring-boot","Getting Started with AWS Lambda and Spring Boot",[651,110292,110293],{},"Now that we understand why AWS Lambda is a great solution for hosting our personal Spring Boot projects, let's dive into the details of how we can deploy our applications to AWS Lambda.",[651,110295,56985,110296,110301],{},[812,110297,110300],{"href":110298,"rel":110299},"https://github.com/danvega/serverless-api",[816],"GitHub repository"," associated with this blog post, you'll find all the code and resources you need to follow along. The first step is to create a new Spring Boot project using a Maven archetype specifically designed for deploying Spring Boot applications to AWS Lambda.",[651,110303,110304],{},"To create the project using IntelliJ, you can use the Maven archetype generator. Alternatively, you can run the following command from the command line:",[651,110306,110307],{},[660,110308],{"alt":110309,"src":110310},"Maven Archetype","/images/blog/2023/06/30/6Kpi6DOLQUkVjHS53ssf-378.57.png",[669,110312,110314],{"className":5851,"code":110313,"language":5853,"meta":674,"style":674},"mvn archetype:generate \\-DarchetypeGroupId=com.amazonaws.serverless.archetypes \\-DarchetypeArtifactId=aws-serverless-spring-java-archetype \\-DarchetypeVersion=1.0.0 \\-DgroupId=com.example \\-DartifactId=my-spring-boot-app \\-Dversion=1.0.0 \\-DinteractiveMode=false\n",[676,110315,110316],{"__ignoreMap":674},[679,110317,110318,110320,110323,110326,110329,110331,110334,110336,110339,110342,110344,110347,110349,110352,110354,110357,110359,110361,110364],{"class":681,"line":682},[679,110319,81922],{"class":880},[679,110321,110322],{"class":689}," archetype:generate",[679,110324,110325],{"class":931}," \\-",[679,110327,110328],{"class":689},"DarchetypeGroupId=com.amazonaws.serverless.archetypes",[679,110330,110325],{"class":931},[679,110332,110333],{"class":689},"DarchetypeArtifactId=aws-serverless-spring-java-archetype",[679,110335,110325],{"class":931},[679,110337,110338],{"class":689},"DarchetypeVersion=",[679,110340,110341],{"class":931},"1.0.0",[679,110343,110325],{"class":931},[679,110345,110346],{"class":689},"DgroupId=com.example",[679,110348,110325],{"class":931},[679,110350,110351],{"class":689},"DartifactId=my-spring-boot-app",[679,110353,110325],{"class":931},[679,110355,110356],{"class":689},"Dversion=",[679,110358,110341],{"class":931},[679,110360,110325],{"class":931},[679,110362,110363],{"class":689},"DinteractiveMode=",[679,110365,2649],{"class":931},[651,110367,110368,110369,110371,110372,110375,110376,110378,110379,110382],{},"Once you've created the project, you'll notice a few important dependencies in the ",[676,110370,81517],{}," file. These dependencies include ",[676,110373,110374],{},"aws-serverless-java-container-springboot3",", which enables running Spring Boot applications on AWS Lambda, and ",[676,110377,24082],{},", which provides the necessary components for building a REST API. Additionally, the ",[676,110380,110381],{},"assembly-zip"," profile is used to package the application as a ZIP file.",[651,110384,110385],{},"Next, we're going to start building out our Spring Boot REST API. In this example, we'll be creating a blog post API. Our API will have CRUD (Create, Read, Update, Delete) functionality for managing blog posts.",[651,110387,110388,110389,110391,110392,2797,110394,48406,110396,664],{},"We'll start by creating a Java record called ",[676,110390,5105],{},", which represents a blog post. This record will have properties for ",[676,110393,11341],{},[676,110395,11750],{},[676,110397,3006],{},[669,110399,110401],{"className":4107,"code":110400,"language":4109,"meta":674,"style":674},"public record Post(Integer id, String title, String body) {\n\n}\n",[676,110402,110403,110414,110418],{"__ignoreMap":674},[679,110404,110405,110407,110409,110411],{"class":681,"line":682},[679,110406,6073],{"class":685},[679,110408,86928],{"class":685},[679,110410,4658],{"class":880},[679,110412,110413],{"class":693},"(Integer id, String title, String body) {\n",[679,110415,110416],{"class":681,"line":790},[679,110417,889],{"emptyLinePlaceholder":797},[679,110419,110420],{"class":681,"line":892},[679,110421,996],{"class":693},[651,110423,110424,110425,110427,110428,110430,110431,110433],{},"Next, we'll create a new class called ",[676,110426,93270],{},", which will be annotated with ",[676,110429,12329],{},". This class will handle requests related to blog posts, such as retrieving all posts, getting a single post by ID, creating a new post, updating an existing post, and deleting a post. The ",[676,110432,93270],{}," class will have an in-memory collection of posts, which we'll initialize with some dummy data.",[669,110435,110437],{"className":4107,"code":110436,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private List\u003CPost> posts = new ArrayList\u003C>();\n\n @GetMapping\n List\u003CPost> findAll() {\n return posts;\n }\n\n @GetMapping(\"/{id}\")\n Optional\u003CPost> findById(@PathVariable Integer id) {\n return Optional.ofNullable(posts\n .stream()\n .filter(post -> post.id().equals(id))\n .findFirst()\n .orElseThrow(() -> new PostNotFoundException(\"Post with id: \" + id + \" not found.\")));\n }\n\n @PostMapping\n void create(@RequestBody Post post) {\n posts.add(post);\n }\n\n @PutMapping(\"/{id}\")\n void update(@RequestBody Post post, @PathVariable Integer id) {\n posts.stream()\n .filter(p -> p.id().equals(id))\n .findFirst()\n .ifPresent(value -> posts.set(posts.indexOf(value),post));\n }\n\n @DeleteMapping(\"/{id}\")\n void delete(@PathVariable Integer id) {\n posts.removeIf(post -> post.id().equals(id));\n }\n\n}\n",[676,110438,110439,110445,110457,110467,110471,110488,110492,110498,110510,110516,110520,110524,110536,110556,110569,110577,110600,110608,110640,110644,110648,110654,110670,110680,110684,110688,110700,110724,110732,110754,110762,110784,110788,110792,110804,110820,110840,110844,110848],{"__ignoreMap":674},[679,110440,110441,110443],{"class":681,"line":682},[679,110442,4116],{"class":693},[679,110444,9212],{"class":685},[679,110446,110447,110449,110451,110453,110455],{"class":681,"line":790},[679,110448,4116],{"class":693},[679,110450,9275],{"class":685},[679,110452,745],{"class":693},[679,110454,23141],{"class":689},[679,110456,1339],{"class":693},[679,110458,110459,110461,110463,110465],{"class":681,"line":892},[679,110460,6073],{"class":685},[679,110462,4512],{"class":685},[679,110464,23152],{"class":880},[679,110466,884],{"class":693},[679,110468,110469],{"class":681,"line":901},[679,110470,889],{"emptyLinePlaceholder":797},[679,110472,110473,110475,110477,110479,110482,110484,110486],{"class":681,"line":909},[679,110474,9232],{"class":685},[679,110476,87217],{"class":693},[679,110478,5105],{"class":685},[679,110480,110481],{"class":693},"> posts ",[679,110483,686],{"class":685},[679,110485,2054],{"class":685},[679,110487,87229],{"class":693},[679,110489,110490],{"class":681,"line":918},[679,110491,889],{"emptyLinePlaceholder":797},[679,110493,110494,110496],{"class":681,"line":935},[679,110495,6872],{"class":693},[679,110497,80415],{"class":685},[679,110499,110500,110502,110504,110506,110508],{"class":681,"line":944},[679,110501,84917],{"class":693},[679,110503,5105],{"class":685},[679,110505,20881],{"class":693},[679,110507,34142],{"class":880},[679,110509,2667],{"class":693},[679,110511,110512,110514],{"class":681,"line":959},[679,110513,9444],{"class":685},[679,110515,93363],{"class":693},[679,110517,110518],{"class":681,"line":964},[679,110519,985],{"class":693},[679,110521,110522],{"class":681,"line":977},[679,110523,889],{"emptyLinePlaceholder":797},[679,110525,110526,110528,110530,110532,110534],{"class":681,"line":982},[679,110527,6872],{"class":693},[679,110529,20852],{"class":685},[679,110531,745],{"class":693},[679,110533,92261],{"class":689},[679,110535,1339],{"class":693},[679,110537,110538,110540,110542,110544,110546,110548,110550,110552,110554],{"class":681,"line":988},[679,110539,110032],{"class":693},[679,110541,5105],{"class":685},[679,110543,20881],{"class":693},[679,110545,87658],{"class":880},[679,110547,73246],{"class":693},[679,110549,92277],{"class":685},[679,110551,88766],{"class":693},[679,110553,11341],{"class":2099},[679,110555,4390],{"class":693},[679,110557,110558,110560,110563,110566],{"class":681,"line":993},[679,110559,9444],{"class":685},[679,110561,110562],{"class":693}," Optional.",[679,110564,110565],{"class":880},"ofNullable",[679,110567,110568],{"class":693},"(posts\n",[679,110570,110571,110573,110575],{"class":681,"line":2129},[679,110572,73482],{"class":693},[679,110574,87323],{"class":880},[679,110576,17545],{"class":693},[679,110578,110579,110581,110583,110586,110588,110591,110593,110595,110597],{"class":681,"line":2140},[679,110580,73482],{"class":693},[679,110582,65029],{"class":880},[679,110584,110585],{"class":693},"(post ",[679,110587,16955],{"class":685},[679,110589,110590],{"class":693}," post.",[679,110592,11341],{"class":880},[679,110594,10541],{"class":693},[679,110596,14592],{"class":880},[679,110598,110599],{"class":693},"(id))\n",[679,110601,110602,110604,110606],{"class":681,"line":2145},[679,110603,73482],{"class":693},[679,110605,87355],{"class":880},[679,110607,17545],{"class":693},[679,110609,110610,110612,110614,110616,110618,110620,110623,110625,110628,110630,110633,110635,110638],{"class":681,"line":2154},[679,110611,73482],{"class":693},[679,110613,87360],{"class":880},[679,110615,55186],{"class":693},[679,110617,16955],{"class":685},[679,110619,2054],{"class":685},[679,110621,110622],{"class":880}," PostNotFoundException",[679,110624,745],{"class":693},[679,110626,110627],{"class":689},"\"Post with id: \"",[679,110629,3059],{"class":685},[679,110631,110632],{"class":693}," id ",[679,110634,3065],{"class":685},[679,110636,110637],{"class":689}," \" not found.\"",[679,110639,87455],{"class":693},[679,110641,110642],{"class":681,"line":2159},[679,110643,985],{"class":693},[679,110645,110646],{"class":681,"line":2164},[679,110647,889],{"emptyLinePlaceholder":797},[679,110649,110650,110652],{"class":681,"line":3134},[679,110651,6872],{"class":693},[679,110653,96257],{"class":685},[679,110655,110656,110658,110660,110662,110664,110666,110668],{"class":681,"line":3139},[679,110657,3314],{"class":685},[679,110659,49468],{"class":880},[679,110661,73246],{"class":693},[679,110663,96282],{"class":685},[679,110665,93429],{"class":693},[679,110667,5100],{"class":2099},[679,110669,4390],{"class":693},[679,110671,110672,110675,110677],{"class":681,"line":3144},[679,110673,110674],{"class":693}," posts.",[679,110676,12952],{"class":880},[679,110678,110679],{"class":693},"(post);\n",[679,110681,110682],{"class":681,"line":3149},[679,110683,985],{"class":693},[679,110685,110686],{"class":681,"line":3169},[679,110687,889],{"emptyLinePlaceholder":797},[679,110689,110690,110692,110694,110696,110698],{"class":681,"line":3185},[679,110691,6872],{"class":693},[679,110693,109638],{"class":685},[679,110695,745],{"class":693},[679,110697,92261],{"class":689},[679,110699,1339],{"class":693},[679,110701,110702,110704,110706,110708,110710,110712,110714,110716,110718,110720,110722],{"class":681,"line":3194},[679,110703,3314],{"class":685},[679,110705,72244],{"class":880},[679,110707,73246],{"class":693},[679,110709,96282],{"class":685},[679,110711,93429],{"class":693},[679,110713,5100],{"class":2099},[679,110715,85575],{"class":693},[679,110717,92277],{"class":685},[679,110719,88766],{"class":693},[679,110721,11341],{"class":2099},[679,110723,4390],{"class":693},[679,110725,110726,110728,110730],{"class":681,"line":3199},[679,110727,110674],{"class":693},[679,110729,87323],{"class":880},[679,110731,17545],{"class":693},[679,110733,110734,110736,110738,110741,110743,110746,110748,110750,110752],{"class":681,"line":3212},[679,110735,73482],{"class":693},[679,110737,65029],{"class":880},[679,110739,110740],{"class":693},"(p ",[679,110742,16955],{"class":685},[679,110744,110745],{"class":693}," p.",[679,110747,11341],{"class":880},[679,110749,10541],{"class":693},[679,110751,14592],{"class":880},[679,110753,110599],{"class":693},[679,110755,110756,110758,110760],{"class":681,"line":3217},[679,110757,73482],{"class":693},[679,110759,87355],{"class":880},[679,110761,17545],{"class":693},[679,110763,110764,110766,110768,110770,110772,110774,110776,110779,110781],{"class":681,"line":3222},[679,110765,73482],{"class":693},[679,110767,109723],{"class":880},[679,110769,109726],{"class":693},[679,110771,16955],{"class":685},[679,110773,93398],{"class":693},[679,110775,64453],{"class":880},[679,110777,110778],{"class":693},"(posts.",[679,110780,109740],{"class":880},[679,110782,110783],{"class":693},"(value),post));\n",[679,110785,110786],{"class":681,"line":3227},[679,110787,985],{"class":693},[679,110789,110790],{"class":681,"line":3232},[679,110791,889],{"emptyLinePlaceholder":797},[679,110793,110794,110796,110798,110800,110802],{"class":681,"line":3499},[679,110795,6872],{"class":693},[679,110797,92256],{"class":685},[679,110799,745],{"class":693},[679,110801,92261],{"class":689},[679,110803,1339],{"class":693},[679,110805,110806,110808,110810,110812,110814,110816,110818],{"class":681,"line":3509},[679,110807,3314],{"class":685},[679,110809,92272],{"class":880},[679,110811,73246],{"class":693},[679,110813,92277],{"class":685},[679,110815,88766],{"class":693},[679,110817,11341],{"class":2099},[679,110819,4390],{"class":693},[679,110821,110822,110824,110826,110828,110830,110832,110834,110836,110838],{"class":681,"line":3516},[679,110823,110674],{"class":693},[679,110825,92291],{"class":880},[679,110827,110585],{"class":693},[679,110829,16955],{"class":685},[679,110831,110590],{"class":693},[679,110833,11341],{"class":880},[679,110835,10541],{"class":693},[679,110837,14592],{"class":880},[679,110839,92307],{"class":693},[679,110841,110842],{"class":681,"line":3531},[679,110843,985],{"class":693},[679,110845,110846],{"class":681,"line":3536},[679,110847,889],{"emptyLinePlaceholder":797},[679,110849,110850],{"class":681,"line":3541},[679,110851,996],{"class":693},[651,110853,110854,110855,110858],{},"To populate our in-memory collection, we can use a service called JSONPlaceholder, which provides a fake data API. We'll create a new interface called ",[676,110856,110857],{},"JSONPlaceholderService",", which defines a method for loading posts from the JSONPlaceholder API. We'll implement this service to fetch 100 posts and populate our in-memory collection during application initialization.",[669,110860,110862],{"className":4107,"code":110861,"language":4109,"meta":674,"style":674},"public interface JsonPlaceholderService {\n\n @GetExchange(\"/posts\")\n List\u003CPost> loadPosts();\n\n}\n",[676,110863,110864,110875,110879,110892,110905,110909],{"__ignoreMap":674},[679,110865,110866,110868,110870,110873],{"class":681,"line":682},[679,110867,6073],{"class":685},[679,110869,6994],{"class":685},[679,110871,110872],{"class":880}," JsonPlaceholderService",[679,110874,884],{"class":693},[679,110876,110877],{"class":681,"line":790},[679,110878,889],{"emptyLinePlaceholder":797},[679,110880,110881,110883,110885,110887,110890],{"class":681,"line":892},[679,110882,6872],{"class":693},[679,110884,100228],{"class":685},[679,110886,745],{"class":693},[679,110888,110889],{"class":689},"\"/posts\"",[679,110891,1339],{"class":693},[679,110893,110894,110896,110898,110900,110903],{"class":681,"line":901},[679,110895,84917],{"class":693},[679,110897,5105],{"class":685},[679,110899,20881],{"class":693},[679,110901,110902],{"class":880},"loadPosts",[679,110904,9317],{"class":693},[679,110906,110907],{"class":681,"line":909},[679,110908,889],{"emptyLinePlaceholder":797},[679,110910,110911],{"class":681,"line":918},[679,110912,996],{"class":693},[669,110914,110916],{"className":4107,"code":110915,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n JsonPlaceholderService jsonPlaceholderService() {\n WebClient client = WebClient.builder()\n .baseUrl(\"https://jsonplaceholder.typicode.com\")\n .build();\n HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();\n return factory.createClient(JsonPlaceholderService.class);\n }\n}\n",[676,110917,110918,110924,110934,110938,110958,110966,110970,110974,110980,110990,111002,111015,111023,111045,111056,111060],{"__ignoreMap":674},[679,110919,110920,110922],{"class":681,"line":682},[679,110921,4116],{"class":693},[679,110923,6068],{"class":685},[679,110925,110926,110928,110930,110932],{"class":681,"line":790},[679,110927,6073],{"class":685},[679,110929,4512],{"class":685},[679,110931,16878],{"class":880},[679,110933,884],{"class":693},[679,110935,110936],{"class":681,"line":892},[679,110937,889],{"emptyLinePlaceholder":797},[679,110939,110940,110942,110944,110946,110948,110950,110952,110954,110956],{"class":681,"line":901},[679,110941,6089],{"class":685},[679,110943,6092],{"class":685},[679,110945,6095],{"class":685},[679,110947,6098],{"class":880},[679,110949,745],{"class":693},[679,110951,4758],{"class":685},[679,110953,16901],{"class":693},[679,110955,6108],{"class":2099},[679,110957,4390],{"class":693},[679,110959,110960,110962,110964],{"class":681,"line":909},[679,110961,6115],{"class":693},[679,110963,6118],{"class":880},[679,110965,16914],{"class":693},[679,110967,110968],{"class":681,"line":918},[679,110969,985],{"class":693},[679,110971,110972],{"class":681,"line":935},[679,110973,889],{"emptyLinePlaceholder":797},[679,110975,110976,110978],{"class":681,"line":944},[679,110977,6872],{"class":693},[679,110979,16929],{"class":685},[679,110981,110982,110985,110988],{"class":681,"line":959},[679,110983,110984],{"class":693}," JsonPlaceholderService ",[679,110986,110987],{"class":880},"jsonPlaceholderService",[679,110989,2667],{"class":693},[679,110991,110992,110994,110996,110998,111000],{"class":681,"line":964},[679,110993,100512],{"class":693},[679,110995,686],{"class":685},[679,110997,100517],{"class":693},[679,110999,90934],{"class":880},[679,111001,17545],{"class":693},[679,111003,111004,111006,111008,111010,111013],{"class":681,"line":977},[679,111005,73482],{"class":693},[679,111007,100528],{"class":880},[679,111009,745],{"class":693},[679,111011,111012],{"class":689},"\"https://jsonplaceholder.typicode.com\"",[679,111014,1339],{"class":693},[679,111016,111017,111019,111021],{"class":681,"line":982},[679,111018,73482],{"class":693},[679,111020,23612],{"class":880},[679,111022,9317],{"class":693},[679,111024,111025,111027,111029,111032,111034,111036,111038,111041,111043],{"class":681,"line":988},[679,111026,100570],{"class":693},[679,111028,686],{"class":685},[679,111030,111031],{"class":693}," HttpServiceProxyFactory.",[679,111033,90934],{"class":880},[679,111035,100584],{"class":693},[679,111037,100587],{"class":880},[679,111039,111040],{"class":693},"(client)).",[679,111042,23612],{"class":880},[679,111044,9317],{"class":693},[679,111046,111047,111049,111051,111053],{"class":681,"line":993},[679,111048,9444],{"class":685},[679,111050,100605],{"class":693},[679,111052,100608],{"class":880},[679,111054,111055],{"class":693},"(JsonPlaceholderService.class);\n",[679,111057,111058],{"class":681,"line":2129},[679,111059,985],{"class":693},[679,111061,111062],{"class":681,"line":2140},[679,111063,996],{"class":693},[669,111065,111067],{"className":4107,"code":111066,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private static final Logger log = LoggerFactory.getLogger(PostController.class);\n private final JsonPlaceholderService jsonPlaceholderService;\n private List\u003CPost> posts = new ArrayList\u003C>();\n\n public PostController(JsonPlaceholderService jsonPlaceholderService) {\n this.jsonPlaceholderService = jsonPlaceholderService;\n }\n\n // CRUD methods omitted for brevity\n\n @PostConstruct\n private void init() {\n // JsonPlaceHolder Service\n if(posts.isEmpty()) {\n log.info(\"Loading posts using JsonPlaceholderService\");\n posts = jsonPlaceholderService.loadPosts();\n }\n }\n}\n",[676,111068,111069,111075,111087,111097,111101,111121,111130,111146,111150,111163,111175,111179,111183,111188,111192,111198,111208,111213,111225,111238,111252,111256,111260],{"__ignoreMap":674},[679,111070,111071,111073],{"class":681,"line":682},[679,111072,4116],{"class":693},[679,111074,9212],{"class":685},[679,111076,111077,111079,111081,111083,111085],{"class":681,"line":790},[679,111078,4116],{"class":693},[679,111080,9275],{"class":685},[679,111082,745],{"class":693},[679,111084,23141],{"class":689},[679,111086,1339],{"class":693},[679,111088,111089,111091,111093,111095],{"class":681,"line":892},[679,111090,6073],{"class":685},[679,111092,4512],{"class":685},[679,111094,23152],{"class":880},[679,111096,884],{"class":693},[679,111098,111099],{"class":681,"line":901},[679,111100,889],{"emptyLinePlaceholder":797},[679,111102,111103,111105,111107,111109,111112,111114,111116,111118],{"class":681,"line":909},[679,111104,9232],{"class":685},[679,111106,6092],{"class":685},[679,111108,12768],{"class":685},[679,111110,111111],{"class":693}," Logger log ",[679,111113,686],{"class":685},[679,111115,9240],{"class":693},[679,111117,9243],{"class":880},[679,111119,111120],{"class":693},"(PostController.class);\n",[679,111122,111123,111125,111127],{"class":681,"line":918},[679,111124,9232],{"class":685},[679,111126,12768],{"class":685},[679,111128,111129],{"class":693}," JsonPlaceholderService jsonPlaceholderService;\n",[679,111131,111132,111134,111136,111138,111140,111142,111144],{"class":681,"line":935},[679,111133,9232],{"class":685},[679,111135,87217],{"class":693},[679,111137,5105],{"class":685},[679,111139,110481],{"class":693},[679,111141,686],{"class":685},[679,111143,2054],{"class":685},[679,111145,87229],{"class":693},[679,111147,111148],{"class":681,"line":944},[679,111149,889],{"emptyLinePlaceholder":797},[679,111151,111152,111154,111156,111159,111161],{"class":681,"line":959},[679,111153,6089],{"class":685},[679,111155,23152],{"class":880},[679,111157,111158],{"class":693},"(JsonPlaceholderService ",[679,111160,110987],{"class":2099},[679,111162,4390],{"class":693},[679,111164,111165,111167,111170,111172],{"class":681,"line":964},[679,111166,7862],{"class":931},[679,111168,111169],{"class":693},".jsonPlaceholderService ",[679,111171,686],{"class":685},[679,111173,111174],{"class":693}," jsonPlaceholderService;\n",[679,111176,111177],{"class":681,"line":977},[679,111178,985],{"class":693},[679,111180,111181],{"class":681,"line":982},[679,111182,889],{"emptyLinePlaceholder":797},[679,111184,111185],{"class":681,"line":988},[679,111186,111187],{"class":1400}," // CRUD methods omitted for brevity\n",[679,111189,111190],{"class":681,"line":993},[679,111191,889],{"emptyLinePlaceholder":797},[679,111193,111194,111196],{"class":681,"line":2129},[679,111195,6872],{"class":693},[679,111197,87391],{"class":685},[679,111199,111200,111202,111204,111206],{"class":681,"line":2140},[679,111201,9232],{"class":685},[679,111203,6095],{"class":685},[679,111205,36742],{"class":880},[679,111207,2667],{"class":693},[679,111209,111210],{"class":681,"line":2145},[679,111211,111212],{"class":1400}," // JsonPlaceHolder Service\n",[679,111214,111215,111217,111219,111222],{"class":681,"line":2154},[679,111216,1249],{"class":685},[679,111218,110778],{"class":693},[679,111220,111221],{"class":880},"isEmpty",[679,111223,111224],{"class":693},"()) {\n",[679,111226,111227,111229,111231,111233,111236],{"class":681,"line":2159},[679,111228,104907],{"class":693},[679,111230,9415],{"class":880},[679,111232,745],{"class":693},[679,111234,111235],{"class":689},"\"Loading posts using JsonPlaceholderService\"",[679,111237,1208],{"class":693},[679,111239,111240,111243,111245,111248,111250],{"class":681,"line":2164},[679,111241,111242],{"class":693}," posts ",[679,111244,686],{"class":685},[679,111246,111247],{"class":693}," jsonPlaceholderService.",[679,111249,110902],{"class":880},[679,111251,9317],{"class":693},[679,111253,111254],{"class":681,"line":3134},[679,111255,1290],{"class":693},[679,111257,111258],{"class":681,"line":3139},[679,111259,985],{"class":693},[679,111261,111262],{"class":681,"line":3144},[679,111263,996],{"class":693},[651,111265,111266],{},"Once we have our Spring Boot application and REST API set up, we can proceed to package and deploy our application to AWS Lambda.",[4542,111268,111270],{"id":111269},"packaging-and-deploying-to-aws-lambda","Packaging and Deploying to AWS Lambda",[651,111272,111273],{},"To package our application, we can use Maven. From the terminal, navigate to the root directory of your project and run the following command:",[669,111275,111276],{"className":5851,"code":81915,"language":5853,"meta":674,"style":674},[676,111277,111278],{"__ignoreMap":674},[679,111279,111280,111282,111284],{"class":681,"line":682},[679,111281,81922],{"class":880},[679,111283,81925],{"class":689},[679,111285,32496],{"class":689},[651,111287,111288,111289,111291],{},"This command will create a ZIP file in the ",[676,111290,77995],{}," directory, which contains our packaged application.",[651,111293,111294],{},"With our packaged application in hand, we can now proceed to deploy it to AWS Lambda. To create a new Lambda function, navigate to the AWS Management Console and select AWS Lambda. Click on \"Create function\" and choose the \"Author from scratch\" option.",[651,111296,111297],{},[660,111298],{"alt":111299,"src":111300},"Create new Lambda Function","/images/blog/2023/06/30/7uleLq2l1fkpOH0HzKEz-1297.12.png",[651,111302,111303],{},"In the function creation wizard, provide a name for your function and select \"Java 17\" as the runtime. Note that we'll be using the x86 architecture, as there are some limitations with Snapstart and Java 17 on other architectures.",[651,111305,111306],{},"After creating the function, you'll need to upload the ZIP file containing your packaged application. Click on \"Upload\" and select the ZIP file from your local machine.",[651,111308,111309,111310,111313,111314,111316],{},"Next, we need to configure the runtime settings and set the handler for our Lambda function. The handler is the entry point for our application and determines how requests are processed. In our case, the handler class is named ",[676,111311,111312],{},"dev.danvega.StreamLambdaHandler"," and the method to handle requests is ",[676,111315,93867],{},". Once you've set the handler, click \"Save\" to apply the configuration.",[651,111318,111319],{},"To test the Lambda function, you can use the AWS Management Console. Select the function, click on \"Test\", and create a new test event using the \"API Gateway AWS Proxy\" template. Fill in the necessary details, such as the path to the resource you want to test, and click \"Create\".",[651,111321,111322],{},"If everything is set up correctly, you should see a successful response from your Lambda function. You can also use the test event to verify that the different endpoints of your REST API are working as expected.",[651,111324,111325],{},"Now that we have our Lambda function up and running, we can create an API Gateway to serve as the front-end for our application. The API Gateway will act as a proxy, forwarding incoming requests to our Lambda function.",[4542,111327,111329],{"id":111328},"creating-an-api-gateway","Creating an API Gateway",[651,111331,111332],{},"To create an API Gateway, navigate to the AWS Management Console and select API Gateway. Click on \"Create API\" and choose the \"REST API\" option.",[651,111334,111335],{},"Provide a name for your API and choose \"Create API\". This will create a new REST API in API Gateway.",[651,111337,111338,111339,111342],{},"To define the resources and methods for our API, we'll create a new resource for each endpoint of our REST API. For example, if we have an endpoint ",[676,111340,111341],{},"/posts",", we'll create a resource named \"Posts\". Under each resource, we can define the HTTP methods that are allowed, such as GET, POST, PUT, and DELETE.",[651,111344,111345],{},[660,111346],{"alt":111347,"src":111348},"API Gateway","/images/blog/2023/06/30/alA7JYq3UOnAEEbsdORU-1607.83.png",[651,111350,111351],{},"To configure the integration between the API Gateway and our Lambda function, select a resource and method, and click on \"Integration Request\". Choose the Lambda function as the integration type and select the appropriate Lambda function from the list. Click \"Save\" to apply the changes.",[651,111353,111354],{},"Once the integration is set up, you can deploy your API by creating a new stage. This will provide you with a unique Invoke URL that you can use to access your API.",[651,111356,111357],{},"Congratulations! You have successfully deployed your Spring Boot application to AWS Lambda using API Gateway as a front-end proxy. You can now access your REST API using the provided Invoke URL.",[4542,111359,9042],{"id":9041},[651,111361,111362],{},"In this comprehensive blog post, we explored the process of deploying Spring Boot applications to AWS Lambda. We discussed the benefits of serverless computing, such as automatic scaling, reduced costs, and high availability. By leveraging AWS Lambda, you can offload the burden of server management and focus on developing and delivering your applications.",[651,111364,111365],{},"We walked through the process step-by-step, from creating a new Spring Boot project using a Maven archetype specifically designed for AWS Lambda, to packaging and deploying the application to AWS Lambda using the AWS Management Console.",[651,111367,111368],{},"Now that you have the knowledge and resources to deploy your Spring Boot applications to AWS Lambda, you can unlock the power of serverless computing and take your projects to the next level.",[651,111370,111371],{},"If you have any questions or need further assistance, please don't hesitate to leave a comment and I'll be happy to help. I hope you found this blog post helpful and informative. As always, happy coding, friends!",[786,111373,111374],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":111376},[111377,111378,111379,111380,111381,111382],{"id":110266,"depth":790,"text":110267},{"id":110276,"depth":790,"text":110277},{"id":110289,"depth":790,"text":110290},{"id":111269,"depth":790,"text":111270},{"id":111328,"depth":790,"text":111329},{"id":9041,"depth":790,"text":9042},"Discover effective solutions for hosting your personal spring boot projects without hefty costs. Boost your skills with Dan Vega, your spring developer advocate, while exploring the capabilities of AWS Lambda.",{"slug":111385,"date":111386,"published":797,"author":798,"tags":111387,"cover":109068,"video":111388,"github":110298,"keywords":111389},"aws-lambda-spring-boot-3","2023-06-30T15:00:00.000Z",[7077,79471],"https://www.youtube.com/embed/GGPPkUcHleQ","Spring Boot, AWS, AWS Lambda, Serverless, Java, Spring, Spring Boot 3",{"title":135,"description":111383},"blog/2023/06/30/aws-lambda-spring-boot-3","XKvvJS8yVvcRiSt0kw-dxp0DYjVuBniw5ZN3QC1INME",{"id":111394,"title":132,"body":111395,"description":112224,"extension":793,"meta":112225,"navigation":797,"path":133,"seo":112233,"stem":112234,"__hash__":112235},"content/blog/2023/07/13/graphql-schema-mapping-inspection.md",{"type":648,"value":111396,"toc":112215},[111397,111400,111403,111406,111410,111413,111416,111419,111422,111426,111433,111438,111441,111445,111455,111480,111502,111560,111573,111581,111605,111706,111778,111781,111785,111804,111831,111836,111840,111843,111857,111868,111873,111977,111993,112138,112148,112203,112205,112208,112211,112213],[651,111398,111399],{},"If you have been following this channel, you know that Im am a big fan of GraphQL, especially when paired with Spring. Today, we will delve into some exciting new releases in Spring for GraphQL and share how they can improve your development workflow.",[651,111401,111402],{},"I've created a number of tutorials centred on these recent updates but thought it appropriate to kick things off by examining the inspection report. A lesser-known feature to many, the inspection report can be an absolute game changer in how you troubleshoot issues in your GraphQL schema.",[651,111404,111405],{},"Let's dive in!",[4542,111407,111409],{"id":111408},"what-is-the-inspection-report-in-graphql","What is the Inspection Report in GraphQL?",[651,111411,111412],{},"While working with GraphQL, you often start with writing your schema, in what's known as the schema-first approach. You may write a controller with methods typically referred to as \"data fetchers\" or \"data resolvers\" that pair with your schema. There are certain situations where mismatches can occur between the schema and the class on the Java side or between the schema and controller methods. These mismatches tend to create confusion since it might be challenging to pinpoint where things went awry.",[651,111414,111415],{},"Often, it's difficult to determine if the returned null is genuine because no data was found, or it resulted from a missing mapping. This is where the inspection report swoops in to save the day, providing a seamless way to troubleshoot these inconsistencies.",[651,111417,111418],{},"By enabling an opt-in inspection feature, you can configure your system to generate a report delineating what's happening in your schema. This, in turn, allows you to spot what's missing quickly and efficiently.",[651,111420,111421],{},"Today, we'll create a sample application around the idea of conference events and sessions using this fabulous feature. Our mission will be to establish a simple project, thereby illustrating how to use the inspection report and how it can significantly improve your GraphQL debugging experience.",[4542,111423,111425],{"id":111424},"beginning-our-project-the-building-blocks","Beginning Our Project: The Building Blocks",[651,111427,111428,111429,111432],{},"To kick off our project, we'll use ",[812,111430,7117],{"href":51358,"rel":111431},[816],". We will choose a Maven project using Java, with Web and Spring for GraphQL as our only dependencies.",[651,111434,111435],{},[660,111436],{"alt":7117,"src":111437},"/images/blog/2023/07/13/spring-init.png",[651,111439,111440],{},"Please note that the version used in this blog is 3.1.1, which is the latest version at the time of writing. With our project generated and opened in our favorite ID or editor, we will commence with writing some code.",[5909,111442,111444],{"id":111443},"adding-a-schema","Adding a Schema",[651,111446,75826,111447,84406,111449,111451,111452,111454],{},[676,111448,87939],{},[676,111450,87935],{}," which will contain our schema. In terms of creating a schema, we have the freedom to start with any root-level operation types, such as query, mutation, and subscription. In this case, we'll begin by modelling a conference event as an object type, which we'll name ",[676,111453,76320],{},". It features properties such as:",[5316,111456,111457,111459,111461,111463,111466,111469,111472,111475,111477],{},[5332,111458,22662],{},[5332,111460,23058],{},[5332,111462,12285],{},[5332,111464,111465],{},"Start date",[5332,111467,111468],{},"End date",[5332,111470,111471],{},"CFP start date (Call for Proposals start date)",[5332,111473,111474],{},"CFP end date",[5332,111476,76341],{},[5332,111478,111479],{},"Website",[651,111481,111482,111483,111485,111486,23212,111488,111490,111491,23212,111494,111497,111498,111501],{},"The event details are expected to be of a certain type. For instance, the ",[676,111484,22662],{}," is an identifier type, ",[676,111487,16334],{},[676,111489,76293],{}," are strings, ",[676,111492,111493],{},"start date",[676,111495,111496],{},"end date"," are dates, while ",[676,111499,111500],{},"website"," is a URL.",[669,111503,111505],{"className":66259,"code":111504,"language":66261,"meta":674,"style":674},"type Event {\n id: ID!\n name: String!\n description: String\n startDate: Date\n endDate: Date\n cfpStartDate: Date\n cfpEndDate: Date\n location: String\n website: URL\n}\n",[676,111506,111507,111512,111516,111521,111526,111531,111536,111541,111546,111551,111556],{"__ignoreMap":674},[679,111508,111509],{"class":681,"line":682},[679,111510,111511],{},"type Event {\n",[679,111513,111514],{"class":681,"line":790},[679,111515,86545],{},[679,111517,111518],{"class":681,"line":892},[679,111519,111520],{}," name: String!\n",[679,111522,111523],{"class":681,"line":901},[679,111524,111525],{}," description: String\n",[679,111527,111528],{"class":681,"line":909},[679,111529,111530],{}," startDate: Date\n",[679,111532,111533],{"class":681,"line":918},[679,111534,111535],{}," endDate: Date\n",[679,111537,111538],{"class":681,"line":935},[679,111539,111540],{}," cfpStartDate: Date\n",[679,111542,111543],{"class":681,"line":944},[679,111544,111545],{}," cfpEndDate: Date\n",[679,111547,111548],{"class":681,"line":959},[679,111549,111550],{}," location: String\n",[679,111552,111553],{"class":681,"line":964},[679,111554,111555],{}," website: URL\n",[679,111557,111558],{"class":681,"line":977},[679,111559,996],{},[651,111561,111562,111563,23212,111565,111567,111568,664],{},"Notice that the ",[676,111564,48502],{},[676,111566,76281],{}," are not built-in types which means we need to declare them as extended scalars. If you need more examples on how to handle extended scalar types in GraphQL, check out this ",[812,111569,111572],{"href":111570,"rel":111571},"https://youtu.be/ooknmgr4WiA",[816],"previous video tutorial",[651,111574,111575,111576,23212,111578,111580],{},"Here's some code on how you can add the ",[676,111577,48502],{},[676,111579,76281],{}," extended scalars.",[669,111582,111583],{"className":9101,"code":98914,"language":9103,"meta":674,"style":674},[676,111584,111585,111589,111593,111597,111601],{"__ignoreMap":674},[679,111586,111587],{"class":681,"line":682},[679,111588,9110],{},[679,111590,111591],{"class":681,"line":790},[679,111592,98925],{},[679,111594,111595],{"class":681,"line":892},[679,111596,98930],{},[679,111598,111599],{"class":681,"line":901},[679,111600,98935],{},[679,111602,111603],{"class":681,"line":909},[679,111604,9125],{},[669,111606,111608],{"className":4107,"code":111607,"language":4109,"meta":674,"style":674},"@Configuration\npublic class GraphQlConfiguration {\n\n private static final Logger log = LoggerFactory.getLogger(GraphQlConfiguration.class);\n\n @Bean\n public RuntimeWiringConfigurer runtimeWiringConfigurer() {\n return wiringBuilder -> wiringBuilder\n .scalar(ExtendedScalars.Date)\n .scalar(ExtendedScalars.Url);\n }\n}\n",[676,111609,111610,111616,111627,111631,111650,111654,111660,111670,111680,111689,111698,111702],{"__ignoreMap":674},[679,111611,111612,111614],{"class":681,"line":682},[679,111613,4116],{"class":693},[679,111615,6212],{"class":685},[679,111617,111618,111620,111622,111625],{"class":681,"line":790},[679,111619,6073],{"class":685},[679,111621,4512],{"class":685},[679,111623,111624],{"class":880}," GraphQlConfiguration",[679,111626,884],{"class":693},[679,111628,111629],{"class":681,"line":892},[679,111630,889],{"emptyLinePlaceholder":797},[679,111632,111633,111635,111637,111639,111641,111643,111645,111647],{"class":681,"line":901},[679,111634,9232],{"class":685},[679,111636,6092],{"class":685},[679,111638,12768],{"class":685},[679,111640,111111],{"class":693},[679,111642,686],{"class":685},[679,111644,9240],{"class":693},[679,111646,9243],{"class":880},[679,111648,111649],{"class":693},"(GraphQlConfiguration.class);\n",[679,111651,111652],{"class":681,"line":909},[679,111653,889],{"emptyLinePlaceholder":797},[679,111655,111656,111658],{"class":681,"line":918},[679,111657,6872],{"class":693},[679,111659,16929],{"class":685},[679,111661,111662,111664,111666,111668],{"class":681,"line":935},[679,111663,6089],{"class":685},[679,111665,98981],{"class":693},[679,111667,98984],{"class":880},[679,111669,2667],{"class":693},[679,111671,111672,111674,111676,111678],{"class":681,"line":944},[679,111673,9444],{"class":685},[679,111675,98993],{"class":693},[679,111677,16955],{"class":685},[679,111679,98998],{"class":693},[679,111681,111682,111684,111686],{"class":681,"line":959},[679,111683,73482],{"class":693},[679,111685,99005],{"class":880},[679,111687,111688],{"class":693},"(ExtendedScalars.Date)\n",[679,111690,111691,111693,111695],{"class":681,"line":964},[679,111692,73482],{"class":693},[679,111694,99005],{"class":880},[679,111696,111697],{"class":693},"(ExtendedScalars.Url);\n",[679,111699,111700],{"class":681,"line":977},[679,111701,985],{"class":693},[679,111703,111704],{"class":681,"line":982},[679,111705,996],{"class":693},[669,111707,111709],{"className":66259,"code":111708,"language":66261,"meta":674,"style":674},"scalar Date @specifiedBy(url:\"https://tools.ietf.org/html/rfc3339\")\nscalar Url @specifiedBy(url:\"https://www.w3.org/Addressing/URL/url-spec.txt\")\ntype Event {\n id: ID!\n name: String!\n description: String!\n startDate: Date!\n endDate: Date!\n cfpStartDate: Date!\n cfpEndDate: Date!\n location: String\n website: Url\n sessions(first: Int,last: Int,before: String,after: String): SessionConnection\n}\n\n",[676,111710,111711,111716,111721,111725,111729,111734,111739,111744,111749,111754,111759,111764,111769,111774],{"__ignoreMap":674},[679,111712,111713],{"class":681,"line":682},[679,111714,111715],{},"scalar Date @specifiedBy(url:\"https://tools.ietf.org/html/rfc3339\")\n",[679,111717,111718],{"class":681,"line":790},[679,111719,111720],{},"scalar Url @specifiedBy(url:\"https://www.w3.org/Addressing/URL/url-spec.txt\")\n",[679,111722,111723],{"class":681,"line":892},[679,111724,111511],{},[679,111726,111727],{"class":681,"line":901},[679,111728,98559],{},[679,111730,111731],{"class":681,"line":909},[679,111732,111733],{}," name: String!\n",[679,111735,111736],{"class":681,"line":918},[679,111737,111738],{}," description: String!\n",[679,111740,111741],{"class":681,"line":935},[679,111742,111743],{}," startDate: Date!\n",[679,111745,111746],{"class":681,"line":944},[679,111747,111748],{}," endDate: Date!\n",[679,111750,111751],{"class":681,"line":959},[679,111752,111753],{}," cfpStartDate: Date!\n",[679,111755,111756],{"class":681,"line":964},[679,111757,111758],{}," cfpEndDate: Date!\n",[679,111760,111761],{"class":681,"line":977},[679,111762,111763],{}," location: String\n",[679,111765,111766],{"class":681,"line":982},[679,111767,111768],{}," website: Url\n",[679,111770,111771],{"class":681,"line":988},[679,111772,111773],{}," sessions(first: Int,last: Int,before: String,after: String): SessionConnection\n",[679,111775,111776],{"class":681,"line":993},[679,111777,996],{},[651,111779,111780],{},"At this point, we have a schema in place for our GraphQL API.",[4542,111782,111784],{"id":111783},"java-side-adding-a-spring-record","Java Side: Adding a Spring Record",[651,111786,111787,111788,111791,111792,111795,111796,111798,111799,111801,111802,664],{},"To mirror the GraphQL schema in our Java code, we create a new package for events, ",[676,111789,111790],{},"com.example.demo.event"," package. This will contain a ",[676,111793,111794],{},"spring record"," named ",[676,111797,76320],{},", which will contain two properties initially: integer ",[676,111800,11341],{}," and string ",[676,111803,16334],{},[669,111805,111807],{"className":4107,"code":111806,"language":4109,"meta":674,"style":674},"public record Event(@NotBlank Integer id,@NotBlank String name) {}\n",[676,111808,111809],{"__ignoreMap":674},[679,111810,111811,111813,111815,111818,111820,111823,111826,111828],{"class":681,"line":682},[679,111812,6073],{"class":685},[679,111814,86928],{"class":685},[679,111816,111817],{"class":880}," Event",[679,111819,73246],{"class":693},[679,111821,111822],{"class":685},"NotBlank",[679,111824,111825],{"class":693}," Integer id,@",[679,111827,111822],{"class":685},[679,111829,111830],{"class":693}," String name) {}\n",[651,111832,111833,111834,664],{},"When we define a query and then run the application, we notice that everything starts up correctly with no obvious indications of any mismatches. However, when we run some query requests, they return ",[676,111835,1146],{},[4542,111837,111839],{"id":111838},"how-to-enable-graphql-inspection-reports-unmasking-mismatches","How to Enable GraphQL Inspection Reports: Unmasking Mismatches",[651,111841,111842],{},"To avoid confusion and make diagnosing mismatches easier, we can enable the inspection feature of GraphQL to check that all schema fields have a corresponding data fetcher. There are two main steps involved in enabling inspection reports:",[27665,111844,111845],{},[5332,111846,111847,111848,51393,111850,111852,111853,111856],{},"Enable introspection in the ",[676,111849,40799],{},[676,111851,16242],{}," file by setting the property ",[676,111854,111855],{},"spring.graphql.schema.introspection.enabled"," to true:",[669,111858,111860],{"className":76589,"code":111859,"language":35538,"meta":674,"style":674},"spring.graphql.schema.introspection.enabled=true\n",[676,111861,111862],{"__ignoreMap":674},[679,111863,111864,111866],{"class":681,"line":682},[679,111865,111855],{"class":685},[679,111867,93485],{"class":693},[27665,111869,111870],{"start":790},[5332,111871,111872],{},"Add a custom bean to the source builder of your GraphQL API which will handle the report. You access the report and choose what to do with it. In our case, we'll log it as a string.",[669,111874,111876],{"className":4107,"code":111875,"language":4109,"meta":674,"style":674},"@Configuration\npublic class GraphQlConfiguration {\n\n private static final Logger log = LoggerFactory.getLogger(GraphQlConfiguration.class);\n\n @Bean\n GraphQlSourceBuilderCustomizer inspectionCustomizer() {\n return source -> source.inspectSchemaMappings(report -> log.info(report.toString()));\n }\n}\n",[676,111877,111878,111884,111894,111898,111916,111920,111926,111936,111969,111973],{"__ignoreMap":674},[679,111879,111880,111882],{"class":681,"line":682},[679,111881,4116],{"class":693},[679,111883,6212],{"class":685},[679,111885,111886,111888,111890,111892],{"class":681,"line":790},[679,111887,6073],{"class":685},[679,111889,4512],{"class":685},[679,111891,111624],{"class":880},[679,111893,884],{"class":693},[679,111895,111896],{"class":681,"line":892},[679,111897,889],{"emptyLinePlaceholder":797},[679,111899,111900,111902,111904,111906,111908,111910,111912,111914],{"class":681,"line":901},[679,111901,9232],{"class":685},[679,111903,6092],{"class":685},[679,111905,12768],{"class":685},[679,111907,111111],{"class":693},[679,111909,686],{"class":685},[679,111911,9240],{"class":693},[679,111913,9243],{"class":880},[679,111915,111649],{"class":693},[679,111917,111918],{"class":681,"line":909},[679,111919,889],{"emptyLinePlaceholder":797},[679,111921,111922,111924],{"class":681,"line":918},[679,111923,6872],{"class":693},[679,111925,16929],{"class":685},[679,111927,111928,111931,111934],{"class":681,"line":935},[679,111929,111930],{"class":693}," GraphQlSourceBuilderCustomizer ",[679,111932,111933],{"class":880},"inspectionCustomizer",[679,111935,2667],{"class":693},[679,111937,111938,111940,111943,111945,111948,111951,111954,111956,111959,111961,111964,111966],{"class":681,"line":944},[679,111939,9444],{"class":685},[679,111941,111942],{"class":693}," source ",[679,111944,16955],{"class":685},[679,111946,111947],{"class":693}," source.",[679,111949,111950],{"class":880},"inspectSchemaMappings",[679,111952,111953],{"class":693},"(report ",[679,111955,16955],{"class":685},[679,111957,111958],{"class":693}," log.",[679,111960,9415],{"class":880},[679,111962,111963],{"class":693},"(report.",[679,111965,14391],{"class":880},[679,111967,111968],{"class":693},"()));\n",[679,111970,111971],{"class":681,"line":959},[679,111972,985],{"class":693},[679,111974,111975],{"class":681,"line":964},[679,111976,996],{"class":693},[651,111978,111979,111980,111983,111984,23212,111986,111988,111989,111992],{},"The complete configuration of the ",[676,111981,111982],{},"GraphQLConfig"," class also includes declaring exposure of extended scalars, such as ",[676,111985,48502],{},[676,111987,76281],{},", using a ",[676,111990,111991],{},"RuntimeWiringConfigurer"," bean. Here's how that entire class could look like:",[669,111994,111996],{"className":4107,"code":111995,"language":4109,"meta":674,"style":674},"@Configuration\npublic class GraphQlConfiguration {\n\n private static final Logger log = LoggerFactory.getLogger(GraphQlConfiguration.class);\n\n @Bean\n public RuntimeWiringConfigurer runtimeWiringConfigurer() {\n return wiringBuilder -> wiringBuilder\n .scalar(ExtendedScalars.Date)\n .scalar(ExtendedScalars.Url);\n }\n\n @Bean\n GraphQlSourceBuilderCustomizer inspectionCustomizer() {\n return source -> source.inspectSchemaMappings(report -> log.info(report.toString()));\n }\n}\n",[676,111997,111998,112004,112014,112018,112036,112040,112046,112056,112066,112074,112082,112086,112090,112096,112104,112130,112134],{"__ignoreMap":674},[679,111999,112000,112002],{"class":681,"line":682},[679,112001,4116],{"class":693},[679,112003,6212],{"class":685},[679,112005,112006,112008,112010,112012],{"class":681,"line":790},[679,112007,6073],{"class":685},[679,112009,4512],{"class":685},[679,112011,111624],{"class":880},[679,112013,884],{"class":693},[679,112015,112016],{"class":681,"line":892},[679,112017,889],{"emptyLinePlaceholder":797},[679,112019,112020,112022,112024,112026,112028,112030,112032,112034],{"class":681,"line":901},[679,112021,9232],{"class":685},[679,112023,6092],{"class":685},[679,112025,12768],{"class":685},[679,112027,111111],{"class":693},[679,112029,686],{"class":685},[679,112031,9240],{"class":693},[679,112033,9243],{"class":880},[679,112035,111649],{"class":693},[679,112037,112038],{"class":681,"line":909},[679,112039,889],{"emptyLinePlaceholder":797},[679,112041,112042,112044],{"class":681,"line":918},[679,112043,6872],{"class":693},[679,112045,16929],{"class":685},[679,112047,112048,112050,112052,112054],{"class":681,"line":935},[679,112049,6089],{"class":685},[679,112051,98981],{"class":693},[679,112053,98984],{"class":880},[679,112055,2667],{"class":693},[679,112057,112058,112060,112062,112064],{"class":681,"line":944},[679,112059,9444],{"class":685},[679,112061,98993],{"class":693},[679,112063,16955],{"class":685},[679,112065,98998],{"class":693},[679,112067,112068,112070,112072],{"class":681,"line":959},[679,112069,73482],{"class":693},[679,112071,99005],{"class":880},[679,112073,111688],{"class":693},[679,112075,112076,112078,112080],{"class":681,"line":964},[679,112077,73482],{"class":693},[679,112079,99005],{"class":880},[679,112081,111697],{"class":693},[679,112083,112084],{"class":681,"line":977},[679,112085,985],{"class":693},[679,112087,112088],{"class":681,"line":982},[679,112089,889],{"emptyLinePlaceholder":797},[679,112091,112092,112094],{"class":681,"line":988},[679,112093,6872],{"class":693},[679,112095,16929],{"class":685},[679,112097,112098,112100,112102],{"class":681,"line":993},[679,112099,111930],{"class":693},[679,112101,111933],{"class":880},[679,112103,2667],{"class":693},[679,112105,112106,112108,112110,112112,112114,112116,112118,112120,112122,112124,112126,112128],{"class":681,"line":2129},[679,112107,9444],{"class":685},[679,112109,111942],{"class":693},[679,112111,16955],{"class":685},[679,112113,111947],{"class":693},[679,112115,111950],{"class":880},[679,112117,111953],{"class":693},[679,112119,16955],{"class":685},[679,112121,111958],{"class":693},[679,112123,9415],{"class":880},[679,112125,111963],{"class":693},[679,112127,14391],{"class":880},[679,112129,111968],{"class":693},[679,112131,112132],{"class":681,"line":2140},[679,112133,985],{"class":693},[679,112135,112136],{"class":681,"line":2145},[679,112137,996],{"class":693},[651,112139,112140,112141,112144,112145,112147],{},"With the inspection report enabled, it becomes straightforward to identify that there is an \"unmapped field\" ",[676,112142,112143],{},"events"," in our query. We can infer that the query ",[676,112146,112143],{}," does not map to any controller method in our schema. If you run the application you should see the report logged to the console:",[669,112149,112151],{"className":5851,"code":112150,"language":5853,"meta":674,"style":674},"Unmapped fields: {Event=[description, startDate, endDate, cfpStartDate, cfpEndDate, location, website]}\nUnmapped registrations: {}\nSkipped types: []\n",[676,112152,112153,112182,112192],{"__ignoreMap":674},[679,112154,112155,112158,112161,112164,112167,112170,112173,112176,112179],{"class":681,"line":682},[679,112156,112157],{"class":880},"Unmapped",[679,112159,112160],{"class":689}," fields:",[679,112162,112163],{"class":689}," {Event=[description,",[679,112165,112166],{"class":689}," startDate,",[679,112168,112169],{"class":689}," endDate,",[679,112171,112172],{"class":689}," cfpStartDate,",[679,112174,112175],{"class":689}," cfpEndDate,",[679,112177,112178],{"class":689}," location,",[679,112180,112181],{"class":689}," website]}\n",[679,112183,112184,112186,112189],{"class":681,"line":790},[679,112185,112157],{"class":880},[679,112187,112188],{"class":689}," registrations:",[679,112190,112191],{"class":689}," {}\n",[679,112193,112194,112197,112200],{"class":681,"line":892},[679,112195,112196],{"class":880},"Skipped",[679,112198,112199],{"class":689}," types:",[679,112201,112202],{"class":693}," []\n",[4542,112204,78006],{"id":78005},[651,112206,112207],{},"While it may seem subtle, the inspection report is an incredibly powerful feature in Spring for GraphQL. It enhances the ease of troubleshooting potential mismatches between your GraphQL schema and Java code, saving you lots of time in your debugging.",[651,112209,112210],{},"I'll be creating more tutorials on Spring GraphQL, including best practices on paging and sorting. If you find this interest in these topics, please let me know.",[651,112212,78024],{},[786,112214,104979],{},{"title":674,"searchDepth":790,"depth":790,"links":112216},[112217,112218,112221,112222,112223],{"id":111408,"depth":790,"text":111409},{"id":111424,"depth":790,"text":111425,"children":112219},[112220],{"id":111443,"depth":892,"text":111444},{"id":111783,"depth":790,"text":111784},{"id":111838,"depth":790,"text":111839},{"id":78005,"depth":790,"text":78006},"Join VMware's Spring Developer Advocate, Dan Vega, as he explores the exciting new releases in Spring for GraphQL. Dive into the enhanced features of the inspection report, walk through how to create a project, and more!",{"slug":112226,"date":112227,"published":797,"author":798,"tags":112228,"cover":112229,"video":112230,"github":112231,"keywords":112232},"graphql-schema-mapping-inspection","2023-07-13T15:00:00.000Z",[7077,99135],"./graphql-schema-inspection.png","https://www.youtube.com/embed/YBPG0JbHvpY","https://github.com/danvega/gqli","Spring Boot, Spring for GraphQL, GraphQL Java, Java, GraphQL, GraphQL Schema, GraphQL Schema Mapping",{"title":132,"description":112224},"blog/2023/07/13/graphql-schema-mapping-inspection","kQbifut4eR97KSYKeCwwxue_NL4H6gq3Ikolv3Lp6ek",{"id":112237,"title":129,"body":112238,"description":113171,"extension":793,"meta":113172,"navigation":797,"path":130,"seo":113180,"stem":113181,"__hash__":113182},"content/blog/2023/07/17/pgadmin-docker-compose.md",{"type":648,"value":112239,"toc":113162},[112240,112248,112252,112255,112258,112265,112270,112274,112281,112288,112291,112295,112298,112301,112427,112433,112439,112475,112479,112482,112799,112802,112805,112809,112815,112849,112863,112869,112872,112876,112884,112889,112959,112974,113004,113014,113142,113145,113151,113153,113156,113159],[651,112241,52282,112242,112247],{},[812,112243,112246],{"href":112244,"rel":112245},"https://danvega.dev/blog/2023/04/26/spring-boot-docker-compose/",[816],"previous blog post"," we looked at the new Docker Compose feature that was added to Spring Boot 3.1. In that article (and accompanying video) Spring Boot 3.1 had yet to be released and since then a couple of things have changed so I decided to follow it up with another tutorial. I also received a few questions around how to gain visibility into a PostgreSQL database when using it inside of a container so I will try and answer that here as well.",[4542,112249,112251],{"id":112250},"why-the-follow-up","Why the Follow-up?",[651,112253,112254],{},"Firstly, when I recorded the previous video, version 3.1 wasn't released yet. So we were peeking at an unreleased feature. Now that 3.1 is out, I am excited to show you a new development - the automatic generation of the Docker Compose file when you create a project.",[651,112256,112257],{},"Secondly, in the last video, we took a gander at the Postgres database service. I received several queries about gaining insights and exploring the tables and data in the Postgres database container. So today, I'll be addressing this by demonstrating two options - using psql in a shell, and using PG Admin, a tool that provides a graphical exploration of our database.",[651,112259,112260,112261,112264],{},"So without further ado, let's head over to ",[812,112262,77478],{"href":20748,"rel":112263},[816]," and get started.",[651,112266,112267],{},[660,112268],{"alt":7117,"src":112269},"/images/blog/2023/07/17/spring-init.png",[4542,112271,112273],{"id":112272},"getting-started-with-spring-io","Getting Started with Spring IO",[651,112275,112276,112277,112280],{},"At ",[812,112278,77478],{"href":20748,"rel":112279},[816],", choose a Maven project using Java and the latest version of Spring Boot - 3.1.1, at the time of this recording. For the group ID, I've chosen \"dev.danvega\" and named the project \"pgAdmin.\" I've selected Java 17 and included the following dependencies: the web dependency, Spring Data JPA, Postgres driver, and Spring Boot's Docker Compose support.",[651,112282,112283,112284,112287],{},"Now, let's explore the project quickly. You'll notice that a ",[676,112285,112286],{},"compose.yml"," is already created for you - no need to manually write the file. It's generated automatically because we included the Docker Compose module and defined a service that needs to be written into it.",[651,112289,112290],{},"Following this, simply generate the project - you'll download a zip file which you can open up in whatever IDE or text editor you prefer. I'll be using the IntelliJ Ultimate Edition.",[4542,112292,112294],{"id":112293},"writing-a-simple-application-with-conference-events-and-sessions","Writing a Simple Application with Conference Events and Sessions",[651,112296,112297],{},"We're working on a simple application called \"Sessions,\" where we can manage events with sessions and speakers. The aim is to create a type of data that we can save to a database.",[651,112299,112300],{},"Then, we create a new package named \"event\" and a new entity called \"Event.\" The Event class looks like this:",[669,112302,112304],{"className":4107,"code":112303,"language":4109,"meta":674,"style":674},"@Entity\npublic class Event {\n\n @Id\n private Integer id;\n private String name;\n @Column(columnDefinition = \"TEXT\")\n private String description;\n private LocalDate startDate;\n private LocalDate endDate;\n private LocalDate cfpStartDate;\n private LocalDate cfpEndDate;\n private String location;\n private String website;\n\n // constructors, getters,setters & toString\n\n}\n",[676,112305,112306,112312,112322,112326,112332,112338,112344,112362,112369,112376,112383,112390,112397,112404,112410,112414,112419,112423],{"__ignoreMap":674},[679,112307,112308,112310],{"class":681,"line":682},[679,112309,4116],{"class":693},[679,112311,11234],{"class":685},[679,112313,112314,112316,112318,112320],{"class":681,"line":790},[679,112315,6073],{"class":685},[679,112317,4512],{"class":685},[679,112319,111817],{"class":880},[679,112321,884],{"class":693},[679,112323,112324],{"class":681,"line":892},[679,112325,889],{"emptyLinePlaceholder":797},[679,112327,112328,112330],{"class":681,"line":901},[679,112329,6872],{"class":693},[679,112331,33530],{"class":685},[679,112333,112334,112336],{"class":681,"line":909},[679,112335,9232],{"class":685},[679,112337,83952],{"class":693},[679,112339,112340,112342],{"class":681,"line":918},[679,112341,9232],{"class":685},[679,112343,16319],{"class":693},[679,112345,112346,112348,112350,112352,112355,112357,112360],{"class":681,"line":935},[679,112347,6872],{"class":693},[679,112349,33853],{"class":685},[679,112351,745],{"class":693},[679,112353,112354],{"class":931},"columnDefinition",[679,112356,6883],{"class":685},[679,112358,112359],{"class":689}," \"TEXT\"",[679,112361,1339],{"class":693},[679,112363,112364,112366],{"class":681,"line":944},[679,112365,9232],{"class":685},[679,112367,112368],{"class":693}," String description;\n",[679,112370,112371,112373],{"class":681,"line":959},[679,112372,9232],{"class":685},[679,112374,112375],{"class":693}," LocalDate startDate;\n",[679,112377,112378,112380],{"class":681,"line":964},[679,112379,9232],{"class":685},[679,112381,112382],{"class":693}," LocalDate endDate;\n",[679,112384,112385,112387],{"class":681,"line":977},[679,112386,9232],{"class":685},[679,112388,112389],{"class":693}," LocalDate cfpStartDate;\n",[679,112391,112392,112394],{"class":681,"line":982},[679,112393,9232],{"class":685},[679,112395,112396],{"class":693}," LocalDate cfpEndDate;\n",[679,112398,112399,112401],{"class":681,"line":988},[679,112400,9232],{"class":685},[679,112402,112403],{"class":693}," String location;\n",[679,112405,112406,112408],{"class":681,"line":993},[679,112407,9232],{"class":685},[679,112409,33585],{"class":693},[679,112411,112412],{"class":681,"line":2129},[679,112413,889],{"emptyLinePlaceholder":797},[679,112415,112416],{"class":681,"line":2140},[679,112417,112418],{"class":1400}," // constructors, getters,setters & toString\n",[679,112420,112421],{"class":681,"line":2145},[679,112422,889],{"emptyLinePlaceholder":797},[679,112424,112425],{"class":681,"line":2154},[679,112426,996],{"class":693},[651,112428,112429,112430,78287],{},"An event has properties such as ID, name, description, start date, end date, CFP start and end date (Call for Proposals), location, and a website. We then need some constructors, getters, and setters for our Event class, and a ",[676,112431,112432],{},"toString()",[651,112434,112435,112436,112438],{},"Now, we need a repository for \"Event\" - we can simply create an interface and have it extend the ",[676,112437,97758],{}," interface in Spring Data.",[669,112440,112442],{"className":4107,"code":112441,"language":4109,"meta":674,"style":674},"public interface EventRepository extends ListCrudRepository\u003CEvent,Integer> {\n\n}\n",[676,112443,112444,112467,112471],{"__ignoreMap":674},[679,112445,112446,112448,112450,112453,112455,112457,112459,112461,112463,112465],{"class":681,"line":682},[679,112447,6073],{"class":685},[679,112449,6994],{"class":685},[679,112451,112452],{"class":880}," EventRepository",[679,112454,2767],{"class":685},[679,112456,97778],{"class":880},[679,112458,4505],{"class":693},[679,112460,76320],{"class":685},[679,112462,1202],{"class":693},[679,112464,1083],{"class":685},[679,112466,16397],{"class":693},[679,112468,112469],{"class":681,"line":790},[679,112470,889],{"emptyLinePlaceholder":797},[679,112472,112473],{"class":681,"line":892},[679,112474,996],{"class":693},[4542,112476,112478],{"id":112477},"populating-the-database","Populating the Database",[651,112480,112481],{},"To insert some data into the tables, we create a bean of the Command Line Runner type. In this runner, we create an instance of the event repository:",[669,112483,112485],{"className":4107,"code":112484,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n private static final Logger log = LoggerFactory.getLogger(Application.class);\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n CommandLineRunner commandLineRunner(EventRepository repository) {\n return args -> {\n // persist 1 event\n if(repository.count() == 0) {\n var event = new Event(1,\n \"SpringOne at VMware Explore\",\n \"Join us at the biggest gathering of Spring enthusiasts\",\n LocalDate.of(2023,8,21),\n LocalDate.of(2023,8,24),\n LocalDate.now().minusDays(180),\n LocalDate.now().minusDays(90),\n \"Las Vegas, NV\",\n \"https://springone.io/\");\n\n repository.save(event);\n log.info(\"Event created: \" + event.getName());\n }\n };\n }\n\n}\n",[676,112486,112487,112493,112503,112507,112526,112530,112550,112558,112562,112566,112572,112585,112595,112600,112618,112638,112645,112652,112675,112695,112713,112730,112737,112744,112748,112758,112779,112783,112787,112791,112795],{"__ignoreMap":674},[679,112488,112489,112491],{"class":681,"line":682},[679,112490,4116],{"class":693},[679,112492,6068],{"class":685},[679,112494,112495,112497,112499,112501],{"class":681,"line":790},[679,112496,6073],{"class":685},[679,112498,4512],{"class":685},[679,112500,16878],{"class":880},[679,112502,884],{"class":693},[679,112504,112505],{"class":681,"line":892},[679,112506,889],{"emptyLinePlaceholder":797},[679,112508,112509,112511,112513,112515,112517,112519,112521,112523],{"class":681,"line":901},[679,112510,9232],{"class":685},[679,112512,6092],{"class":685},[679,112514,12768],{"class":685},[679,112516,111111],{"class":693},[679,112518,686],{"class":685},[679,112520,9240],{"class":693},[679,112522,9243],{"class":880},[679,112524,112525],{"class":693},"(Application.class);\n",[679,112527,112528],{"class":681,"line":909},[679,112529,889],{"emptyLinePlaceholder":797},[679,112531,112532,112534,112536,112538,112540,112542,112544,112546,112548],{"class":681,"line":918},[679,112533,6089],{"class":685},[679,112535,6092],{"class":685},[679,112537,6095],{"class":685},[679,112539,6098],{"class":880},[679,112541,745],{"class":693},[679,112543,4758],{"class":685},[679,112545,16901],{"class":693},[679,112547,6108],{"class":2099},[679,112549,4390],{"class":693},[679,112551,112552,112554,112556],{"class":681,"line":935},[679,112553,6115],{"class":693},[679,112555,6118],{"class":880},[679,112557,16914],{"class":693},[679,112559,112560],{"class":681,"line":944},[679,112561,985],{"class":693},[679,112563,112564],{"class":681,"line":959},[679,112565,889],{"emptyLinePlaceholder":797},[679,112567,112568,112570],{"class":681,"line":964},[679,112569,6872],{"class":693},[679,112571,16929],{"class":685},[679,112573,112574,112576,112578,112581,112583],{"class":681,"line":977},[679,112575,20982],{"class":693},[679,112577,23712],{"class":880},[679,112579,112580],{"class":693},"(EventRepository ",[679,112582,16596],{"class":2099},[679,112584,4390],{"class":693},[679,112586,112587,112589,112591,112593],{"class":681,"line":982},[679,112588,9444],{"class":685},[679,112590,16952],{"class":693},[679,112592,16955],{"class":685},[679,112594,884],{"class":693},[679,112596,112597],{"class":681,"line":988},[679,112598,112599],{"class":1400}," // persist 1 event\n",[679,112601,112602,112605,112608,112610,112612,112614,112616],{"class":681,"line":993},[679,112603,112604],{"class":685}," if",[679,112606,112607],{"class":693},"(repository.",[679,112609,108369],{"class":880},[679,112611,6700],{"class":693},[679,112613,2304],{"class":685},[679,112615,14987],{"class":931},[679,112617,4390],{"class":693},[679,112619,112620,112623,112626,112628,112630,112632,112634,112636],{"class":681,"line":2129},[679,112621,112622],{"class":685}," var",[679,112624,112625],{"class":693}," event ",[679,112627,686],{"class":685},[679,112629,2054],{"class":685},[679,112631,111817],{"class":880},[679,112633,745],{"class":693},[679,112635,1557],{"class":931},[679,112637,12083],{"class":693},[679,112639,112640,112643],{"class":681,"line":2140},[679,112641,112642],{"class":689}," \"SpringOne at VMware Explore\"",[679,112644,12083],{"class":693},[679,112646,112647,112650],{"class":681,"line":2145},[679,112648,112649],{"class":689}," \"Join us at the biggest gathering of Spring enthusiasts\"",[679,112651,12083],{"class":693},[679,112653,112654,112657,112659,112661,112664,112666,112668,112670,112673],{"class":681,"line":2154},[679,112655,112656],{"class":693}," LocalDate.",[679,112658,16672],{"class":880},[679,112660,745],{"class":693},[679,112662,112663],{"class":931},"2023",[679,112665,1202],{"class":693},[679,112667,73893],{"class":931},[679,112669,1202],{"class":693},[679,112671,112672],{"class":931},"21",[679,112674,66689],{"class":693},[679,112676,112677,112679,112681,112683,112685,112687,112689,112691,112693],{"class":681,"line":2159},[679,112678,112656],{"class":693},[679,112680,16672],{"class":880},[679,112682,745],{"class":693},[679,112684,112663],{"class":931},[679,112686,1202],{"class":693},[679,112688,73893],{"class":931},[679,112690,1202],{"class":693},[679,112692,56053],{"class":931},[679,112694,66689],{"class":693},[679,112696,112697,112699,112701,112703,112706,112708,112711],{"class":681,"line":2164},[679,112698,112656],{"class":693},[679,112700,90866],{"class":880},[679,112702,10541],{"class":693},[679,112704,112705],{"class":880},"minusDays",[679,112707,745],{"class":693},[679,112709,112710],{"class":931},"180",[679,112712,66689],{"class":693},[679,112714,112715,112717,112719,112721,112723,112725,112728],{"class":681,"line":3134},[679,112716,112656],{"class":693},[679,112718,90866],{"class":880},[679,112720,10541],{"class":693},[679,112722,112705],{"class":880},[679,112724,745],{"class":693},[679,112726,112727],{"class":931},"90",[679,112729,66689],{"class":693},[679,112731,112732,112735],{"class":681,"line":3139},[679,112733,112734],{"class":689}," \"Las Vegas, NV\"",[679,112736,12083],{"class":693},[679,112738,112739,112742],{"class":681,"line":3144},[679,112740,112741],{"class":689}," \"https://springone.io/\"",[679,112743,1208],{"class":693},[679,112745,112746],{"class":681,"line":3149},[679,112747,889],{"emptyLinePlaceholder":797},[679,112749,112750,112753,112755],{"class":681,"line":3169},[679,112751,112752],{"class":693}," repository.",[679,112754,7629],{"class":880},[679,112756,112757],{"class":693},"(event);\n",[679,112759,112760,112763,112765,112767,112770,112772,112775,112777],{"class":681,"line":3185},[679,112761,112762],{"class":693}," log.",[679,112764,9415],{"class":880},[679,112766,745],{"class":693},[679,112768,112769],{"class":689},"\"Event created: \"",[679,112771,3059],{"class":685},[679,112773,112774],{"class":693}," event.",[679,112776,10577],{"class":880},[679,112778,9431],{"class":693},[679,112780,112781],{"class":681,"line":3194},[679,112782,25517],{"class":693},[679,112784,112785],{"class":681,"line":3199},[679,112786,17018],{"class":693},[679,112788,112789],{"class":681,"line":3212},[679,112790,985],{"class":693},[679,112792,112793],{"class":681,"line":3217},[679,112794,889],{"emptyLinePlaceholder":797},[679,112796,112797],{"class":681,"line":3222},[679,112798,996],{"class":693},[651,112800,112801],{},"If the repository count is zero (meaning the database is freshly created and empty), we create and save an event to it. Then, we log a message with the event's name.",[651,112803,112804],{},"And that's all! Despite not having set up a database connection, our Docker Compose file will manage to successfully start the database.",[4542,112806,112808],{"id":112807},"accessing-postgresql-database","Accessing PostgreSQL Database",[651,112810,112811,112812,664],{},"We now want to verify that our function ran as expected by checking the database for the new entry. For that, we use ",[676,112813,112814],{},"docker exec -it",[669,112816,112818],{"className":5851,"code":112817,"language":5853,"meta":674,"style":674},"docker exec -it my_postgres_container psql -U my_user -d my_database\n",[676,112819,112820],{"__ignoreMap":674},[679,112821,112822,112825,112828,112831,112834,112837,112840,112843,112846],{"class":681,"line":682},[679,112823,112824],{"class":880},"docker",[679,112826,112827],{"class":689}," exec",[679,112829,112830],{"class":931}," -it",[679,112832,112833],{"class":689}," my_postgres_container",[679,112835,112836],{"class":689}," psql",[679,112838,112839],{"class":931}," -U",[679,112841,112842],{"class":689}," my_user",[679,112844,112845],{"class":931}," -d",[679,112847,112848],{"class":689}," my_database\n",[651,112850,112851,112852,112855,112856,112859,112860,112862],{},"The command above will help you connect to a running PostgreSQL container from your terminal, where you can explore the data. Run ",[676,112853,112854],{},"dt"," to list tables, ",[676,112857,112858],{},"\\c"," to connect to a database or ",[676,112861,65123],{}," statements to fetch data.",[651,112864,112865],{},[660,112866],{"alt":112867,"src":112868},"PSQL","/images/blog/2023/07/17/psql.png",[651,112870,112871],{},"A better way to view and manage your Postgres data is through a tool like pgAdmin.",[4542,112873,112875],{"id":112874},"exploring-data-with-pg-admin","Exploring Data with PG Admin",[651,112877,112878,112879,112883],{},"You'll find more on PG Admin at ",[812,112880,112881],{"href":112881,"rel":112882},"https://www.pgadmin.org/",[816],", offering a feature-rich administration and development tool for PostgreSQL. It allows you to comfortably view tables, index, and data within your database.",[651,112885,112886,112887,2391],{},"To incorporate PG Admin into your Docker Compose module, add the following code to your ",[676,112888,107155],{},[669,112890,112892],{"className":67476,"code":112891,"language":56308,"meta":674,"style":674},"pgadmin:\n image: dpage/pgadmin4:latest\n environment:\n PGADMIN_DEFAULT_EMAIL: admin@localhost.com\n PGADMIN_DEFAULT_PASSWORD: admin\n PGADMIN_LISTEN_PORT: 5050\n ports:\n - '5050:5050'\n",[676,112893,112894,112901,112910,112916,112926,112936,112946,112952],{"__ignoreMap":674},[679,112895,112896,112899],{"class":681,"line":682},[679,112897,112898],{"class":4508},"pgadmin",[679,112900,3327],{"class":693},[679,112902,112903,112905,112907],{"class":681,"line":790},[679,112904,107194],{"class":4508},[679,112906,4282],{"class":693},[679,112908,112909],{"class":689},"dpage/pgadmin4:latest\n",[679,112911,112912,112914],{"class":681,"line":892},[679,112913,107229],{"class":4508},[679,112915,3327],{"class":693},[679,112917,112918,112921,112923],{"class":681,"line":901},[679,112919,112920],{"class":4508}," PGADMIN_DEFAULT_EMAIL",[679,112922,4282],{"class":693},[679,112924,112925],{"class":689},"admin@localhost.com\n",[679,112927,112928,112931,112933],{"class":681,"line":909},[679,112929,112930],{"class":4508}," PGADMIN_DEFAULT_PASSWORD",[679,112932,4282],{"class":693},[679,112934,112935],{"class":689},"admin\n",[679,112937,112938,112941,112943],{"class":681,"line":918},[679,112939,112940],{"class":4508}," PGADMIN_LISTEN_PORT",[679,112942,4282],{"class":693},[679,112944,112945],{"class":931},"5050\n",[679,112947,112948,112950],{"class":681,"line":935},[679,112949,107214],{"class":4508},[679,112951,3327],{"class":693},[679,112953,112954,112956],{"class":681,"line":944},[679,112955,107221],{"class":693},[679,112957,112958],{"class":689},"'5050:5050'\n",[651,112960,112961,112962,112965,112966,112969,112970,112973],{},"Now, upon running the application and visiting ",[676,112963,112964],{},"localhost:5050",", you'll be welcomed with the login screen for PG Admin. On successful login, hit ",[676,112967,112968],{},"Add New Server",", and use your ",[676,112971,112972],{},"docker inspect"," command to fetch the container's IP address, or set the container's name as the hostname/post.",[669,112975,112977],{"className":5851,"code":112976,"language":5853,"meta":674,"style":674},"docker ps\ndocker inspect 7738686e136d | grep IPAddress\n",[676,112978,112979,112986],{"__ignoreMap":674},[679,112980,112981,112983],{"class":681,"line":682},[679,112982,112824],{"class":880},[679,112984,112985],{"class":689}," ps\n",[679,112987,112988,112990,112993,112996,112998,113001],{"class":681,"line":790},[679,112989,112824],{"class":880},[679,112991,112992],{"class":689}," inspect",[679,112994,112995],{"class":689}," 7738686e136d",[679,112997,42344],{"class":685},[679,112999,113000],{"class":880}," grep",[679,113002,113003],{"class":689}," IPAddress\n",[651,113005,113006,113007,113010,113011,113013],{},"However, there's a minor flaw. Every time the application is restarted, you'll have to log back into PG Admin. To avoid this, explore the Github repository published by my coworker, Adib Saikali. The repository includes a ",[676,113008,113009],{},"servers.json"," file with login details for PG Admin, and a ",[676,113012,107155],{}," with volumes for PostgreSQL and PG Admin.",[669,113015,113017],{"className":28439,"code":113016,"language":28441,"meta":674,"style":674},"{\n \"Servers\": {\n \"1\": {\n \"Name\": \"Docker Compose\",\n \"Group\": \"Servers\",\n \"Port\": 5432,\n \"Username\": \"dvega\",\n \"Host\": \"sessionz_postgres\",\n \"SSLMode\": \"prefer\",\n \"MaintenanceDB\": \"sessionz\",\n \"PassFile\": \"/tmp/pgpassfile\"\n }\n }\n}\n",[676,113018,113019,113023,113030,113037,113049,113061,113073,113084,113096,113108,113120,113130,113134,113138],{"__ignoreMap":674},[679,113020,113021],{"class":681,"line":682},[679,113022,28448],{"class":693},[679,113024,113025,113028],{"class":681,"line":790},[679,113026,113027],{"class":931}," \"Servers\"",[679,113029,28468],{"class":693},[679,113031,113032,113035],{"class":681,"line":892},[679,113033,113034],{"class":931}," \"1\"",[679,113036,28468],{"class":693},[679,113038,113039,113042,113044,113047],{"class":681,"line":901},[679,113040,113041],{"class":931}," \"Name\"",[679,113043,4282],{"class":693},[679,113045,113046],{"class":689},"\"Docker Compose\"",[679,113048,12083],{"class":693},[679,113050,113051,113054,113056,113059],{"class":681,"line":909},[679,113052,113053],{"class":931}," \"Group\"",[679,113055,4282],{"class":693},[679,113057,113058],{"class":689},"\"Servers\"",[679,113060,12083],{"class":693},[679,113062,113063,113066,113068,113071],{"class":681,"line":918},[679,113064,113065],{"class":931}," \"Port\"",[679,113067,4282],{"class":693},[679,113069,113070],{"class":931},"5432",[679,113072,12083],{"class":693},[679,113074,113075,113078,113080,113082],{"class":681,"line":935},[679,113076,113077],{"class":931}," \"Username\"",[679,113079,4282],{"class":693},[679,113081,89723],{"class":689},[679,113083,12083],{"class":693},[679,113085,113086,113089,113091,113094],{"class":681,"line":944},[679,113087,113088],{"class":931}," \"Host\"",[679,113090,4282],{"class":693},[679,113092,113093],{"class":689},"\"sessionz_postgres\"",[679,113095,12083],{"class":693},[679,113097,113098,113101,113103,113106],{"class":681,"line":959},[679,113099,113100],{"class":931}," \"SSLMode\"",[679,113102,4282],{"class":693},[679,113104,113105],{"class":689},"\"prefer\"",[679,113107,12083],{"class":693},[679,113109,113110,113113,113115,113118],{"class":681,"line":964},[679,113111,113112],{"class":931}," \"MaintenanceDB\"",[679,113114,4282],{"class":693},[679,113116,113117],{"class":689},"\"sessionz\"",[679,113119,12083],{"class":693},[679,113121,113122,113125,113127],{"class":681,"line":977},[679,113123,113124],{"class":931}," \"PassFile\"",[679,113126,4282],{"class":693},[679,113128,113129],{"class":689},"\"/tmp/pgpassfile\"\n",[679,113131,113132],{"class":681,"line":982},[679,113133,985],{"class":693},[679,113135,113136],{"class":681,"line":988},[679,113137,21405],{"class":693},[679,113139,113140],{"class":681,"line":993},[679,113141,996],{"class":693},[651,113143,113144],{},"Now each time you restart the application you will be automatically logged into pgAdmin.",[651,113146,113147],{},[660,113148],{"alt":113149,"src":113150},"pgAdmin","/images/blog/2023/07/17/pgadmin.png",[4542,113152,9042],{"id":9041},[651,113154,113155],{},"We've covered the Docker Compose module in Spring Boot 3.1 and learned how to connect to a Postgres database from our application. We also explored how to use psql in the shell and pgAdmin to manage our database smoothly. This Docker Compose Module will streamline your development process, making it much simpler to view and manage your data.",[651,113157,113158],{},"Thanks Adib, for the invaluable Github reposit",[786,113160,113161],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":113163},[113164,113165,113166,113167,113168,113169,113170],{"id":112250,"depth":790,"text":112251},{"id":112272,"depth":790,"text":112273},{"id":112293,"depth":790,"text":112294},{"id":112477,"depth":790,"text":112478},{"id":112807,"depth":790,"text":112808},{"id":112874,"depth":790,"text":112875},{"id":9041,"depth":790,"text":9042},"In this article you will learn about the new Docker Compose module in Spring Boot 3.1 and how to connect to a Postgres database from your application. You will also learn how to use psql in the shell and pgAdmin to manage your database.",{"slug":113173,"date":113174,"published":797,"author":798,"tags":113175,"cover":113176,"video":113177,"github":113178,"keywords":113179},"pgadmin-docker-compose","2023-07-17T17:00:00.000Z",[7077],"./pgAdminDockerCompose.png","https://www.youtube.com/embed/XDlgWyVfSMA","https://github.com/danvega/pgadmin","Spring Boot, Spring Framework, Spring Boot 3, Docker, Docker Compose, PostgreSQL, pgAdmin",{"title":129,"description":113171},"blog/2023/07/17/pgadmin-docker-compose","Y-pBKv4il4qq7I8Svnc0kupG9tgGV1WQzaVZDpoKGdo",{"id":113184,"title":126,"body":113185,"description":114814,"extension":793,"meta":114815,"navigation":797,"path":127,"seo":114823,"stem":114824,"__hash__":114825},"content/blog/2023/09/08/rest-client-first-look.md",{"type":648,"value":113186,"toc":114807},[113187,113190,113194,113203,113213,113227,113234,113237,113243,113247,113250,113255,113258,113262,113270,113273,113297,113303,113633,113636,113728,113734,113808,113811,113825,113828,114268,114464,114467,114471,114474,114477,114652,114796,114798,114801,114804],[651,113188,113189],{},"Greetings, folks! It's Dan Vega, Spring developer advocate at VMware. Today, I am thrilled to dive into Spring Boot 3.2 and its new REST client. But before we do, let's take a moment to examine our journey to this moment.",[4542,113191,113193],{"id":113192},"from-start-to-rest-client","From Start to REST Client",[651,113195,113196,113197,113202],{},"The term \"client\" in the context of REST refers to a method for invoking another REST API within our organization, or perhaps a public API. In the Spring framework world, we've had numerous implementations of ",[812,113198,113201],{"href":113199,"rel":113200},"https://docs.spring.io/spring-framework/reference/integration/rest-clients.html",[816],"REST clients",", and today we'll touch on a few of those.",[651,113204,113205,113206,28997,113210,113212],{},"If you're developing a Spring MVC application, a blocking application, you'd traditionally use something like the ",[812,113207,23551],{"href":113208,"rel":113209},"https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-resttemplate",[816],[676,113211,23551],{}," is fantastic — it offers numerous overloaded methods and the ability for us to construct a service that calls another service. However, the myriad of overloaded methods can lead to confusion about which method is appropriate for the scenario you're addressing.",[651,113214,113215,113216,113220,113221,113223,113224,113226],{},"Next up, we had the ",[812,113217,109920],{"href":113218,"rel":113219},"https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html",[816],". In the reactive space, with Spring WebFlux, we needed a fresh client. The ",[676,113222,109920],{}," took a plethora of lessons from the ",[676,113225,23551],{}," and enhanced upon them, with the beautiful bonus of a fluent API, allowing a graceful and straightforward declaration of service-to-service communication.",[651,113228,113229,113230,113233],{},"Fast forward to the introduction of ",[676,113231,113232],{},"RestClient",". The RestClient took a lot from the WebClient and applied it to Spring MVC. The key advantage here is that you can use it in your MVC applications without having to bring in an external dependency.",[651,113235,113236],{},"What's really fascinating to note is the support for HTTP interfaces that came in Spring Boot 3 and Spring Framework 6. This support extends to the RestClient as well. This means you no longer need to include the Spring WebFlux dependency to utilize HTTP interfaces.",[651,113238,113239,113240,1223],{},"Let's create a small CRUD service application that uses the Rest client to make a call to a public API. Excited? Let's create a new application on ",[812,113241,77478],{"href":30440,"rel":113242},[816],[4542,113244,113246],{"id":113245},"building-an-application","Building an Application",[651,113248,113249],{},"Start off by selecting a project type like Maven and choosing Java as your language. I'm using Spring's Boot 3.2.0, Milestone 2. If you're tuning in from the future, please go ahead with the most current milestone release of 3.2, or perhaps 3.2+ if it's publicly available.",[651,113251,113252],{},[660,113253],{"alt":7117,"src":113254},"/images/blog/2023/09/08/start-spring-io.png",[651,113256,113257],{},"Once you've entered your group and project names, choose Spring Web for your dependencies. Our application will be auto-configured, and we'll have access to the Rest client in all of our classes. Let's generate our application. Upon doing this, a zip file will be downloaded. Open it up in your IDE or text editor of choice - I'll be using IntelliJ Ultimate Edition.",[4542,113259,113261],{"id":113260},"coding-the-application","Coding the Application",[651,113263,113264,113265,113269],{},"The application we're creating is aimed at calling a public service known as ",[812,113266,113268],{"href":33252,"rel":113267},[816],"JSONPlaceholder"," – it's a super useful, free API we can use for testing. Our system will be modeled around the post schema of JSONPlaceholder.",[651,113271,113272],{},"To get started, you need to make new packages and create a Java record called Post to represent a post entity.",[669,113274,113276],{"className":4107,"code":113275,"language":4109,"meta":674,"style":674},"public record Post(Integer id, Integer userId, String title, String body){\n\n}\n",[676,113277,113278,113289,113293],{"__ignoreMap":674},[679,113279,113280,113282,113284,113286],{"class":681,"line":682},[679,113281,6073],{"class":685},[679,113283,86928],{"class":685},[679,113285,4658],{"class":880},[679,113287,113288],{"class":693},"(Integer id, Integer userId, String title, String body){\n",[679,113290,113291],{"class":681,"line":790},[679,113292,889],{"emptyLinePlaceholder":797},[679,113294,113295],{"class":681,"line":892},[679,113296,996],{"class":693},[651,113298,113299,113300,113302],{},"Now let's create a PostController class marked with the ",[676,113301,12329],{}," annotation and set up a PostService through constructor injection. Put simply, controllers receive requests, delegate to another class for business logic, and return responses.",[669,113304,113306],{"className":4107,"code":113305,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private final JsonPlaceholderService postService;\n\n public PostController(JsonPlaceholderService postService) {\n this.postService = postService;\n }\n\n @GetMapping(\"\")\n List\u003CPost> findAll() {\n return postService.findAll();\n }\n\n @GetMapping(\"/{id}\")\n Post findById(@PathVariable Integer id) {\n return postService.findById(id);\n }\n\n @PostMapping\n @ResponseStatus(HttpStatus.CREATED)\n Post create(@RequestBody Post post) {\n return postService.create(post);\n }\n\n @PutMapping(\"/{id}\")\n Post update(@PathVariable Integer id, @RequestBody Post post) {\n return postService.update(id, post);\n }\n\n @DeleteMapping(\"/{id}\")\n @ResponseStatus(HttpStatus.NO_CONTENT)\n void delete(@PathVariable Integer id) {\n postService.delete(id);\n }\n\n}\n",[676,113307,113308,113314,113326,113336,113340,113349,113353,113365,113375,113379,113383,113395,113407,113418,113422,113426,113438,113455,113465,113469,113473,113479,113487,113503,113513,113517,113521,113533,113557,113568,113572,113576,113588,113596,113612,113621,113625,113629],{"__ignoreMap":674},[679,113309,113310,113312],{"class":681,"line":682},[679,113311,4116],{"class":693},[679,113313,9212],{"class":685},[679,113315,113316,113318,113320,113322,113324],{"class":681,"line":790},[679,113317,4116],{"class":693},[679,113319,9275],{"class":685},[679,113321,745],{"class":693},[679,113323,23141],{"class":689},[679,113325,1339],{"class":693},[679,113327,113328,113330,113332,113334],{"class":681,"line":892},[679,113329,6073],{"class":685},[679,113331,4512],{"class":685},[679,113333,23152],{"class":880},[679,113335,884],{"class":693},[679,113337,113338],{"class":681,"line":901},[679,113339,889],{"emptyLinePlaceholder":797},[679,113341,113342,113344,113346],{"class":681,"line":909},[679,113343,9232],{"class":685},[679,113345,12768],{"class":685},[679,113347,113348],{"class":693}," JsonPlaceholderService postService;\n",[679,113350,113351],{"class":681,"line":918},[679,113352,889],{"emptyLinePlaceholder":797},[679,113354,113355,113357,113359,113361,113363],{"class":681,"line":935},[679,113356,6089],{"class":685},[679,113358,23152],{"class":880},[679,113360,111158],{"class":693},[679,113362,9999],{"class":2099},[679,113364,4390],{"class":693},[679,113366,113367,113369,113371,113373],{"class":681,"line":944},[679,113368,7862],{"class":931},[679,113370,10008],{"class":693},[679,113372,686],{"class":685},[679,113374,10013],{"class":693},[679,113376,113377],{"class":681,"line":959},[679,113378,985],{"class":693},[679,113380,113381],{"class":681,"line":964},[679,113382,889],{"emptyLinePlaceholder":797},[679,113384,113385,113387,113389,113391,113393],{"class":681,"line":977},[679,113386,6872],{"class":693},[679,113388,20852],{"class":685},[679,113390,745],{"class":693},[679,113392,3579],{"class":689},[679,113394,1339],{"class":693},[679,113396,113397,113399,113401,113403,113405],{"class":681,"line":982},[679,113398,84917],{"class":693},[679,113400,5105],{"class":685},[679,113402,20881],{"class":693},[679,113404,34142],{"class":880},[679,113406,2667],{"class":693},[679,113408,113409,113411,113414,113416],{"class":681,"line":988},[679,113410,9444],{"class":685},[679,113412,113413],{"class":693}," postService.",[679,113415,34142],{"class":880},[679,113417,9317],{"class":693},[679,113419,113420],{"class":681,"line":993},[679,113421,985],{"class":693},[679,113423,113424],{"class":681,"line":2129},[679,113425,889],{"emptyLinePlaceholder":797},[679,113427,113428,113430,113432,113434,113436],{"class":681,"line":2140},[679,113429,6872],{"class":693},[679,113431,20852],{"class":685},[679,113433,745],{"class":693},[679,113435,92261],{"class":689},[679,113437,1339],{"class":693},[679,113439,113440,113443,113445,113447,113449,113451,113453],{"class":681,"line":2145},[679,113441,113442],{"class":693}," Post ",[679,113444,87658],{"class":880},[679,113446,73246],{"class":693},[679,113448,92277],{"class":685},[679,113450,88766],{"class":693},[679,113452,11341],{"class":2099},[679,113454,4390],{"class":693},[679,113456,113457,113459,113461,113463],{"class":681,"line":2154},[679,113458,9444],{"class":685},[679,113460,113413],{"class":693},[679,113462,87658],{"class":880},[679,113464,88781],{"class":693},[679,113466,113467],{"class":681,"line":2159},[679,113468,985],{"class":693},[679,113470,113471],{"class":681,"line":2164},[679,113472,889],{"emptyLinePlaceholder":797},[679,113474,113475,113477],{"class":681,"line":3134},[679,113476,6872],{"class":693},[679,113478,96257],{"class":685},[679,113480,113481,113483,113485],{"class":681,"line":3139},[679,113482,6872],{"class":693},[679,113484,96264],{"class":685},[679,113486,96267],{"class":693},[679,113488,113489,113491,113493,113495,113497,113499,113501],{"class":681,"line":3144},[679,113490,113442],{"class":693},[679,113492,5697],{"class":880},[679,113494,73246],{"class":693},[679,113496,96282],{"class":685},[679,113498,93429],{"class":693},[679,113500,5100],{"class":2099},[679,113502,4390],{"class":693},[679,113504,113505,113507,113509,113511],{"class":681,"line":3149},[679,113506,9444],{"class":685},[679,113508,113413],{"class":693},[679,113510,5697],{"class":880},[679,113512,110679],{"class":693},[679,113514,113515],{"class":681,"line":3169},[679,113516,985],{"class":693},[679,113518,113519],{"class":681,"line":3185},[679,113520,889],{"emptyLinePlaceholder":797},[679,113522,113523,113525,113527,113529,113531],{"class":681,"line":3194},[679,113524,6872],{"class":693},[679,113526,109638],{"class":685},[679,113528,745],{"class":693},[679,113530,92261],{"class":689},[679,113532,1339],{"class":693},[679,113534,113535,113537,113539,113541,113543,113545,113547,113549,113551,113553,113555],{"class":681,"line":3199},[679,113536,113442],{"class":693},[679,113538,82237],{"class":880},[679,113540,73246],{"class":693},[679,113542,92277],{"class":685},[679,113544,88766],{"class":693},[679,113546,11341],{"class":2099},[679,113548,85575],{"class":693},[679,113550,96282],{"class":685},[679,113552,93429],{"class":693},[679,113554,5100],{"class":2099},[679,113556,4390],{"class":693},[679,113558,113559,113561,113563,113565],{"class":681,"line":3212},[679,113560,9444],{"class":685},[679,113562,113413],{"class":693},[679,113564,82237],{"class":880},[679,113566,113567],{"class":693},"(id, post);\n",[679,113569,113570],{"class":681,"line":3217},[679,113571,985],{"class":693},[679,113573,113574],{"class":681,"line":3222},[679,113575,889],{"emptyLinePlaceholder":797},[679,113577,113578,113580,113582,113584,113586],{"class":681,"line":3227},[679,113579,6872],{"class":693},[679,113581,92256],{"class":685},[679,113583,745],{"class":693},[679,113585,92261],{"class":689},[679,113587,1339],{"class":693},[679,113589,113590,113592,113594],{"class":681,"line":3232},[679,113591,6872],{"class":693},[679,113593,96264],{"class":685},[679,113595,109653],{"class":693},[679,113597,113598,113600,113602,113604,113606,113608,113610],{"class":681,"line":3499},[679,113599,3314],{"class":685},[679,113601,92272],{"class":880},[679,113603,73246],{"class":693},[679,113605,92277],{"class":685},[679,113607,88766],{"class":693},[679,113609,11341],{"class":2099},[679,113611,4390],{"class":693},[679,113613,113614,113617,113619],{"class":681,"line":3509},[679,113615,113616],{"class":693}," postService.",[679,113618,7632],{"class":880},[679,113620,88781],{"class":693},[679,113622,113623],{"class":681,"line":3516},[679,113624,985],{"class":693},[679,113626,113627],{"class":681,"line":3531},[679,113628,889],{"emptyLinePlaceholder":797},[679,113630,113631],{"class":681,"line":3536},[679,113632,996],{"class":693},[651,113634,113635],{},"In the PostService class, we're going to create an instance for our client. This is where the action really starts. We use the builder method to pass in a base URL (the endpoint of the service you're calling) and build the RestClient.",[669,113637,113639],{"className":4107,"code":113638,"language":4109,"meta":674,"style":674},"@Service\npublic class PostService {\n\n private final RestClient restClient;\n\n public PostService() {\n restClient = RestClient.builder()\n .baseUrl(\"https://jsonplaceholder.typicode.com\")\n .build();\n }\n\n}\n",[676,113640,113641,113647,113657,113661,113670,113674,113682,113696,113708,113716,113720,113724],{"__ignoreMap":674},[679,113642,113643,113645],{"class":681,"line":682},[679,113644,4116],{"class":693},[679,113646,9486],{"class":685},[679,113648,113649,113651,113653,113655],{"class":681,"line":790},[679,113650,6073],{"class":685},[679,113652,4512],{"class":685},[679,113654,23189],{"class":880},[679,113656,884],{"class":693},[679,113658,113659],{"class":681,"line":892},[679,113660,889],{"emptyLinePlaceholder":797},[679,113662,113663,113665,113667],{"class":681,"line":901},[679,113664,9232],{"class":685},[679,113666,12768],{"class":685},[679,113668,113669],{"class":693}," RestClient restClient;\n",[679,113671,113672],{"class":681,"line":909},[679,113673,889],{"emptyLinePlaceholder":797},[679,113675,113676,113678,113680],{"class":681,"line":918},[679,113677,6089],{"class":685},[679,113679,23189],{"class":880},[679,113681,2667],{"class":693},[679,113683,113684,113687,113689,113692,113694],{"class":681,"line":935},[679,113685,113686],{"class":693}," restClient ",[679,113688,686],{"class":685},[679,113690,113691],{"class":693}," RestClient.",[679,113693,90934],{"class":880},[679,113695,17545],{"class":693},[679,113697,113698,113700,113702,113704,113706],{"class":681,"line":944},[679,113699,73482],{"class":693},[679,113701,100528],{"class":880},[679,113703,745],{"class":693},[679,113705,111012],{"class":689},[679,113707,1339],{"class":693},[679,113709,113710,113712,113714],{"class":681,"line":959},[679,113711,73482],{"class":693},[679,113713,23612],{"class":880},[679,113715,9317],{"class":693},[679,113717,113718],{"class":681,"line":964},[679,113719,985],{"class":693},[679,113721,113722],{"class":681,"line":977},[679,113723,889],{"emptyLinePlaceholder":797},[679,113725,113726],{"class":681,"line":982},[679,113727,996],{"class":693},[651,113729,113730,113731,113733],{},"Then we need to create methods to interact with our service, such as a ",[676,113732,34142],{}," method to get all of the posts in the system.",[669,113735,113737],{"className":4107,"code":113736,"language":4109,"meta":674,"style":674},"public List\u003CPost> findAll() {\n return restClient.get()\n .uri(\"/posts\")\n .retrieve()\n .body(new ParameterizedTypeReference\u003CList\u003CPost>>() {});\n}\n",[676,113738,113739,113755,113766,113778,113786,113804],{"__ignoreMap":674},[679,113740,113741,113743,113745,113747,113749,113751,113753],{"class":681,"line":682},[679,113742,6073],{"class":685},[679,113744,96047],{"class":693},[679,113746,4505],{"class":685},[679,113748,5105],{"class":693},[679,113750,5860],{"class":685},[679,113752,96057],{"class":880},[679,113754,2667],{"class":693},[679,113756,113757,113759,113762,113764],{"class":681,"line":790},[679,113758,21478],{"class":685},[679,113760,113761],{"class":693}," restClient.",[679,113763,7626],{"class":880},[679,113765,17545],{"class":693},[679,113767,113768,113770,113772,113774,113776],{"class":681,"line":892},[679,113769,40148],{"class":693},[679,113771,4836],{"class":880},[679,113773,745],{"class":693},[679,113775,110889],{"class":689},[679,113777,1339],{"class":693},[679,113779,113780,113782,113784],{"class":681,"line":901},[679,113781,40148],{"class":693},[679,113783,105458],{"class":880},[679,113785,17545],{"class":693},[679,113787,113788,113790,113792,113794,113796,113799,113801],{"class":681,"line":909},[679,113789,40148],{"class":693},[679,113791,3006],{"class":880},[679,113793,745],{"class":693},[679,113795,8930],{"class":685},[679,113797,113798],{"class":693}," ParameterizedTypeReference\u003CList\u003C",[679,113800,5105],{"class":685},[679,113802,113803],{"class":693},">>() {});\n",[679,113805,113806],{"class":681,"line":918},[679,113807,996],{"class":693},[651,113809,113810],{},"Just run your Spring Boot application, and if it starts up fine, you can test it out using an HTTP client to list the posts.",[669,113812,113815],{"className":113813,"code":113814,"language":89511,"meta":674,"style":674},"language-http shiki shiki-themes github-light github-dark github-light","GET http://localhost:8080/api/posts\n",[676,113816,113817],{"__ignoreMap":674},[679,113818,113819,113822],{"class":681,"line":682},[679,113820,113821],{"class":685},"GET",[679,113823,113824],{"class":693}," http://localhost:8080/api/posts\n",[651,113826,113827],{},"To further demonstrate the elegance of the RestClient, let's create a few more methods - for creating, updating, and deleting posts. Once you've written the methods, it's straightforward to test them all in an HTTP client.",[669,113829,113831],{"className":4107,"code":113830,"language":4109,"meta":674,"style":674},"@Service\npublic class PostService {\n\n private final RestClient restClient;\n\n public PostService() {\n restClient = RestClient.builder()\n .baseUrl(\"https://jsonplaceholder.typicode.com\")\n .build();\n }\n\n public List\u003CPost> findAll() {\n return restClient.get()\n .uri(\"/posts\")\n .retrieve()\n .body(new ParameterizedTypeReference\u003CList\u003CPost>>() {});\n }\n\n public Post findById(Integer id) {\n return restClient.get()\n .uri(\"/posts/{id}\", id)\n .retrieve()\n .body(Post.class);\n }\n\n Post create(Post post) {\n return restClient.post()\n .uri(\"/posts\")\n .contentType(MediaType.APPLICATION_JSON)\n .body(post)\n .retrieve()\n .body(Post.class);\n }\n\n Post update(Integer id, Post post) {\n return restClient.put()\n .uri(\"/posts/{id}\", id)\n .contentType(MediaType.APPLICATION_JSON)\n .body(post)\n .retrieve()\n .body(Post.class);\n }\n\n void delete(Integer id) {\n restClient.delete()\n .uri(\"/posts/{id}\", id)\n .retrieve()\n .toBodilessEntity();\n }\n\n\n}\n",[676,113832,113833,113839,113849,113853,113861,113865,113873,113885,113897,113905,113909,113913,113927,113937,113949,113957,113973,113977,113981,113995,114005,114019,114027,114036,114040,114044,114057,114067,114079,114089,114098,114106,114114,114118,114122,114139,114150,114162,114170,114178,114186,114194,114198,114202,114214,114223,114235,114243,114252,114256,114260,114264],{"__ignoreMap":674},[679,113834,113835,113837],{"class":681,"line":682},[679,113836,4116],{"class":693},[679,113838,9486],{"class":685},[679,113840,113841,113843,113845,113847],{"class":681,"line":790},[679,113842,6073],{"class":685},[679,113844,4512],{"class":685},[679,113846,23189],{"class":880},[679,113848,884],{"class":693},[679,113850,113851],{"class":681,"line":892},[679,113852,889],{"emptyLinePlaceholder":797},[679,113854,113855,113857,113859],{"class":681,"line":901},[679,113856,9232],{"class":685},[679,113858,12768],{"class":685},[679,113860,113669],{"class":693},[679,113862,113863],{"class":681,"line":909},[679,113864,889],{"emptyLinePlaceholder":797},[679,113866,113867,113869,113871],{"class":681,"line":918},[679,113868,6089],{"class":685},[679,113870,23189],{"class":880},[679,113872,2667],{"class":693},[679,113874,113875,113877,113879,113881,113883],{"class":681,"line":935},[679,113876,113686],{"class":693},[679,113878,686],{"class":685},[679,113880,113691],{"class":693},[679,113882,90934],{"class":880},[679,113884,17545],{"class":693},[679,113886,113887,113889,113891,113893,113895],{"class":681,"line":944},[679,113888,73482],{"class":693},[679,113890,100528],{"class":880},[679,113892,745],{"class":693},[679,113894,111012],{"class":689},[679,113896,1339],{"class":693},[679,113898,113899,113901,113903],{"class":681,"line":959},[679,113900,73482],{"class":693},[679,113902,23612],{"class":880},[679,113904,9317],{"class":693},[679,113906,113907],{"class":681,"line":964},[679,113908,985],{"class":693},[679,113910,113911],{"class":681,"line":977},[679,113912,889],{"emptyLinePlaceholder":797},[679,113914,113915,113917,113919,113921,113923,113925],{"class":681,"line":982},[679,113916,6089],{"class":685},[679,113918,87217],{"class":693},[679,113920,5105],{"class":685},[679,113922,20881],{"class":693},[679,113924,34142],{"class":880},[679,113926,2667],{"class":693},[679,113928,113929,113931,113933,113935],{"class":681,"line":988},[679,113930,9444],{"class":685},[679,113932,113761],{"class":693},[679,113934,7626],{"class":880},[679,113936,17545],{"class":693},[679,113938,113939,113941,113943,113945,113947],{"class":681,"line":993},[679,113940,73482],{"class":693},[679,113942,4836],{"class":880},[679,113944,745],{"class":693},[679,113946,110889],{"class":689},[679,113948,1339],{"class":693},[679,113950,113951,113953,113955],{"class":681,"line":2129},[679,113952,73482],{"class":693},[679,113954,105458],{"class":880},[679,113956,17545],{"class":693},[679,113958,113959,113961,113963,113965,113967,113969,113971],{"class":681,"line":2140},[679,113960,73482],{"class":693},[679,113962,3006],{"class":880},[679,113964,745],{"class":693},[679,113966,8930],{"class":685},[679,113968,113798],{"class":693},[679,113970,5105],{"class":685},[679,113972,113803],{"class":693},[679,113974,113975],{"class":681,"line":2145},[679,113976,985],{"class":693},[679,113978,113979],{"class":681,"line":2154},[679,113980,889],{"emptyLinePlaceholder":797},[679,113982,113983,113985,113987,113989,113991,113993],{"class":681,"line":2159},[679,113984,6089],{"class":685},[679,113986,93429],{"class":693},[679,113988,87658],{"class":880},[679,113990,87309],{"class":693},[679,113992,11341],{"class":2099},[679,113994,4390],{"class":693},[679,113996,113997,113999,114001,114003],{"class":681,"line":2164},[679,113998,9444],{"class":685},[679,114000,113761],{"class":693},[679,114002,7626],{"class":880},[679,114004,17545],{"class":693},[679,114006,114007,114009,114011,114013,114016],{"class":681,"line":3134},[679,114008,73482],{"class":693},[679,114010,4836],{"class":880},[679,114012,745],{"class":693},[679,114014,114015],{"class":689},"\"/posts/{id}\"",[679,114017,114018],{"class":693},", id)\n",[679,114020,114021,114023,114025],{"class":681,"line":3139},[679,114022,73482],{"class":693},[679,114024,105458],{"class":880},[679,114026,17545],{"class":693},[679,114028,114029,114031,114033],{"class":681,"line":3144},[679,114030,73482],{"class":693},[679,114032,3006],{"class":880},[679,114034,114035],{"class":693},"(Post.class);\n",[679,114037,114038],{"class":681,"line":3149},[679,114039,985],{"class":693},[679,114041,114042],{"class":681,"line":3169},[679,114043,889],{"emptyLinePlaceholder":797},[679,114045,114046,114048,114050,114053,114055],{"class":681,"line":3185},[679,114047,113442],{"class":693},[679,114049,5697],{"class":880},[679,114051,114052],{"class":693},"(Post ",[679,114054,5100],{"class":2099},[679,114056,4390],{"class":693},[679,114058,114059,114061,114063,114065],{"class":681,"line":3194},[679,114060,9444],{"class":685},[679,114062,113761],{"class":693},[679,114064,5100],{"class":880},[679,114066,17545],{"class":693},[679,114068,114069,114071,114073,114075,114077],{"class":681,"line":3199},[679,114070,73482],{"class":693},[679,114072,4836],{"class":880},[679,114074,745],{"class":693},[679,114076,110889],{"class":689},[679,114078,1339],{"class":693},[679,114080,114081,114083,114086],{"class":681,"line":3212},[679,114082,73482],{"class":693},[679,114084,114085],{"class":880},"contentType",[679,114087,114088],{"class":693},"(MediaType.APPLICATION_JSON)\n",[679,114090,114091,114093,114095],{"class":681,"line":3217},[679,114092,73482],{"class":693},[679,114094,3006],{"class":880},[679,114096,114097],{"class":693},"(post)\n",[679,114099,114100,114102,114104],{"class":681,"line":3222},[679,114101,73482],{"class":693},[679,114103,105458],{"class":880},[679,114105,17545],{"class":693},[679,114107,114108,114110,114112],{"class":681,"line":3227},[679,114109,73482],{"class":693},[679,114111,3006],{"class":880},[679,114113,114035],{"class":693},[679,114115,114116],{"class":681,"line":3232},[679,114117,985],{"class":693},[679,114119,114120],{"class":681,"line":3499},[679,114121,889],{"emptyLinePlaceholder":797},[679,114123,114124,114126,114128,114130,114132,114135,114137],{"class":681,"line":3509},[679,114125,113442],{"class":693},[679,114127,82237],{"class":880},[679,114129,87309],{"class":693},[679,114131,11341],{"class":2099},[679,114133,114134],{"class":693},", Post ",[679,114136,5100],{"class":2099},[679,114138,4390],{"class":693},[679,114140,114141,114143,114145,114148],{"class":681,"line":3516},[679,114142,9444],{"class":685},[679,114144,113761],{"class":693},[679,114146,114147],{"class":880},"put",[679,114149,17545],{"class":693},[679,114151,114152,114154,114156,114158,114160],{"class":681,"line":3531},[679,114153,73482],{"class":693},[679,114155,4836],{"class":880},[679,114157,745],{"class":693},[679,114159,114015],{"class":689},[679,114161,114018],{"class":693},[679,114163,114164,114166,114168],{"class":681,"line":3536},[679,114165,73482],{"class":693},[679,114167,114085],{"class":880},[679,114169,114088],{"class":693},[679,114171,114172,114174,114176],{"class":681,"line":3541},[679,114173,73482],{"class":693},[679,114175,3006],{"class":880},[679,114177,114097],{"class":693},[679,114179,114180,114182,114184],{"class":681,"line":3546},[679,114181,73482],{"class":693},[679,114183,105458],{"class":880},[679,114185,17545],{"class":693},[679,114187,114188,114190,114192],{"class":681,"line":3551},[679,114189,73482],{"class":693},[679,114191,3006],{"class":880},[679,114193,114035],{"class":693},[679,114195,114196],{"class":681,"line":3557},[679,114197,985],{"class":693},[679,114199,114200],{"class":681,"line":3567},[679,114201,889],{"emptyLinePlaceholder":797},[679,114203,114204,114206,114208,114210,114212],{"class":681,"line":3574},[679,114205,3314],{"class":685},[679,114207,92272],{"class":880},[679,114209,87309],{"class":693},[679,114211,11341],{"class":2099},[679,114213,4390],{"class":693},[679,114215,114216,114219,114221],{"class":681,"line":3589},[679,114217,114218],{"class":693}," restClient.",[679,114220,7632],{"class":880},[679,114222,17545],{"class":693},[679,114224,114225,114227,114229,114231,114233],{"class":681,"line":3594},[679,114226,73482],{"class":693},[679,114228,4836],{"class":880},[679,114230,745],{"class":693},[679,114232,114015],{"class":689},[679,114234,114018],{"class":693},[679,114236,114237,114239,114241],{"class":681,"line":3602},[679,114238,73482],{"class":693},[679,114240,105458],{"class":880},[679,114242,17545],{"class":693},[679,114244,114245,114247,114250],{"class":681,"line":3608},[679,114246,73482],{"class":693},[679,114248,114249],{"class":880},"toBodilessEntity",[679,114251,9317],{"class":693},[679,114253,114254],{"class":681,"line":3619},[679,114255,985],{"class":693},[679,114257,114258],{"class":681,"line":3624},[679,114259,889],{"emptyLinePlaceholder":797},[679,114261,114262],{"class":681,"line":3629},[679,114263,889],{"emptyLinePlaceholder":797},[679,114265,114266],{"class":681,"line":3639},[679,114267,996],{"class":693},[669,114269,114271],{"className":113813,"code":114270,"language":89511,"meta":674,"style":674},"### List Posts\nGET http://localhost:8080/api/posts\n\n### Get Post by ID\nGET http://localhost:8080/api/posts/1\n\n### Create new Post\nPOST http://localhost:8080/api/posts\nContent-Type: application/json\n\n{\n \"title\": \"Dan's Test Title\",\n \"body\": \"Dan's Test Body\",\n \"userId\": 1\n}\n\n### Update Post\nPUT http://localhost:8080/api/posts/1\nContent-Type: application/json\n\n{\n \"id\": 1,\n \"title\": \"UPDATED TITLE\",\n \"body\": \"UPDATED BODY\",\n \"userId\": 1\n}\n\n### Delete Post\nDELETE http://localhost:8080/api/posts/1\n",[676,114272,114273,114278,114284,114288,114293,114300,114304,114309,114315,114325,114329,114333,114345,114357,114367,114371,114375,114380,114387,114395,114399,114403,114414,114425,114436,114444,114448,114452,114457],{"__ignoreMap":674},[679,114274,114275],{"class":681,"line":682},[679,114276,114277],{"class":1400},"### List Posts\n",[679,114279,114280,114282],{"class":681,"line":790},[679,114281,113821],{"class":685},[679,114283,113824],{"class":693},[679,114285,114286],{"class":681,"line":892},[679,114287,889],{"emptyLinePlaceholder":797},[679,114289,114290],{"class":681,"line":901},[679,114291,114292],{"class":1400},"### Get Post by ID\n",[679,114294,114295,114297],{"class":681,"line":909},[679,114296,113821],{"class":685},[679,114298,114299],{"class":693}," http://localhost:8080/api/posts/1\n",[679,114301,114302],{"class":681,"line":918},[679,114303,889],{"emptyLinePlaceholder":797},[679,114305,114306],{"class":681,"line":935},[679,114307,114308],{"class":1400},"### Create new Post\n",[679,114310,114311,114313],{"class":681,"line":944},[679,114312,93764],{"class":685},[679,114314,113824],{"class":693},[679,114316,114317,114320,114322],{"class":681,"line":959},[679,114318,114319],{"class":4508},"Content-Type",[679,114321,2391],{"class":685},[679,114323,114324],{"class":689}," application/json\n",[679,114326,114327],{"class":681,"line":964},[679,114328,889],{"emptyLinePlaceholder":797},[679,114330,114331],{"class":681,"line":977},[679,114332,28448],{"class":693},[679,114334,114335,114338,114340,114343],{"class":681,"line":982},[679,114336,114337],{"class":931}," \"title\"",[679,114339,4282],{"class":693},[679,114341,114342],{"class":689},"\"Dan's Test Title\"",[679,114344,12083],{"class":693},[679,114346,114347,114350,114352,114355],{"class":681,"line":988},[679,114348,114349],{"class":931}," \"body\"",[679,114351,4282],{"class":693},[679,114353,114354],{"class":689},"\"Dan's Test Body\"",[679,114356,12083],{"class":693},[679,114358,114359,114362,114364],{"class":681,"line":993},[679,114360,114361],{"class":931}," \"userId\"",[679,114363,4282],{"class":693},[679,114365,114366],{"class":931},"1\n",[679,114368,114369],{"class":681,"line":2129},[679,114370,996],{"class":693},[679,114372,114373],{"class":681,"line":2140},[679,114374,889],{"emptyLinePlaceholder":797},[679,114376,114377],{"class":681,"line":2145},[679,114378,114379],{"class":1400},"### Update Post\n",[679,114381,114382,114385],{"class":681,"line":2154},[679,114383,114384],{"class":685},"PUT",[679,114386,114299],{"class":693},[679,114388,114389,114391,114393],{"class":681,"line":2159},[679,114390,114319],{"class":4508},[679,114392,2391],{"class":685},[679,114394,114324],{"class":689},[679,114396,114397],{"class":681,"line":2164},[679,114398,889],{"emptyLinePlaceholder":797},[679,114400,114401],{"class":681,"line":3134},[679,114402,28448],{"class":693},[679,114404,114405,114408,114410,114412],{"class":681,"line":3139},[679,114406,114407],{"class":931}," \"id\"",[679,114409,4282],{"class":693},[679,114411,1557],{"class":931},[679,114413,12083],{"class":693},[679,114415,114416,114418,114420,114423],{"class":681,"line":3144},[679,114417,114337],{"class":931},[679,114419,4282],{"class":693},[679,114421,114422],{"class":689},"\"UPDATED TITLE\"",[679,114424,12083],{"class":693},[679,114426,114427,114429,114431,114434],{"class":681,"line":3149},[679,114428,114349],{"class":931},[679,114430,4282],{"class":693},[679,114432,114433],{"class":689},"\"UPDATED BODY\"",[679,114435,12083],{"class":693},[679,114437,114438,114440,114442],{"class":681,"line":3169},[679,114439,114361],{"class":931},[679,114441,4282],{"class":693},[679,114443,114366],{"class":931},[679,114445,114446],{"class":681,"line":3185},[679,114447,996],{"class":693},[679,114449,114450],{"class":681,"line":3194},[679,114451,889],{"emptyLinePlaceholder":797},[679,114453,114454],{"class":681,"line":3199},[679,114455,114456],{"class":1400},"### Delete Post\n",[679,114458,114459,114462],{"class":681,"line":3212},[679,114460,114461],{"class":685},"DELETE",[679,114463,114299],{"class":693},[651,114465,114466],{},"At this point, you can see how effortlessly we've made connections to a public API using the Rest Client. I hope what comes thorugh in the tutorial is the simplicity of the API and the readability of the code.",[4542,114468,114470],{"id":114469},"http-interfaces-rest-client","HTTP Interfaces + REST Client",[651,114472,114473],{},"To save you from the manual implementation of all connectivity details, Spring Boot 3.0 introduced HTTP interfaces. Essentially, you define the contracts for your HTTP requests in an interface and allow Spring to fill in the implementation details at runtime.",[651,114475,114476],{},"The novelty here is that while you needed the WebClient to utilize HTTP interfaces in Spring Boot 3.0, Spring MVC applications that leverage RestClient can now use HTTP interfaces natively, sidestepping the need to add an additional dependency to your Maven configuration.",[669,114478,114480],{"className":4107,"code":114479,"language":4109,"meta":674,"style":674},"public interface JsonPlaceholderService {\n\n @GetExchange(\"/posts\")\n List\u003CPost> findAll();\n\n @GetExchange(\"/posts/{id}\")\n Post findById(Integer id);\n\n @PostExchange(\"/posts\")\n Post create(Post post);\n\n @PutExchange(\"/posts/{id}\")\n Post update(@PathVariable Integer id, Post post);\n\n @DeleteMapping(\"/posts/{id}\")\n void delete(@PathVariable Integer id);\n\n}\n",[676,114481,114482,114492,114496,114508,114520,114524,114536,114548,114552,114564,114576,114580,114592,114612,114616,114628,114644,114648],{"__ignoreMap":674},[679,114483,114484,114486,114488,114490],{"class":681,"line":682},[679,114485,6073],{"class":685},[679,114487,6994],{"class":685},[679,114489,110872],{"class":880},[679,114491,884],{"class":693},[679,114493,114494],{"class":681,"line":790},[679,114495,889],{"emptyLinePlaceholder":797},[679,114497,114498,114500,114502,114504,114506],{"class":681,"line":892},[679,114499,6872],{"class":693},[679,114501,100228],{"class":685},[679,114503,745],{"class":693},[679,114505,110889],{"class":689},[679,114507,1339],{"class":693},[679,114509,114510,114512,114514,114516,114518],{"class":681,"line":901},[679,114511,84917],{"class":693},[679,114513,5105],{"class":685},[679,114515,20881],{"class":693},[679,114517,34142],{"class":880},[679,114519,9317],{"class":693},[679,114521,114522],{"class":681,"line":909},[679,114523,889],{"emptyLinePlaceholder":797},[679,114525,114526,114528,114530,114532,114534],{"class":681,"line":918},[679,114527,6872],{"class":693},[679,114529,100228],{"class":685},[679,114531,745],{"class":693},[679,114533,114015],{"class":689},[679,114535,1339],{"class":693},[679,114537,114538,114540,114542,114544,114546],{"class":681,"line":935},[679,114539,113442],{"class":693},[679,114541,87658],{"class":880},[679,114543,87309],{"class":693},[679,114545,11341],{"class":2099},[679,114547,1208],{"class":693},[679,114549,114550],{"class":681,"line":944},[679,114551,889],{"emptyLinePlaceholder":797},[679,114553,114554,114556,114558,114560,114562],{"class":681,"line":959},[679,114555,6872],{"class":693},[679,114557,110059],{"class":685},[679,114559,745],{"class":693},[679,114561,110889],{"class":689},[679,114563,1339],{"class":693},[679,114565,114566,114568,114570,114572,114574],{"class":681,"line":964},[679,114567,113442],{"class":693},[679,114569,5697],{"class":880},[679,114571,114052],{"class":693},[679,114573,5100],{"class":2099},[679,114575,1208],{"class":693},[679,114577,114578],{"class":681,"line":977},[679,114579,889],{"emptyLinePlaceholder":797},[679,114581,114582,114584,114586,114588,114590],{"class":681,"line":982},[679,114583,6872],{"class":693},[679,114585,110092],{"class":685},[679,114587,745],{"class":693},[679,114589,114015],{"class":689},[679,114591,1339],{"class":693},[679,114593,114594,114596,114598,114600,114602,114604,114606,114608,114610],{"class":681,"line":988},[679,114595,113442],{"class":693},[679,114597,82237],{"class":880},[679,114599,73246],{"class":693},[679,114601,92277],{"class":685},[679,114603,88766],{"class":693},[679,114605,11341],{"class":2099},[679,114607,114134],{"class":693},[679,114609,5100],{"class":2099},[679,114611,1208],{"class":693},[679,114613,114614],{"class":681,"line":993},[679,114615,889],{"emptyLinePlaceholder":797},[679,114617,114618,114620,114622,114624,114626],{"class":681,"line":2129},[679,114619,6872],{"class":693},[679,114621,92256],{"class":685},[679,114623,745],{"class":693},[679,114625,114015],{"class":689},[679,114627,1339],{"class":693},[679,114629,114630,114632,114634,114636,114638,114640,114642],{"class":681,"line":2140},[679,114631,3314],{"class":685},[679,114633,92272],{"class":880},[679,114635,73246],{"class":693},[679,114637,92277],{"class":685},[679,114639,88766],{"class":693},[679,114641,11341],{"class":2099},[679,114643,1208],{"class":693},[679,114645,114646],{"class":681,"line":2145},[679,114647,889],{"emptyLinePlaceholder":797},[679,114649,114650],{"class":681,"line":2154},[679,114651,996],{"class":693},[669,114653,114655],{"className":4107,"code":114654,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n JsonPlaceholderService jsonPlaceholderService() {\n RestClient client = RestClient.create(\"https://jsonplaceholder.typicode.com\");\n HttpServiceProxyFactory factory = HttpServiceProxyFactory\n .builderFor(RestClientAdapter.create(client))\n .build();\n return factory.createClient(JsonPlaceholderService.class);\n }\n\n}\n",[676,114656,114657,114663,114673,114677,114697,114705,114709,114713,114719,114727,114744,114752,114766,114774,114784,114788,114792],{"__ignoreMap":674},[679,114658,114659,114661],{"class":681,"line":682},[679,114660,4116],{"class":693},[679,114662,6068],{"class":685},[679,114664,114665,114667,114669,114671],{"class":681,"line":790},[679,114666,6073],{"class":685},[679,114668,4512],{"class":685},[679,114670,16878],{"class":880},[679,114672,884],{"class":693},[679,114674,114675],{"class":681,"line":892},[679,114676,889],{"emptyLinePlaceholder":797},[679,114678,114679,114681,114683,114685,114687,114689,114691,114693,114695],{"class":681,"line":901},[679,114680,6089],{"class":685},[679,114682,6092],{"class":685},[679,114684,6095],{"class":685},[679,114686,6098],{"class":880},[679,114688,745],{"class":693},[679,114690,4758],{"class":685},[679,114692,16901],{"class":693},[679,114694,6108],{"class":2099},[679,114696,4390],{"class":693},[679,114698,114699,114701,114703],{"class":681,"line":909},[679,114700,6115],{"class":693},[679,114702,6118],{"class":880},[679,114704,16914],{"class":693},[679,114706,114707],{"class":681,"line":918},[679,114708,985],{"class":693},[679,114710,114711],{"class":681,"line":935},[679,114712,889],{"emptyLinePlaceholder":797},[679,114714,114715,114717],{"class":681,"line":944},[679,114716,6872],{"class":693},[679,114718,16929],{"class":685},[679,114720,114721,114723,114725],{"class":681,"line":959},[679,114722,110984],{"class":693},[679,114724,110987],{"class":880},[679,114726,2667],{"class":693},[679,114728,114729,114732,114734,114736,114738,114740,114742],{"class":681,"line":964},[679,114730,114731],{"class":693}," RestClient client ",[679,114733,686],{"class":685},[679,114735,113691],{"class":693},[679,114737,5697],{"class":880},[679,114739,745],{"class":693},[679,114741,111012],{"class":689},[679,114743,1208],{"class":693},[679,114745,114746,114748,114750],{"class":681,"line":977},[679,114747,100570],{"class":693},[679,114749,686],{"class":685},[679,114751,100575],{"class":693},[679,114753,114754,114756,114759,114762,114764],{"class":681,"line":982},[679,114755,73482],{"class":693},[679,114757,114758],{"class":880},"builderFor",[679,114760,114761],{"class":693},"(RestClientAdapter.",[679,114763,5697],{"class":880},[679,114765,100590],{"class":693},[679,114767,114768,114770,114772],{"class":681,"line":988},[679,114769,73482],{"class":693},[679,114771,23612],{"class":880},[679,114773,9317],{"class":693},[679,114775,114776,114778,114780,114782],{"class":681,"line":993},[679,114777,9444],{"class":685},[679,114779,100605],{"class":693},[679,114781,100608],{"class":880},[679,114783,111055],{"class":693},[679,114785,114786],{"class":681,"line":2129},[679,114787,985],{"class":693},[679,114789,114790],{"class":681,"line":2140},[679,114791,889],{"emptyLinePlaceholder":797},[679,114793,114794],{"class":681,"line":2145},[679,114795,996],{"class":693},[4542,114797,78006],{"id":78005},[651,114799,114800],{},"And that's it, folks! With the new RestClient, you'll find your Spring Boot app development journey easier and more enjoyable. The fluent API, fantastic readability, and compatibility with HTTP Interfaces make a compelling case for adopting the RestClient.",[651,114802,114803],{},"Remember, your feedback is valuable. So feel free to drop a comment if you've started tinkering with the RestClient and what you think about it.",[786,114805,114806],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":114808},[114809,114810,114811,114812,114813],{"id":113192,"depth":790,"text":113193},{"id":113245,"depth":790,"text":113246},{"id":113260,"depth":790,"text":113261},{"id":114469,"depth":790,"text":114470},{"id":78005,"depth":790,"text":78006},"This is my first look at the new Rest Client in Spring Boot 3.2. In this tutorial we will discuss what a client is, what are the different implementations of clients available and how to get started with the new Rest Client in Spring Framework 6.1 and Spring Boot 3.2",{"slug":114816,"date":114817,"published":797,"author":798,"tags":114818,"cover":114819,"video":114820,"github":114821,"keywords":114822},"rest-client-first-look","2023-09-08T17:00:00.000Z",[7077],"./spring-boot-rest-client.png","https://www.youtube.com/embed/UDNrJAvKc0k","https://github.com/danvega/rest-client","Spring Framework, Spring Boot, Rest Client",{"title":126,"description":114814},"blog/2023/09/08/rest-client-first-look","4JHNPqinLd-w2rZ9lufAcrQYS7xkD4QvSYPGS-7uQ6I",{"id":114827,"title":123,"body":114828,"description":117199,"extension":793,"meta":117200,"navigation":797,"path":124,"seo":117208,"stem":117209,"__hash__":117210},"content/blog/2023/09/11/spring-jdbc-client.md",{"type":648,"value":114829,"toc":117186},[114830,114833,114836,114840,114843,114846,114849,114852,114856,114859,114862,114866,114869,114875,114879,114886,114889,114893,114911,114940,114948,115266,115282,115393,115397,115406,115544,115550,115572,115575,115579,115588,115594,116303,116306,116310,116323,116329,116889,116892,116896,116911,117014,117170,117173,117175,117178,117183],[651,114831,114832],{},"In this tutorial, we'll be diving into the fresh waters of the new JDBC client in Spring Framework 6.1 and Spring Boot 3.2.",[651,114834,114835],{},"We'll be not only reviewing how to use it but also discussing some of the associated advantages. So, without further ado, let's delve into it.",[4542,114837,114839],{"id":114838},"remembering-the-journey-jdbc-template","Remembering the Journey - JDBC Template",[651,114841,114842],{},"Before we get started, let's talk about how we got here. Interacting with the database, reading and persisting data in Java has historically been quite complex.",[651,114844,114845],{},"You need to consider a lot of factors, like building JDBC URLs, managing database connections, dealing with real-world application concerns such as connection pools, etc. Fortunately, Spring came to our rescue and made these tasks more manageable through its abstractions. One of such is the JDBC template in Spring.",[651,114847,114848],{},"The JDBC template is a solid abstraction that simplified our interaction with databases. However, it has a few cons as it can get pretty verbose if you're building simple CRUD services based on resources. It demands a deeper understanding of all the methods you need to communicate with a database - factors like row mappers, column to field mapping and so on - which can be pretty tricky.",[651,114850,114851],{},"Despite these complexities, one main advantage is the complete control over SQL, which is highly appreciated amongst developers.",[4542,114853,114855],{"id":114854},"introducing-the-new-jdbc-client","Introducing the New JDBC Client",[651,114857,114858],{},"This is where the newest kid on the block - the JDBC client, comes in. Among the things it brings to the table is a fluent API, which is a breeze to understand and read. You'll see this when we glance over the code.",[651,114860,114861],{},"One exciting feature with the JDBC client is that it's auto-configured for us in Spring Boot 3.2. This means we can simply ask for a bean in our application, and we get an instance of it, hassle-free!",[4542,114863,114865],{"id":114864},"diving-deep-into-code","Diving Deep Into Code",[651,114867,114868],{},"Let's get to the fun part - the coding! Being a massive fan of the new JDBC client, I trust that you will love it too!",[651,114870,114871],{},[660,114872],{"alt":114873,"src":114874},"Post Controller","/images/blog/2023/09/11/post-controller.png",[5909,114876,114878],{"id":114877},"step-1-creating-a-new-application","Step 1: Creating a New Application",[651,114880,114881,114882,114885],{},"For this demo, we'll create a brand-new application and set up database access to a local H2 database. For the starter, we are going to use the ",[812,114883,7117],{"href":7115,"rel":114884},[816]," which allows you to quickly prototype your application.",[651,114887,114888],{},"After generating the skeleton of your Maven project, import it in your favorite IDE or text editor.",[5909,114890,114892],{"id":114891},"step-2-building-the-application","Step 2: Building the Application",[651,114894,114895,114896,114898,114899,2797,114901,2797,114903,2797,114905,2797,114908,114910],{},"We'll start by creating a ",[676,114897,5105],{}," class, which represents a blog post in our application. It's a basic record with properties like ",[676,114900,22662],{},[676,114902,11750],{},[676,114904,55311],{},[676,114906,114907],{},"localDateTime",[676,114909,55480],{},", etc.",[669,114912,114914],{"className":4107,"code":114913,"language":4109,"meta":674,"style":674},"public record Post(String id, String title, String slug, LocalDate date, int timeToRead, String tags) {\n\n}\n",[676,114915,114916,114932,114936],{"__ignoreMap":674},[679,114917,114918,114920,114922,114924,114927,114929],{"class":681,"line":682},[679,114919,6073],{"class":685},[679,114921,86928],{"class":685},[679,114923,4658],{"class":880},[679,114925,114926],{"class":693},"(String id, String title, String slug, LocalDate date, ",[679,114928,1078],{"class":685},[679,114930,114931],{"class":693}," timeToRead, String tags) {\n",[679,114933,114934],{"class":681,"line":790},[679,114935,889],{"emptyLinePlaceholder":797},[679,114937,114938],{"class":681,"line":892},[679,114939,996],{"class":693},[651,114941,114942,114943,114945,114946,664],{},"Next, we'll create a ",[676,114944,93270],{}," that will act as a REST controller. It will respond to a request mapping of \"api/posts\". For this controller, we'll wire in a ",[676,114947,23347],{},[669,114949,114951],{"className":4107,"code":114950,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private final PostService postService;\n\n public PostController(PostService postService) {\n this.postService = postService;\n }\n\n @GetMapping(\"\")\n List\u003CPost> findAll() {\n return postService.findAll();\n }\n\n @GetMapping(\"/{id}\")\n Optional\u003CPost> findById(@PathVariable String id) {\n return postService.findById(id);\n }\n\n @PostMapping\n @ResponseStatus(HttpStatus.CREATED)\n void create(@RequestBody Post post) {\n postService.create(post);\n }\n\n @PutMapping(\"/{id}\")\n void update(@RequestBody Post post, @PathVariable String id) {\n postService.update(post, id);\n }\n\n @DeleteMapping(\"/{id}\")\n void delete(@PathVariable String id) {\n postService.delete(id);\n }\n\n}\n",[676,114952,114953,114959,114971,114981,114985,114993,114997,115009,115019,115023,115027,115039,115051,115061,115065,115069,115081,115101,115111,115115,115119,115125,115133,115149,115157,115161,115165,115177,115201,115210,115214,115218,115230,115246,115254,115258,115262],{"__ignoreMap":674},[679,114954,114955,114957],{"class":681,"line":682},[679,114956,4116],{"class":693},[679,114958,9212],{"class":685},[679,114960,114961,114963,114965,114967,114969],{"class":681,"line":790},[679,114962,4116],{"class":693},[679,114964,9275],{"class":685},[679,114966,745],{"class":693},[679,114968,23141],{"class":689},[679,114970,1339],{"class":693},[679,114972,114973,114975,114977,114979],{"class":681,"line":892},[679,114974,6073],{"class":685},[679,114976,4512],{"class":685},[679,114978,23152],{"class":880},[679,114980,884],{"class":693},[679,114982,114983],{"class":681,"line":901},[679,114984,889],{"emptyLinePlaceholder":797},[679,114986,114987,114989,114991],{"class":681,"line":909},[679,114988,9232],{"class":685},[679,114990,12768],{"class":685},[679,114992,9977],{"class":693},[679,114994,114995],{"class":681,"line":918},[679,114996,889],{"emptyLinePlaceholder":797},[679,114998,114999,115001,115003,115005,115007],{"class":681,"line":935},[679,115000,6089],{"class":685},[679,115002,23152],{"class":880},[679,115004,9996],{"class":693},[679,115006,9999],{"class":2099},[679,115008,4390],{"class":693},[679,115010,115011,115013,115015,115017],{"class":681,"line":944},[679,115012,7862],{"class":931},[679,115014,10008],{"class":693},[679,115016,686],{"class":685},[679,115018,10013],{"class":693},[679,115020,115021],{"class":681,"line":959},[679,115022,985],{"class":693},[679,115024,115025],{"class":681,"line":964},[679,115026,889],{"emptyLinePlaceholder":797},[679,115028,115029,115031,115033,115035,115037],{"class":681,"line":977},[679,115030,6872],{"class":693},[679,115032,20852],{"class":685},[679,115034,745],{"class":693},[679,115036,3579],{"class":689},[679,115038,1339],{"class":693},[679,115040,115041,115043,115045,115047,115049],{"class":681,"line":982},[679,115042,84917],{"class":693},[679,115044,5105],{"class":685},[679,115046,20881],{"class":693},[679,115048,34142],{"class":880},[679,115050,2667],{"class":693},[679,115052,115053,115055,115057,115059],{"class":681,"line":988},[679,115054,9444],{"class":685},[679,115056,113413],{"class":693},[679,115058,34142],{"class":880},[679,115060,9317],{"class":693},[679,115062,115063],{"class":681,"line":993},[679,115064,985],{"class":693},[679,115066,115067],{"class":681,"line":2129},[679,115068,889],{"emptyLinePlaceholder":797},[679,115070,115071,115073,115075,115077,115079],{"class":681,"line":2140},[679,115072,6872],{"class":693},[679,115074,20852],{"class":685},[679,115076,745],{"class":693},[679,115078,92261],{"class":689},[679,115080,1339],{"class":693},[679,115082,115083,115085,115087,115089,115091,115093,115095,115097,115099],{"class":681,"line":2145},[679,115084,110032],{"class":693},[679,115086,5105],{"class":685},[679,115088,20881],{"class":693},[679,115090,87658],{"class":880},[679,115092,73246],{"class":693},[679,115094,92277],{"class":685},[679,115096,9289],{"class":693},[679,115098,11341],{"class":2099},[679,115100,4390],{"class":693},[679,115102,115103,115105,115107,115109],{"class":681,"line":2154},[679,115104,9444],{"class":685},[679,115106,113413],{"class":693},[679,115108,87658],{"class":880},[679,115110,88781],{"class":693},[679,115112,115113],{"class":681,"line":2159},[679,115114,985],{"class":693},[679,115116,115117],{"class":681,"line":2164},[679,115118,889],{"emptyLinePlaceholder":797},[679,115120,115121,115123],{"class":681,"line":3134},[679,115122,6872],{"class":693},[679,115124,96257],{"class":685},[679,115126,115127,115129,115131],{"class":681,"line":3139},[679,115128,6872],{"class":693},[679,115130,96264],{"class":685},[679,115132,96267],{"class":693},[679,115134,115135,115137,115139,115141,115143,115145,115147],{"class":681,"line":3144},[679,115136,3314],{"class":685},[679,115138,49468],{"class":880},[679,115140,73246],{"class":693},[679,115142,96282],{"class":685},[679,115144,93429],{"class":693},[679,115146,5100],{"class":2099},[679,115148,4390],{"class":693},[679,115150,115151,115153,115155],{"class":681,"line":3149},[679,115152,113616],{"class":693},[679,115154,5697],{"class":880},[679,115156,110679],{"class":693},[679,115158,115159],{"class":681,"line":3169},[679,115160,985],{"class":693},[679,115162,115163],{"class":681,"line":3185},[679,115164,889],{"emptyLinePlaceholder":797},[679,115166,115167,115169,115171,115173,115175],{"class":681,"line":3194},[679,115168,6872],{"class":693},[679,115170,109638],{"class":685},[679,115172,745],{"class":693},[679,115174,92261],{"class":689},[679,115176,1339],{"class":693},[679,115178,115179,115181,115183,115185,115187,115189,115191,115193,115195,115197,115199],{"class":681,"line":3199},[679,115180,3314],{"class":685},[679,115182,72244],{"class":880},[679,115184,73246],{"class":693},[679,115186,96282],{"class":685},[679,115188,93429],{"class":693},[679,115190,5100],{"class":2099},[679,115192,85575],{"class":693},[679,115194,92277],{"class":685},[679,115196,9289],{"class":693},[679,115198,11341],{"class":2099},[679,115200,4390],{"class":693},[679,115202,115203,115205,115207],{"class":681,"line":3212},[679,115204,113616],{"class":693},[679,115206,82237],{"class":880},[679,115208,115209],{"class":693},"(post, id);\n",[679,115211,115212],{"class":681,"line":3217},[679,115213,985],{"class":693},[679,115215,115216],{"class":681,"line":3222},[679,115217,889],{"emptyLinePlaceholder":797},[679,115219,115220,115222,115224,115226,115228],{"class":681,"line":3227},[679,115221,6872],{"class":693},[679,115223,92256],{"class":685},[679,115225,745],{"class":693},[679,115227,92261],{"class":689},[679,115229,1339],{"class":693},[679,115231,115232,115234,115236,115238,115240,115242,115244],{"class":681,"line":3232},[679,115233,3314],{"class":685},[679,115235,92272],{"class":880},[679,115237,73246],{"class":693},[679,115239,92277],{"class":685},[679,115241,9289],{"class":693},[679,115243,11341],{"class":2099},[679,115245,4390],{"class":693},[679,115247,115248,115250,115252],{"class":681,"line":3499},[679,115249,113616],{"class":693},[679,115251,7632],{"class":880},[679,115253,88781],{"class":693},[679,115255,115256],{"class":681,"line":3509},[679,115257,985],{"class":693},[679,115259,115260],{"class":681,"line":3516},[679,115261,889],{"emptyLinePlaceholder":797},[679,115263,115264],{"class":681,"line":3531},[679,115265,996],{"class":693},[651,115267,115268,115269,115271,115272,2797,115274,2797,115276,2797,115278,48406,115280,664],{},"Crucially, it's important to provide implementations for the ",[676,115270,23347],{}," concerning the two types of JDBC that we're discussing. For this, it involves creating an interface and then defining several methods like ",[676,115273,34142],{},[676,115275,87658],{},[676,115277,5697],{},[676,115279,82237],{},[676,115281,7632],{},[669,115283,115285],{"className":4107,"code":115284,"language":4109,"meta":674,"style":674},"public interface PostService {\n\n List\u003CPost> findAll();\n\n Optional\u003CPost> findById(String id);\n\n void create(Post post);\n\n void update(Post post, String id);\n\n void delete(String id);\n\n}\n",[676,115286,115287,115297,115301,115313,115317,115333,115337,115349,115353,115369,115373,115385,115389],{"__ignoreMap":674},[679,115288,115289,115291,115293,115295],{"class":681,"line":682},[679,115290,6073],{"class":685},[679,115292,6994],{"class":685},[679,115294,23189],{"class":880},[679,115296,884],{"class":693},[679,115298,115299],{"class":681,"line":790},[679,115300,889],{"emptyLinePlaceholder":797},[679,115302,115303,115305,115307,115309,115311],{"class":681,"line":892},[679,115304,84917],{"class":693},[679,115306,5105],{"class":685},[679,115308,20881],{"class":693},[679,115310,34142],{"class":880},[679,115312,9317],{"class":693},[679,115314,115315],{"class":681,"line":901},[679,115316,889],{"emptyLinePlaceholder":797},[679,115318,115319,115321,115323,115325,115327,115329,115331],{"class":681,"line":909},[679,115320,110032],{"class":693},[679,115322,5105],{"class":685},[679,115324,20881],{"class":693},[679,115326,87658],{"class":880},[679,115328,11400],{"class":693},[679,115330,11341],{"class":2099},[679,115332,1208],{"class":693},[679,115334,115335],{"class":681,"line":918},[679,115336,889],{"emptyLinePlaceholder":797},[679,115338,115339,115341,115343,115345,115347],{"class":681,"line":935},[679,115340,3314],{"class":685},[679,115342,49468],{"class":880},[679,115344,114052],{"class":693},[679,115346,5100],{"class":2099},[679,115348,1208],{"class":693},[679,115350,115351],{"class":681,"line":944},[679,115352,889],{"emptyLinePlaceholder":797},[679,115354,115355,115357,115359,115361,115363,115365,115367],{"class":681,"line":959},[679,115356,3314],{"class":685},[679,115358,72244],{"class":880},[679,115360,114052],{"class":693},[679,115362,5100],{"class":2099},[679,115364,20006],{"class":693},[679,115366,11341],{"class":2099},[679,115368,1208],{"class":693},[679,115370,115371],{"class":681,"line":964},[679,115372,889],{"emptyLinePlaceholder":797},[679,115374,115375,115377,115379,115381,115383],{"class":681,"line":977},[679,115376,3314],{"class":685},[679,115378,92272],{"class":880},[679,115380,11400],{"class":693},[679,115382,11341],{"class":2099},[679,115384,1208],{"class":693},[679,115386,115387],{"class":681,"line":982},[679,115388,889],{"emptyLinePlaceholder":797},[679,115390,115391],{"class":681,"line":988},[679,115392,996],{"class":693},[5909,115394,115396],{"id":115395},"step-3-connecting-to-the-database","Step 3: Connecting to the Database",[651,115398,115399,115400,115402,115403,115405],{},"Before we progress, we need to ensure that we can connect to a database. For that, we'll create a ",[676,115401,106017],{}," file that will define our database table structure, which should correspond to the ",[676,115404,5105],{}," record we've created.",[669,115407,115409],{"className":671,"code":115408,"language":673,"meta":674,"style":674},"DROP TABLE IF EXISTS Post;\n\nCREATE TABLE Post (\n id varchar(255) NOT NULL,\n title varchar(255) NOT NULL,\n slug varchar(255) NOT NULL,\n date date NOT NULL,\n time_to_read int NOT NULL,\n tags varchar(255),\n PRIMARY KEY (id)\n);\n",[676,115410,115411,115428,115432,115443,115461,115478,115495,115508,115519,115532,115540],{"__ignoreMap":674},[679,115412,115413,115416,115419,115422,115425],{"class":681,"line":682},[679,115414,115415],{"class":685},"DROP",[679,115417,115418],{"class":685}," TABLE",[679,115420,115421],{"class":685}," IF",[679,115423,115424],{"class":685}," EXISTS",[679,115426,115427],{"class":693}," Post;\n",[679,115429,115430],{"class":681,"line":790},[679,115431,889],{"emptyLinePlaceholder":797},[679,115433,115434,115437,115439,115441],{"class":681,"line":892},[679,115435,115436],{"class":685},"CREATE",[679,115438,115418],{"class":685},[679,115440,4658],{"class":880},[679,115442,59384],{"class":693},[679,115444,115445,115448,115450,115452,115454,115456,115459],{"class":681,"line":901},[679,115446,115447],{"class":693}," id ",[679,115449,7302],{"class":685},[679,115451,745],{"class":693},[679,115453,106071],{"class":931},[679,115455,2378],{"class":693},[679,115457,115458],{"class":685},"NOT NULL",[679,115460,12083],{"class":693},[679,115462,115463,115466,115468,115470,115472,115474,115476],{"class":681,"line":909},[679,115464,115465],{"class":693}," title ",[679,115467,7302],{"class":685},[679,115469,745],{"class":693},[679,115471,106071],{"class":931},[679,115473,2378],{"class":693},[679,115475,115458],{"class":685},[679,115477,12083],{"class":693},[679,115479,115480,115483,115485,115487,115489,115491,115493],{"class":681,"line":918},[679,115481,115482],{"class":693}," slug ",[679,115484,7302],{"class":685},[679,115486,745],{"class":693},[679,115488,106071],{"class":931},[679,115490,2378],{"class":693},[679,115492,115458],{"class":685},[679,115494,12083],{"class":693},[679,115496,115497,115500,115503,115506],{"class":681,"line":935},[679,115498,115499],{"class":685}," date",[679,115501,115502],{"class":685}," date",[679,115504,115505],{"class":685}," NOT NULL",[679,115507,12083],{"class":693},[679,115509,115510,115513,115515,115517],{"class":681,"line":944},[679,115511,115512],{"class":693}," time_to_read ",[679,115514,1078],{"class":685},[679,115516,115505],{"class":685},[679,115518,12083],{"class":693},[679,115520,115521,115524,115526,115528,115530],{"class":681,"line":959},[679,115522,115523],{"class":693}," tags ",[679,115525,7302],{"class":685},[679,115527,745],{"class":693},[679,115529,106071],{"class":931},[679,115531,66689],{"class":693},[679,115533,115534,115537],{"class":681,"line":964},[679,115535,115536],{"class":685}," PRIMARY KEY",[679,115538,115539],{"class":693}," (id)\n",[679,115541,115542],{"class":681,"line":977},[679,115543,1208],{"class":693},[651,115545,115546,115547,115549],{},"You will also need to update ",[676,115548,16242],{}," to include the database connection details.",[669,115551,115552],{"className":76589,"code":106118,"language":35538,"meta":674,"style":674},[676,115553,115554,115560,115566],{"__ignoreMap":674},[679,115555,115556,115558],{"class":681,"line":682},[679,115557,83875],{"class":685},[679,115559,103241],{"class":693},[679,115561,115562,115564],{"class":681,"line":790},[679,115563,27252],{"class":685},[679,115565,106133],{"class":693},[679,115567,115568,115570],{"class":681,"line":892},[679,115569,7323],{"class":685},[679,115571,93485],{"class":693},[651,115573,115574],{},"Remember, since we are not utilizing something like JPA, we must create our table. With all these in place, our application should be ready to launch.",[5909,115576,115578],{"id":115577},"step-4-implementing-the-jdbc-template","Step 4: Implementing the JDBC template",[651,115580,77744,115581,115584,115585,115587],{},[676,115582,115583],{},"TemplatePostService"," class. The task here is to implement all the methods that we previously defined in the ",[676,115586,23347],{}," interface.",[651,115589,115590,115591,115593],{},"For each method, we'll employ the JDBC template to write SQL queries. On a general note, each query method in the JDBC template will necessitate a row mapper to map columns in the database table to the ",[676,115592,5105],{}," record, which can be quite tasking.",[669,115595,115597],{"className":4107,"code":115596,"language":4109,"meta":674,"style":674},"@Service\npublic class TemplatePostService implements PostService {\n\n private static final Logger log = LoggerFactory.getLogger(TemplatePostService.class);\n private final JdbcTemplate jdbcTemplate;\n\n public TemplatePostService(JdbcTemplate jdbcTemplate) {\n this.jdbcTemplate = jdbcTemplate;\n }\n\n RowMapper\u003CPost> rowMapper = (rs, rowNum) -> new Post(\n rs.getString(\"id\"),\n rs.getString(\"title\"),\n rs.getString(\"slug\"),\n rs.getDate(\"date\").toLocalDate(),\n rs.getInt(\"time_to_read\"),\n rs.getString(\"tags\")\n );\n\n @Override\n public List\u003CPost> findAll() {\n var sql = \"SELECT id,title,slug,date,time_to_read,tags FROM post\";\n return jdbcTemplate.query(sql, rowMapper);\n }\n\n @Override\n public Optional\u003CPost> findById(String id) {\n var sql = \"SELECT id,title,slug,date,time_to_read,tags FROM post WHERE id = ?\";\n Post post = null;\n try {\n post = jdbcTemplate.queryForObject(sql,rowMapper,id);\n } catch (DataAccessException ex) {\n log.info(\"Post not found: \" + id);\n }\n\n return Optional.ofNullable(post);\n }\n\n @Override\n public void create(Post post) {\n String sql = \"INSERT INTO post(id,title,slug,date,time_to_read,tags) values(?,?,?,?,?,?)\";\n int insert = jdbcTemplate.update(sql,post.id(),post.title(),post.slug(),post.date(),post.timeToRead(),post.tags());\n if(insert == 1) {\n log.info(\"New Post Created: \" + post.title());\n }\n }\n\n @Override\n public void update(Post post, String id) {\n String sql = \"update post set title = ?, slug = ?, date = ?, time_to_read = ?, tags = ? where id = ?\";\n int update = jdbcTemplate.update(sql,post.title(),post.slug(),post.date(),post.timeToRead(),post.tags(),id);\n if(update == 1) {\n log.info(\"Post Updated: \" + post.title());\n }\n }\n\n @Override\n public void delete(String id) {\n String sql = \"delete from post where id = ?\";\n int delete = jdbcTemplate.update(sql,id);\n if(delete == 1) {\n log.info(\"Post Deleted: \" + id);\n }\n }\n}\n",[676,115598,115599,115605,115620,115624,115643,115652,115656,115670,115682,115686,115690,115713,115727,115739,115752,115770,115784,115796,115800,115804,115810,115824,115837,115850,115854,115858,115864,115882,115895,115906,115912,115927,115941,115957,115961,115965,115975,115979,115983,115989,116003,116015,116057,116070,116089,116093,116097,116101,116107,116125,116136,116172,116185,116204,116208,116212,116216,116222,116236,116247,116263,116276,116291,116295,116299],{"__ignoreMap":674},[679,115600,115601,115603],{"class":681,"line":682},[679,115602,4116],{"class":693},[679,115604,9486],{"class":685},[679,115606,115607,115609,115611,115614,115616,115618],{"class":681,"line":790},[679,115608,6073],{"class":685},[679,115610,4512],{"class":685},[679,115612,115613],{"class":880}," TemplatePostService",[679,115615,4661],{"class":685},[679,115617,23189],{"class":880},[679,115619,884],{"class":693},[679,115621,115622],{"class":681,"line":892},[679,115623,889],{"emptyLinePlaceholder":797},[679,115625,115626,115628,115630,115632,115634,115636,115638,115640],{"class":681,"line":901},[679,115627,9232],{"class":685},[679,115629,6092],{"class":685},[679,115631,12768],{"class":685},[679,115633,111111],{"class":693},[679,115635,686],{"class":685},[679,115637,9240],{"class":693},[679,115639,9243],{"class":880},[679,115641,115642],{"class":693},"(TemplatePostService.class);\n",[679,115644,115645,115647,115649],{"class":681,"line":909},[679,115646,9232],{"class":685},[679,115648,12768],{"class":685},[679,115650,115651],{"class":693}," JdbcTemplate jdbcTemplate;\n",[679,115653,115654],{"class":681,"line":918},[679,115655,889],{"emptyLinePlaceholder":797},[679,115657,115658,115660,115662,115665,115668],{"class":681,"line":935},[679,115659,6089],{"class":685},[679,115661,115613],{"class":880},[679,115663,115664],{"class":693},"(JdbcTemplate ",[679,115666,115667],{"class":2099},"jdbcTemplate",[679,115669,4390],{"class":693},[679,115671,115672,115674,115677,115679],{"class":681,"line":944},[679,115673,7862],{"class":931},[679,115675,115676],{"class":693},".jdbcTemplate ",[679,115678,686],{"class":685},[679,115680,115681],{"class":693}," jdbcTemplate;\n",[679,115683,115684],{"class":681,"line":959},[679,115685,985],{"class":693},[679,115687,115688],{"class":681,"line":964},[679,115689,889],{"emptyLinePlaceholder":797},[679,115691,115692,115695,115697,115700,115702,115705,115707,115709,115711],{"class":681,"line":977},[679,115693,115694],{"class":693}," RowMapper\u003C",[679,115696,5105],{"class":685},[679,115698,115699],{"class":693},"> rowMapper ",[679,115701,686],{"class":685},[679,115703,115704],{"class":693}," (rs, rowNum) ",[679,115706,16955],{"class":685},[679,115708,2054],{"class":685},[679,115710,4658],{"class":880},[679,115712,21337],{"class":693},[679,115714,115715,115718,115721,115723,115725],{"class":681,"line":982},[679,115716,115717],{"class":693}," rs.",[679,115719,115720],{"class":880},"getString",[679,115722,745],{"class":693},[679,115724,93727],{"class":689},[679,115726,66689],{"class":693},[679,115728,115729,115731,115733,115735,115737],{"class":681,"line":988},[679,115730,115717],{"class":693},[679,115732,115720],{"class":880},[679,115734,745],{"class":693},[679,115736,5250],{"class":689},[679,115738,66689],{"class":693},[679,115740,115741,115743,115745,115747,115750],{"class":681,"line":993},[679,115742,115717],{"class":693},[679,115744,115720],{"class":880},[679,115746,745],{"class":693},[679,115748,115749],{"class":689},"\"slug\"",[679,115751,66689],{"class":693},[679,115753,115754,115756,115758,115760,115763,115765,115768],{"class":681,"line":2129},[679,115755,115717],{"class":693},[679,115757,48663],{"class":880},[679,115759,745],{"class":693},[679,115761,115762],{"class":689},"\"date\"",[679,115764,47213],{"class":693},[679,115766,115767],{"class":880},"toLocalDate",[679,115769,56208],{"class":693},[679,115771,115772,115774,115777,115779,115782],{"class":681,"line":2140},[679,115773,115717],{"class":693},[679,115775,115776],{"class":880},"getInt",[679,115778,745],{"class":693},[679,115780,115781],{"class":689},"\"time_to_read\"",[679,115783,66689],{"class":693},[679,115785,115786,115788,115790,115792,115794],{"class":681,"line":2145},[679,115787,115717],{"class":693},[679,115789,115720],{"class":880},[679,115791,745],{"class":693},[679,115793,55583],{"class":689},[679,115795,1339],{"class":693},[679,115797,115798],{"class":681,"line":2154},[679,115799,89766],{"class":693},[679,115801,115802],{"class":681,"line":2159},[679,115803,889],{"emptyLinePlaceholder":797},[679,115805,115806,115808],{"class":681,"line":2164},[679,115807,6872],{"class":693},[679,115809,10723],{"class":685},[679,115811,115812,115814,115816,115818,115820,115822],{"class":681,"line":3134},[679,115813,6089],{"class":685},[679,115815,87217],{"class":693},[679,115817,5105],{"class":685},[679,115819,20881],{"class":693},[679,115821,34142],{"class":880},[679,115823,2667],{"class":693},[679,115825,115826,115828,115830,115832,115835],{"class":681,"line":3139},[679,115827,94003],{"class":685},[679,115829,2049],{"class":693},[679,115831,686],{"class":685},[679,115833,115834],{"class":689}," \"SELECT id,title,slug,date,time_to_read,tags FROM post\"",[679,115836,1186],{"class":693},[679,115838,115839,115841,115844,115847],{"class":681,"line":3144},[679,115840,9444],{"class":685},[679,115842,115843],{"class":693}," jdbcTemplate.",[679,115845,115846],{"class":880},"query",[679,115848,115849],{"class":693},"(sql, rowMapper);\n",[679,115851,115852],{"class":681,"line":3149},[679,115853,985],{"class":693},[679,115855,115856],{"class":681,"line":3169},[679,115857,889],{"emptyLinePlaceholder":797},[679,115859,115860,115862],{"class":681,"line":3185},[679,115861,6872],{"class":693},[679,115863,10723],{"class":685},[679,115865,115866,115868,115870,115872,115874,115876,115878,115880],{"class":681,"line":3194},[679,115867,6089],{"class":685},[679,115869,109520],{"class":693},[679,115871,5105],{"class":685},[679,115873,20881],{"class":693},[679,115875,87658],{"class":880},[679,115877,11400],{"class":693},[679,115879,11341],{"class":2099},[679,115881,4390],{"class":693},[679,115883,115884,115886,115888,115890,115893],{"class":681,"line":3199},[679,115885,94003],{"class":685},[679,115887,2049],{"class":693},[679,115889,686],{"class":685},[679,115891,115892],{"class":689}," \"SELECT id,title,slug,date,time_to_read,tags FROM post WHERE id = ?\"",[679,115894,1186],{"class":693},[679,115896,115897,115900,115902,115904],{"class":681,"line":3212},[679,115898,115899],{"class":693}," Post post ",[679,115901,686],{"class":685},[679,115903,2307],{"class":931},[679,115905,1186],{"class":693},[679,115907,115908,115910],{"class":681,"line":3217},[679,115909,9373],{"class":685},[679,115911,884],{"class":693},[679,115913,115914,115917,115919,115921,115924],{"class":681,"line":3222},[679,115915,115916],{"class":693}," post ",[679,115918,686],{"class":685},[679,115920,115843],{"class":693},[679,115922,115923],{"class":880},"queryForObject",[679,115925,115926],{"class":693},"(sql,rowMapper,id);\n",[679,115928,115929,115931,115933,115936,115939],{"class":681,"line":3227},[679,115930,15675],{"class":693},[679,115932,9394],{"class":685},[679,115934,115935],{"class":693}," (DataAccessException ",[679,115937,115938],{"class":2099},"ex",[679,115940,4390],{"class":693},[679,115942,115943,115945,115947,115949,115952,115954],{"class":681,"line":3232},[679,115944,104907],{"class":693},[679,115946,9415],{"class":880},[679,115948,745],{"class":693},[679,115950,115951],{"class":689},"\"Post not found: \"",[679,115953,3059],{"class":685},[679,115955,115956],{"class":693}," id);\n",[679,115958,115959],{"class":681,"line":3499},[679,115960,1290],{"class":693},[679,115962,115963],{"class":681,"line":3509},[679,115964,889],{"emptyLinePlaceholder":797},[679,115966,115967,115969,115971,115973],{"class":681,"line":3516},[679,115968,9444],{"class":685},[679,115970,110562],{"class":693},[679,115972,110565],{"class":880},[679,115974,110679],{"class":693},[679,115976,115977],{"class":681,"line":3531},[679,115978,985],{"class":693},[679,115980,115981],{"class":681,"line":3536},[679,115982,889],{"emptyLinePlaceholder":797},[679,115984,115985,115987],{"class":681,"line":3541},[679,115986,6872],{"class":693},[679,115988,10723],{"class":685},[679,115990,115991,115993,115995,115997,115999,116001],{"class":681,"line":3546},[679,115992,6089],{"class":685},[679,115994,6095],{"class":685},[679,115996,49468],{"class":880},[679,115998,114052],{"class":693},[679,116000,5100],{"class":2099},[679,116002,4390],{"class":693},[679,116004,116005,116008,116010,116013],{"class":681,"line":3551},[679,116006,116007],{"class":693}," String sql ",[679,116009,686],{"class":685},[679,116011,116012],{"class":689}," \"INSERT INTO post(id,title,slug,date,time_to_read,tags) values(?,?,?,?,?,?)\"",[679,116014,1186],{"class":693},[679,116016,116017,116019,116022,116024,116026,116028,116031,116033,116036,116038,116040,116042,116044,116046,116048,116051,116053,116055],{"class":681,"line":3557},[679,116018,14936],{"class":685},[679,116020,116021],{"class":693}," insert ",[679,116023,686],{"class":685},[679,116025,115843],{"class":693},[679,116027,82237],{"class":880},[679,116029,116030],{"class":693},"(sql,post.",[679,116032,11341],{"class":880},[679,116034,116035],{"class":693},"(),post.",[679,116037,11750],{"class":880},[679,116039,116035],{"class":693},[679,116041,55311],{"class":880},[679,116043,116035],{"class":693},[679,116045,67506],{"class":880},[679,116047,116035],{"class":693},[679,116049,116050],{"class":880},"timeToRead",[679,116052,116035],{"class":693},[679,116054,55480],{"class":880},[679,116056,9431],{"class":693},[679,116058,116059,116061,116064,116066,116068],{"class":681,"line":3567},[679,116060,1249],{"class":685},[679,116062,116063],{"class":693},"(insert ",[679,116065,2304],{"class":685},[679,116067,48606],{"class":931},[679,116069,4390],{"class":693},[679,116071,116072,116074,116076,116078,116081,116083,116085,116087],{"class":681,"line":3574},[679,116073,104907],{"class":693},[679,116075,9415],{"class":880},[679,116077,745],{"class":693},[679,116079,116080],{"class":689},"\"New Post Created: \"",[679,116082,3059],{"class":685},[679,116084,110590],{"class":693},[679,116086,11750],{"class":880},[679,116088,9431],{"class":693},[679,116090,116091],{"class":681,"line":3589},[679,116092,1290],{"class":693},[679,116094,116095],{"class":681,"line":3594},[679,116096,985],{"class":693},[679,116098,116099],{"class":681,"line":3602},[679,116100,889],{"emptyLinePlaceholder":797},[679,116102,116103,116105],{"class":681,"line":3608},[679,116104,6872],{"class":693},[679,116106,10723],{"class":685},[679,116108,116109,116111,116113,116115,116117,116119,116121,116123],{"class":681,"line":3619},[679,116110,6089],{"class":685},[679,116112,6095],{"class":685},[679,116114,72244],{"class":880},[679,116116,114052],{"class":693},[679,116118,5100],{"class":2099},[679,116120,20006],{"class":693},[679,116122,11341],{"class":2099},[679,116124,4390],{"class":693},[679,116126,116127,116129,116131,116134],{"class":681,"line":3624},[679,116128,116007],{"class":693},[679,116130,686],{"class":685},[679,116132,116133],{"class":689}," \"update post set title = ?, slug = ?, date = ?, time_to_read = ?, tags = ? where id = ?\"",[679,116135,1186],{"class":693},[679,116137,116138,116140,116143,116145,116147,116149,116151,116153,116155,116157,116159,116161,116163,116165,116167,116169],{"class":681,"line":3629},[679,116139,14936],{"class":685},[679,116141,116142],{"class":693}," update ",[679,116144,686],{"class":685},[679,116146,115843],{"class":693},[679,116148,82237],{"class":880},[679,116150,116030],{"class":693},[679,116152,11750],{"class":880},[679,116154,116035],{"class":693},[679,116156,55311],{"class":880},[679,116158,116035],{"class":693},[679,116160,67506],{"class":880},[679,116162,116035],{"class":693},[679,116164,116050],{"class":880},[679,116166,116035],{"class":693},[679,116168,55480],{"class":880},[679,116170,116171],{"class":693},"(),id);\n",[679,116173,116174,116176,116179,116181,116183],{"class":681,"line":3639},[679,116175,1249],{"class":685},[679,116177,116178],{"class":693},"(update ",[679,116180,2304],{"class":685},[679,116182,48606],{"class":931},[679,116184,4390],{"class":693},[679,116186,116187,116189,116191,116193,116196,116198,116200,116202],{"class":681,"line":3644},[679,116188,104907],{"class":693},[679,116190,9415],{"class":880},[679,116192,745],{"class":693},[679,116194,116195],{"class":689},"\"Post Updated: \"",[679,116197,3059],{"class":685},[679,116199,110590],{"class":693},[679,116201,11750],{"class":880},[679,116203,9431],{"class":693},[679,116205,116206],{"class":681,"line":3649},[679,116207,1290],{"class":693},[679,116209,116210],{"class":681,"line":3659},[679,116211,985],{"class":693},[679,116213,116214],{"class":681,"line":3664},[679,116215,889],{"emptyLinePlaceholder":797},[679,116217,116218,116220],{"class":681,"line":3669},[679,116219,6872],{"class":693},[679,116221,10723],{"class":685},[679,116223,116224,116226,116228,116230,116232,116234],{"class":681,"line":3679},[679,116225,6089],{"class":685},[679,116227,6095],{"class":685},[679,116229,92272],{"class":880},[679,116231,11400],{"class":693},[679,116233,11341],{"class":2099},[679,116235,4390],{"class":693},[679,116237,116238,116240,116242,116245],{"class":681,"line":3684},[679,116239,116007],{"class":693},[679,116241,686],{"class":685},[679,116243,116244],{"class":689}," \"delete from post where id = ?\"",[679,116246,1186],{"class":693},[679,116248,116249,116251,116254,116256,116258,116260],{"class":681,"line":3689},[679,116250,14936],{"class":685},[679,116252,116253],{"class":693}," delete ",[679,116255,686],{"class":685},[679,116257,115843],{"class":693},[679,116259,82237],{"class":880},[679,116261,116262],{"class":693},"(sql,id);\n",[679,116264,116265,116267,116270,116272,116274],{"class":681,"line":3699},[679,116266,1249],{"class":685},[679,116268,116269],{"class":693},"(delete ",[679,116271,2304],{"class":685},[679,116273,48606],{"class":931},[679,116275,4390],{"class":693},[679,116277,116278,116280,116282,116284,116287,116289],{"class":681,"line":3704},[679,116279,104907],{"class":693},[679,116281,9415],{"class":880},[679,116283,745],{"class":693},[679,116285,116286],{"class":689},"\"Post Deleted: \"",[679,116288,3059],{"class":685},[679,116290,115956],{"class":693},[679,116292,116293],{"class":681,"line":3709},[679,116294,1290],{"class":693},[679,116296,116297],{"class":681,"line":3719},[679,116298,985],{"class":693},[679,116300,116301],{"class":681,"line":3724},[679,116302,996],{"class":693},[651,116304,116305],{},"This is just to show off an example of how to use the JDBC Template so you can compare it to the JDBC Client.",[5909,116307,116309],{"id":116308},"step-5-using-the-jdbc-client","Step 5: Using the JDBC Client",[651,116311,116312,116313,116315,116316,116319,116320,116322],{},"Now, we're going to simplify things. We've seen the ",[676,116314,115583],{},"; now it's time to bring in the sleek ",[676,116317,116318],{},"ClientPostService",". This class implements the ",[676,116321,23347],{}," interface, with similar methods to those we have already discussed.",[651,116324,116325,116326,116328],{},"We'll start by obtaining an instance of the JDBC client. Just like the JDBC template for ",[676,116327,115583],{},", the auto-configured JDBC client of Spring Boot 3.2 is handed over to us.",[669,116330,116332],{"className":4107,"code":116331,"language":4109,"meta":674,"style":674},"@Service\npublic class ClientPostService implements PostService {\n\n private final JdbcClient jdbcClient;\n\n public ClientPostService(JdbcClient jdbcClient) {\n this.jdbcClient = jdbcClient;\n }\n\n @Override\n public List\u003CPost> findAll() {\n return jdbcClient.sql(\"SELECT id,title,slug,date,time_to_read,tags FROM post\")\n .query(Post.class)\n .list();\n }\n\n @Override\n public Optional\u003CPost> findById(String id) {\n return jdbcClient.sql(\"SELECT id,title,slug,date,time_to_read,tags FROM post WHERE id = :id\")\n .param(\"id\", id)\n .query(Post.class)\n .optional();\n }\n\n @Override\n public void create(Post post) {\n int update = jdbcClient.sql(\"INSERT INTO post(id,title,slug,date,time_to_read,tags) values(?,?,?,?,?,?)\")\n .params(List.of(post.id(), post.title(), post.slug(), post.date(), post.timeToRead(), post.tags()))\n .update();\n\n Assert.state(update == 1, \"Failed to create post \" + post.title());\n }\n\n @Override\n public void update(Post post, String id) {\n var updated = jdbcClient.sql(\"update post set title = ?, slug = ?, date = ?, time_to_read = ?, tags = ? where id = ?\")\n .params(List.of(post.title(), post.slug(), post.date(), post.timeToRead(), post.tags(), id))\n .update();\n\n Assert.state(updated == 1, \"Failed to update post \" + post.title());\n }\n\n @Override\n public void delete(String id) {\n var updated = jdbcClient.sql(\"delete from post where id = :id\")\n .param(\"id\", id)\n .update();\n\n Assert.state(updated == 1, \"Failed to delete post \" + id);\n }\n\n}\n",[676,116333,116334,116340,116355,116359,116368,116372,116386,116398,116402,116406,116413,116427,116443,116452,116460,116464,116468,116474,116492,116507,116520,116528,116537,116541,116545,116551,116565,116584,116624,116632,116636,116661,116665,116669,116675,116693,116714,116747,116755,116759,116785,116789,116793,116799,116813,116832,116844,116852,116856,116877,116881,116885],{"__ignoreMap":674},[679,116335,116336,116338],{"class":681,"line":682},[679,116337,4116],{"class":693},[679,116339,9486],{"class":685},[679,116341,116342,116344,116346,116349,116351,116353],{"class":681,"line":790},[679,116343,6073],{"class":685},[679,116345,4512],{"class":685},[679,116347,116348],{"class":880}," ClientPostService",[679,116350,4661],{"class":685},[679,116352,23189],{"class":880},[679,116354,884],{"class":693},[679,116356,116357],{"class":681,"line":892},[679,116358,889],{"emptyLinePlaceholder":797},[679,116360,116361,116363,116365],{"class":681,"line":901},[679,116362,77582],{"class":685},[679,116364,12768],{"class":685},[679,116366,116367],{"class":693}," JdbcClient jdbcClient;\n",[679,116369,116370],{"class":681,"line":909},[679,116371,889],{"emptyLinePlaceholder":797},[679,116373,116374,116376,116378,116381,116384],{"class":681,"line":918},[679,116375,77606],{"class":685},[679,116377,116348],{"class":880},[679,116379,116380],{"class":693},"(JdbcClient ",[679,116382,116383],{"class":2099},"jdbcClient",[679,116385,4390],{"class":693},[679,116387,116388,116390,116393,116395],{"class":681,"line":935},[679,116389,27825],{"class":931},[679,116391,116392],{"class":693},".jdbcClient ",[679,116394,686],{"class":685},[679,116396,116397],{"class":693}," jdbcClient;\n",[679,116399,116400],{"class":681,"line":944},[679,116401,21405],{"class":693},[679,116403,116404],{"class":681,"line":959},[679,116405,889],{"emptyLinePlaceholder":797},[679,116407,116408,116411],{"class":681,"line":964},[679,116409,116410],{"class":693}," @",[679,116412,10723],{"class":685},[679,116414,116415,116417,116419,116421,116423,116425],{"class":681,"line":977},[679,116416,77606],{"class":685},[679,116418,87217],{"class":693},[679,116420,5105],{"class":685},[679,116422,20881],{"class":693},[679,116424,34142],{"class":880},[679,116426,2667],{"class":693},[679,116428,116429,116431,116434,116436,116438,116441],{"class":681,"line":982},[679,116430,21478],{"class":685},[679,116432,116433],{"class":693}," jdbcClient.",[679,116435,673],{"class":880},[679,116437,745],{"class":693},[679,116439,116440],{"class":689},"\"SELECT id,title,slug,date,time_to_read,tags FROM post\"",[679,116442,1339],{"class":693},[679,116444,116445,116447,116449],{"class":681,"line":988},[679,116446,27839],{"class":693},[679,116448,115846],{"class":880},[679,116450,116451],{"class":693},"(Post.class)\n",[679,116453,116454,116456,116458],{"class":681,"line":993},[679,116455,27839],{"class":693},[679,116457,7623],{"class":880},[679,116459,9317],{"class":693},[679,116461,116462],{"class":681,"line":2129},[679,116463,21405],{"class":693},[679,116465,116466],{"class":681,"line":2140},[679,116467,889],{"emptyLinePlaceholder":797},[679,116469,116470,116472],{"class":681,"line":2145},[679,116471,116410],{"class":693},[679,116473,10723],{"class":685},[679,116475,116476,116478,116480,116482,116484,116486,116488,116490],{"class":681,"line":2154},[679,116477,77606],{"class":685},[679,116479,109520],{"class":693},[679,116481,5105],{"class":685},[679,116483,20881],{"class":693},[679,116485,87658],{"class":880},[679,116487,11400],{"class":693},[679,116489,11341],{"class":2099},[679,116491,4390],{"class":693},[679,116493,116494,116496,116498,116500,116502,116505],{"class":681,"line":2159},[679,116495,21478],{"class":685},[679,116497,116433],{"class":693},[679,116499,673],{"class":880},[679,116501,745],{"class":693},[679,116503,116504],{"class":689},"\"SELECT id,title,slug,date,time_to_read,tags FROM post WHERE id = :id\"",[679,116506,1339],{"class":693},[679,116508,116509,116511,116514,116516,116518],{"class":681,"line":2164},[679,116510,27839],{"class":693},[679,116512,116513],{"class":880},"param",[679,116515,745],{"class":693},[679,116517,93727],{"class":689},[679,116519,114018],{"class":693},[679,116521,116522,116524,116526],{"class":681,"line":3134},[679,116523,27839],{"class":693},[679,116525,115846],{"class":880},[679,116527,116451],{"class":693},[679,116529,116530,116532,116535],{"class":681,"line":3139},[679,116531,27839],{"class":693},[679,116533,116534],{"class":880},"optional",[679,116536,9317],{"class":693},[679,116538,116539],{"class":681,"line":3144},[679,116540,21405],{"class":693},[679,116542,116543],{"class":681,"line":3149},[679,116544,889],{"emptyLinePlaceholder":797},[679,116546,116547,116549],{"class":681,"line":3169},[679,116548,116410],{"class":693},[679,116550,10723],{"class":685},[679,116552,116553,116555,116557,116559,116561,116563],{"class":681,"line":3185},[679,116554,77606],{"class":685},[679,116556,6095],{"class":685},[679,116558,49468],{"class":880},[679,116560,114052],{"class":693},[679,116562,5100],{"class":2099},[679,116564,4390],{"class":693},[679,116566,116567,116569,116571,116573,116575,116577,116579,116582],{"class":681,"line":3194},[679,116568,36111],{"class":685},[679,116570,116142],{"class":693},[679,116572,686],{"class":685},[679,116574,116433],{"class":693},[679,116576,673],{"class":880},[679,116578,745],{"class":693},[679,116580,116581],{"class":689},"\"INSERT INTO post(id,title,slug,date,time_to_read,tags) values(?,?,?,?,?,?)\"",[679,116583,1339],{"class":693},[679,116585,116586,116588,116591,116594,116596,116599,116601,116604,116606,116608,116610,116612,116614,116616,116618,116620,116622],{"class":681,"line":3199},[679,116587,27839],{"class":693},[679,116589,116590],{"class":880},"params",[679,116592,116593],{"class":693},"(List.",[679,116595,16672],{"class":880},[679,116597,116598],{"class":693},"(post.",[679,116600,11341],{"class":880},[679,116602,116603],{"class":693},"(), post.",[679,116605,11750],{"class":880},[679,116607,116603],{"class":693},[679,116609,55311],{"class":880},[679,116611,116603],{"class":693},[679,116613,67506],{"class":880},[679,116615,116603],{"class":693},[679,116617,116050],{"class":880},[679,116619,116603],{"class":693},[679,116621,55480],{"class":880},[679,116623,104226],{"class":693},[679,116625,116626,116628,116630],{"class":681,"line":3212},[679,116627,27839],{"class":693},[679,116629,82237],{"class":880},[679,116631,9317],{"class":693},[679,116633,116634],{"class":681,"line":3217},[679,116635,889],{"emptyLinePlaceholder":797},[679,116637,116638,116640,116642,116644,116646,116648,116650,116653,116655,116657,116659],{"class":681,"line":3222},[679,116639,79289],{"class":693},[679,116641,20009],{"class":880},[679,116643,116178],{"class":693},[679,116645,2304],{"class":685},[679,116647,48606],{"class":931},[679,116649,2797],{"class":693},[679,116651,116652],{"class":689},"\"Failed to create post \"",[679,116654,3059],{"class":685},[679,116656,110590],{"class":693},[679,116658,11750],{"class":880},[679,116660,9431],{"class":693},[679,116662,116663],{"class":681,"line":3227},[679,116664,21405],{"class":693},[679,116666,116667],{"class":681,"line":3232},[679,116668,889],{"emptyLinePlaceholder":797},[679,116670,116671,116673],{"class":681,"line":3499},[679,116672,116410],{"class":693},[679,116674,10723],{"class":685},[679,116676,116677,116679,116681,116683,116685,116687,116689,116691],{"class":681,"line":3509},[679,116678,77606],{"class":685},[679,116680,6095],{"class":685},[679,116682,72244],{"class":880},[679,116684,114052],{"class":693},[679,116686,5100],{"class":2099},[679,116688,20006],{"class":693},[679,116690,11341],{"class":2099},[679,116692,4390],{"class":693},[679,116694,116695,116698,116701,116703,116705,116707,116709,116712],{"class":681,"line":3516},[679,116696,116697],{"class":685}," var",[679,116699,116700],{"class":693}," updated ",[679,116702,686],{"class":685},[679,116704,116433],{"class":693},[679,116706,673],{"class":880},[679,116708,745],{"class":693},[679,116710,116711],{"class":689},"\"update post set title = ?, slug = ?, date = ?, time_to_read = ?, tags = ? where id = ?\"",[679,116713,1339],{"class":693},[679,116715,116716,116718,116720,116722,116724,116726,116728,116730,116732,116734,116736,116738,116740,116742,116744],{"class":681,"line":3531},[679,116717,27839],{"class":693},[679,116719,116590],{"class":880},[679,116721,116593],{"class":693},[679,116723,16672],{"class":880},[679,116725,116598],{"class":693},[679,116727,11750],{"class":880},[679,116729,116603],{"class":693},[679,116731,55311],{"class":880},[679,116733,116603],{"class":693},[679,116735,67506],{"class":880},[679,116737,116603],{"class":693},[679,116739,116050],{"class":880},[679,116741,116603],{"class":693},[679,116743,55480],{"class":880},[679,116745,116746],{"class":693},"(), id))\n",[679,116748,116749,116751,116753],{"class":681,"line":3536},[679,116750,27839],{"class":693},[679,116752,82237],{"class":880},[679,116754,9317],{"class":693},[679,116756,116757],{"class":681,"line":3541},[679,116758,889],{"emptyLinePlaceholder":797},[679,116760,116761,116763,116765,116768,116770,116772,116774,116777,116779,116781,116783],{"class":681,"line":3546},[679,116762,79289],{"class":693},[679,116764,20009],{"class":880},[679,116766,116767],{"class":693},"(updated ",[679,116769,2304],{"class":685},[679,116771,48606],{"class":931},[679,116773,2797],{"class":693},[679,116775,116776],{"class":689},"\"Failed to update post \"",[679,116778,3059],{"class":685},[679,116780,110590],{"class":693},[679,116782,11750],{"class":880},[679,116784,9431],{"class":693},[679,116786,116787],{"class":681,"line":3551},[679,116788,21405],{"class":693},[679,116790,116791],{"class":681,"line":3557},[679,116792,889],{"emptyLinePlaceholder":797},[679,116794,116795,116797],{"class":681,"line":3567},[679,116796,116410],{"class":693},[679,116798,10723],{"class":685},[679,116800,116801,116803,116805,116807,116809,116811],{"class":681,"line":3574},[679,116802,77606],{"class":685},[679,116804,6095],{"class":685},[679,116806,92272],{"class":880},[679,116808,11400],{"class":693},[679,116810,11341],{"class":2099},[679,116812,4390],{"class":693},[679,116814,116815,116817,116819,116821,116823,116825,116827,116830],{"class":681,"line":3589},[679,116816,116697],{"class":685},[679,116818,116700],{"class":693},[679,116820,686],{"class":685},[679,116822,116433],{"class":693},[679,116824,673],{"class":880},[679,116826,745],{"class":693},[679,116828,116829],{"class":689},"\"delete from post where id = :id\"",[679,116831,1339],{"class":693},[679,116833,116834,116836,116838,116840,116842],{"class":681,"line":3594},[679,116835,27839],{"class":693},[679,116837,116513],{"class":880},[679,116839,745],{"class":693},[679,116841,93727],{"class":689},[679,116843,114018],{"class":693},[679,116845,116846,116848,116850],{"class":681,"line":3602},[679,116847,27839],{"class":693},[679,116849,82237],{"class":880},[679,116851,9317],{"class":693},[679,116853,116854],{"class":681,"line":3608},[679,116855,889],{"emptyLinePlaceholder":797},[679,116857,116858,116860,116862,116864,116866,116868,116870,116873,116875],{"class":681,"line":3619},[679,116859,79289],{"class":693},[679,116861,20009],{"class":880},[679,116863,116767],{"class":693},[679,116865,2304],{"class":685},[679,116867,48606],{"class":931},[679,116869,2797],{"class":693},[679,116871,116872],{"class":689},"\"Failed to delete post \"",[679,116874,3059],{"class":685},[679,116876,115956],{"class":693},[679,116878,116879],{"class":681,"line":3624},[679,116880,21405],{"class":693},[679,116882,116883],{"class":681,"line":3629},[679,116884,889],{"emptyLinePlaceholder":797},[679,116886,116887],{"class":681,"line":3639},[679,116888,996],{"class":693},[651,116890,116891],{},"The JDBC client has a cleaner and much more readable interface compared to the JDBC template, making writing code smoother and more intuitive.",[5909,116893,116895],{"id":116894},"step-6-using-the-new-post-service","Step 6: Using the new Post Service",[651,116897,116898,116899,116901,116902,116904,116905,116907,116908,88808],{},"As the final step, we'll swap the JDBC template version of our ",[676,116900,23347],{}," with the JDBC client version in the ",[676,116903,93270],{}," class. When the type is\n",[676,116906,23347],{}," are there are 2 implementations you need to be explicit about which implementation Spring should select. There are a few\nways to do this but one way is to use the ",[676,116909,116910],{},"@Qualifier",[669,116912,116914],{"className":4107,"code":116913,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/posts\")\npublic class PostController {\n\n private final PostService postService;\n\n public PostController(@Qualifier(\"clientPostService\") PostService postService) {\n this.postService = postService;\n }\n\n // ...\n\n}\n",[676,116915,116916,116922,116934,116944,116948,116956,116960,116983,116993,116997,117001,117006,117010],{"__ignoreMap":674},[679,116917,116918,116920],{"class":681,"line":682},[679,116919,4116],{"class":693},[679,116921,9212],{"class":685},[679,116923,116924,116926,116928,116930,116932],{"class":681,"line":790},[679,116925,4116],{"class":693},[679,116927,9275],{"class":685},[679,116929,745],{"class":693},[679,116931,23141],{"class":689},[679,116933,1339],{"class":693},[679,116935,116936,116938,116940,116942],{"class":681,"line":892},[679,116937,6073],{"class":685},[679,116939,4512],{"class":685},[679,116941,23152],{"class":880},[679,116943,884],{"class":693},[679,116945,116946],{"class":681,"line":901},[679,116947,889],{"emptyLinePlaceholder":797},[679,116949,116950,116952,116954],{"class":681,"line":909},[679,116951,9232],{"class":685},[679,116953,12768],{"class":685},[679,116955,9977],{"class":693},[679,116957,116958],{"class":681,"line":918},[679,116959,889],{"emptyLinePlaceholder":797},[679,116961,116962,116964,116966,116968,116971,116973,116976,116979,116981],{"class":681,"line":935},[679,116963,6089],{"class":685},[679,116965,23152],{"class":880},[679,116967,73246],{"class":693},[679,116969,116970],{"class":685},"Qualifier",[679,116972,745],{"class":693},[679,116974,116975],{"class":689},"\"clientPostService\"",[679,116977,116978],{"class":693},") PostService ",[679,116980,9999],{"class":2099},[679,116982,4390],{"class":693},[679,116984,116985,116987,116989,116991],{"class":681,"line":944},[679,116986,7862],{"class":931},[679,116988,10008],{"class":693},[679,116990,686],{"class":685},[679,116992,10013],{"class":693},[679,116994,116995],{"class":681,"line":959},[679,116996,985],{"class":693},[679,116998,116999],{"class":681,"line":964},[679,117000,889],{"emptyLinePlaceholder":797},[679,117002,117003],{"class":681,"line":977},[679,117004,117005],{"class":1400}," // ...\n",[679,117007,117008],{"class":681,"line":982},[679,117009,889],{"emptyLinePlaceholder":797},[679,117011,117012],{"class":681,"line":988},[679,117013,996],{"class":693},[669,117015,117017],{"className":4107,"code":117016,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n CommandLineRunner commandLineRunner(@Qualifier(\"clientPostService\") PostService postService) {\n return args -> {\n postService.create(new Post(\"1234\", \"Hello World\", \"hello-world\", LocalDate.now(), 1, \"java, spring\"));\n };\n }\n\n}\n",[676,117018,117019,117025,117035,117039,117059,117067,117071,117075,117081,117101,117111,117154,117158,117162,117166],{"__ignoreMap":674},[679,117020,117021,117023],{"class":681,"line":682},[679,117022,4116],{"class":693},[679,117024,6068],{"class":685},[679,117026,117027,117029,117031,117033],{"class":681,"line":790},[679,117028,6073],{"class":685},[679,117030,4512],{"class":685},[679,117032,16878],{"class":880},[679,117034,884],{"class":693},[679,117036,117037],{"class":681,"line":892},[679,117038,889],{"emptyLinePlaceholder":797},[679,117040,117041,117043,117045,117047,117049,117051,117053,117055,117057],{"class":681,"line":901},[679,117042,6089],{"class":685},[679,117044,6092],{"class":685},[679,117046,6095],{"class":685},[679,117048,6098],{"class":880},[679,117050,745],{"class":693},[679,117052,4758],{"class":685},[679,117054,16901],{"class":693},[679,117056,6108],{"class":2099},[679,117058,4390],{"class":693},[679,117060,117061,117063,117065],{"class":681,"line":909},[679,117062,6115],{"class":693},[679,117064,6118],{"class":880},[679,117066,16914],{"class":693},[679,117068,117069],{"class":681,"line":918},[679,117070,985],{"class":693},[679,117072,117073],{"class":681,"line":935},[679,117074,889],{"emptyLinePlaceholder":797},[679,117076,117077,117079],{"class":681,"line":944},[679,117078,6872],{"class":693},[679,117080,16929],{"class":685},[679,117082,117083,117085,117087,117089,117091,117093,117095,117097,117099],{"class":681,"line":959},[679,117084,20982],{"class":693},[679,117086,23712],{"class":880},[679,117088,73246],{"class":693},[679,117090,116970],{"class":685},[679,117092,745],{"class":693},[679,117094,116975],{"class":689},[679,117096,116978],{"class":693},[679,117098,9999],{"class":2099},[679,117100,4390],{"class":693},[679,117102,117103,117105,117107,117109],{"class":681,"line":964},[679,117104,9444],{"class":685},[679,117106,16952],{"class":693},[679,117108,16955],{"class":685},[679,117110,884],{"class":693},[679,117112,117113,117116,117118,117120,117122,117124,117126,117129,117131,117133,117135,117137,117140,117142,117145,117147,117149,117152],{"class":681,"line":977},[679,117114,117115],{"class":693}," postService.",[679,117117,5697],{"class":880},[679,117119,745],{"class":693},[679,117121,8930],{"class":685},[679,117123,4658],{"class":880},[679,117125,745],{"class":693},[679,117127,117128],{"class":689},"\"1234\"",[679,117130,2797],{"class":693},[679,117132,93610],{"class":689},[679,117134,2797],{"class":693},[679,117136,61277],{"class":689},[679,117138,117139],{"class":693},", LocalDate.",[679,117141,90866],{"class":880},[679,117143,117144],{"class":693},"(), ",[679,117146,1557],{"class":931},[679,117148,2797],{"class":693},[679,117150,117151],{"class":689},"\"java, spring\"",[679,117153,1669],{"class":693},[679,117155,117156],{"class":681,"line":982},[679,117157,17018],{"class":693},[679,117159,117160],{"class":681,"line":988},[679,117161,985],{"class":693},[679,117163,117164],{"class":681,"line":993},[679,117165,889],{"emptyLinePlaceholder":797},[679,117167,117168],{"class":681,"line":2129},[679,117169,996],{"class":693},[651,117171,117172],{},"In navigating to our localhost API in the browser, we should see a collection of all the posts we have inserted and check individual post records by id. For non-existing records, a null result will be returned.",[4542,117174,9042],{"id":9041},[651,117176,117177],{},"That concludes our exploration of the new JDBC Client in the Spring Framework 6.1 and Spring Boot 3.2. Once again, Spring has introduced a tool that simplifies otherwise complex tasks – a fluent API with easy setup for smooth use in your spring project.",[1004,117179,117180],{},[651,117181,117182],{},"\"Simplicity is the ultimate sophistication\" - Leonardo da Vinci.",[786,117184,117185],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":117187},[117188,117189,117190,117198],{"id":114838,"depth":790,"text":114839},{"id":114854,"depth":790,"text":114855},{"id":114864,"depth":790,"text":114865,"children":117191},[117192,117193,117194,117195,117196,117197],{"id":114877,"depth":892,"text":114878},{"id":114891,"depth":892,"text":114892},{"id":115395,"depth":892,"text":115396},{"id":115577,"depth":892,"text":115578},{"id":116308,"depth":892,"text":116309},{"id":116894,"depth":892,"text":116895},{"id":9041,"depth":790,"text":9042},"In this tutorial you will learn about the new JDBC Client in Spring Framework 6.1 and Spring Boot 3.2.",{"slug":117201,"date":117202,"published":797,"author":798,"tags":117203,"cover":117204,"video":117205,"github":117206,"keywords":117207},"spring-jdbc-client","2023-09-11T17:00:00.000Z",[7077],"./spring-jdbc-client.png","https://www.youtube.com/embed/JBu5GibEJ4k","https://github.com/danvega/jdbc-client","Spring Framework, Spring Boot, JDBC Client, JDBC Template, Spring Data, Spring Data JDBC, Java, Java JDBC",{"title":123,"description":117199},"blog/2023/09/11/spring-jdbc-client","Mhkiz35jo8JHDVbwdSvppP4vJmUMXD8gKDZL5_atsTo",{"id":117212,"title":120,"body":117213,"description":117561,"extension":793,"meta":117562,"navigation":797,"path":121,"seo":117571,"stem":117572,"__hash__":117573},"content/blog/2023/09/17/spring-boot-actuator.md",{"type":648,"value":117214,"toc":117551},[117215,117218,117222,117225,117233,117236,117239,117245,117249,117252,117261,117264,117267,117271,117274,117291,117294,117297,117301,117304,117324,117330,117333,117337,117344,117347,117350,117354,117363,117366,117369,117372,117375,117381,117401,117407,117411,117414,117434,117529,117532,117536,117539,117542,117545,117548],[651,117216,117217],{},"In this article, we will focus on the most important Spring Boot starter that you should include in all of your projects. When creating a new Spring Boot Application, you have multiple choices for the dependencies you should include based on what you're building. While this is just my opinion, I will share what I believe is the number one Spring Boot Starter that you should include in each of your applications. Before reading the entire article, do you have any guesses? If you guessed the Spring Boot Actuator, you are correct. Let's take a look at what the Spring Boot Actuator is and why you should include it in every project.",[4542,117219,117221],{"id":117220},"what-is-a-spring-boot-starter","What is a Spring Boot Starter",[651,117223,117224],{},"For those who are not familiar with the concept, Spring Boot Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology you may need. Basically, starters are a set of convenient dependency bundles that you can include in your application.",[651,117226,117227,117228,117232],{},"You head over to ",[812,117229,117231],{"href":7115,"rel":117230},[816],"start Spring IO",", fill in some metadata about your project, and then shift over to the right side of the screen. Here you have a plethora of dependencies to choose from.",[651,117234,117235],{},"The pivotal question here is: From this vast range of dependencies, which one should you include in every project you create?",[651,117237,117238],{},"There's no universally correct answer to this question, as it largely hinges on your specific project requirements. However, in this post, I am sharing my opinion or rather the solution that I religiously follow - Include the Spring Boot Actuator in every single project.",[651,117240,117241],{},[660,117242],{"alt":117243,"src":117244},"Start","/images/blog/2023/09/17/photo-1608496601160-f86d19a44f9f.jpeg",[4542,117246,117248],{"id":117247},"why-the-spring-boot-actuator","Why the Spring Boot Actuator?",[651,117250,117251],{},"Our ultimate goal while developing a new application is to usher it to the promised land - production. Regardless of whether you're building a straightforward demo, an MVP (Minimum Viable Product), or even the next-gen SaaS offering, the application should be production-ready.",[651,117253,117254,117255,117260],{},"This is where the ",[812,117256,117259],{"href":117257,"rel":117258},"https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html",[816],"Spring Boot Actuator"," comes into play.",[651,117262,117263],{},"It offers several additional features to monitor and manage your application when you push it into production, providing invaluable insights into app functionality. This is much beyond merely pushing code into production and waiting for traffic.",[651,117265,117266],{},"The Spring Boot Actuator fosters a solid understanding of what's happening within your app.",[4542,117268,117270],{"id":117269},"setting-the-stage-building-a-new-application","Setting the Stage: Building a New Application",[651,117272,117273],{},"In today's discussion, the primary objectives are:",[5316,117275,117276,117279,117282,117285,117288],{},[5332,117277,117278],{},"Building a new application from scratch,",[5332,117280,117281],{},"Incorporating Spring Boot Actuator,",[5332,117283,117284],{},"Discussing what you get out of the box,",[5332,117286,117287],{},"Exploring potentials to configure it,",[5332,117289,117290],{},"Reviewing several endpoints that arrive with Spring Boot Actuator.",[651,117292,117293],{},"In addition to this, we will also delve into creating your custom endpoints for tailoring added functionality within the actuator.",[651,117295,117296],{},"So, are we all set for an exciting journey into the realm of the Spring Boot Actuator? Well, let's roll up our sleeves and get coding!",[4542,117298,117300],{"id":117299},"creating-a-new-project-with-spring-boot-actuator","Creating a New Project with Spring Boot Actuator",[651,117302,117303],{},"To kickstart our process, navigate to start Spring IO and create a new project. Here's a step-by-step guide to assist you with this:",[5316,117305,117306,117309,117312,117315,117318,117321],{},[5332,117307,117308],{},"Choose maven java as your project type.",[5332,117310,117311],{},"Select the latest stable release of Spring Boot, the current one being 3.1.3.",[5332,117313,117314],{},"Fill in the metadata, say, developer name - Dan Vega, and project name - actuator demo.",[5332,117316,117317],{},"Click on dependencies and choose Spring Boot Actuator.",[5332,117319,117320],{},"Depending on the type of application, you can add other dependencies. In this case, I will keep the app simple and include the web dependency for building a web application.",[5332,117322,117323],{},"Finally, generate the project, download the zip file, and open it in your preferred IDE (Integrated Development Environment).",[651,117325,117326],{},[660,117327],{"alt":117328,"src":117329},"Spring Boot Initializr","/images/blog/2023/09/17/spring-init.png",[651,117331,117332],{},"Now that we have a new project with the Spring Boot Actuator dependency configured, it's time to explore what the actuator brings to the table and how we can leverage it to its optimum potential.",[4542,117334,117336],{"id":117335},"running-the-application-taking-a-peep-underneath-the-hood","Running the Application: Taking a Peep Underneath the Hood",[651,117338,117339,117340,664],{},"If you do nothing and just run the application as is you will see in the console that there is mapping exposed at “/actuator”. Head over to your browser and open ",[812,117341,117342],{"href":117342,"rel":117343},"http://localhost:8080/actuator",[816],[651,117345,117346],{},"Here, you will notice some exposed endpoints. Start your exploration with the Health endpoint; it will display a status of \"up.\" This status update is an indicator for other applications about the functioning status of your application.",[651,117348,117349],{},"However, out-of-the-box, Spring Boot Actuator doesn't offer much. To understand why, let's hop over to the documentation.",[4542,117351,117353],{"id":117352},"diving-deeper-into-the-spring-boot-actuator","Diving Deeper into the Spring Boot Actuator",[651,117355,117356,117357,117362],{},"Examining the ",[812,117358,117361],{"href":117359,"rel":117360},"https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready",[816],"Spring Boot documentation",", you can find a section titled \"Production-ready features.\" Here's where you will encounter details about the Actuator, Endpoints, metrics observability, etc.",[651,117364,117365],{},"The Actuator provides a variety of features to help you monitor and manage your application. You can manage and monitor these features either over HTTP endpoints or JMX. Currently, we're working with HTTP.",[651,117367,117368],{},"You can apply features like auditing, health, and metrics gathering to your applications automatically. How to do this? Simply include the Spring Boot Starter Actuator!",[651,117370,117371],{},"The Actuator Endpoints enable you to monitor and interact with your application. While there are several built-in endpoints, you also hold the liberty to add your own. Some of these might be contingent on whether you have another dependency included.",[651,117373,117374],{},"Most of these endpoints are disabled by default, and then you can go through and configure them as per your requirements. In essence, they offer a substantial amount of insight about the beans in your application context.",[651,117376,117377,117378,117380],{},"If you open up ",[676,117379,88437],{}," you can set the following properties to enable all of the endpoints and get more information in the health endpoint.",[669,117382,117384],{"className":76589,"code":117383,"language":35538,"meta":674,"style":674},"management.endpoints.web.exposure.include=*\nmanagement.endpoint.health.show-details=always\n",[676,117385,117386,117393],{"__ignoreMap":674},[679,117387,117388,117391],{"class":681,"line":682},[679,117389,117390],{"class":685},"management.endpoints.web.exposure.include",[679,117392,89035],{"class":693},[679,117394,117395,117398],{"class":681,"line":790},[679,117396,117397],{"class":685},"management.endpoint.health.show-details",[679,117399,117400],{"class":693},"=always\n",[651,117402,117403],{},[660,117404],{"alt":117405,"src":117406},"actuator_endpoints.png","/images/blog/2023/09/17/actuator_endpoints.png",[4542,117408,117410],{"id":117409},"extending-the-usability-of-spring-boot-actuator-creating-your-own-custom-endpoint","Extending the Usability of Spring Boot Actuator: Creating Your Own Custom Endpoint",[651,117412,117413],{},"While the pre-built endpoints of Spring Boot Actuator are great, you might experience the need to develop your own endpoint. The following illustration shows how to create a custom endpoint:",[5316,117415,117416,117419,117422,117425,117428,117431],{},[5332,117417,117418],{},"To create a new random endpoint, add the @Endpoint annotation and give it an ID of \"random.\"",[5332,117420,117421],{},"Implement different operations based on your needs.",[5332,117423,117424],{},"In this case, we want to provide a random number via the custom endpoint. So, @ReadOperation would be the most fitting choice here.",[5332,117426,117427],{},"Enter the return type (integer in this case).",[5332,117429,117430],{},"Write down the method to create a random number, such as {return new Random().nextInt(100);}",[5332,117432,117433],{},"Lastly, it's essential to annotate the custom endpoint class with Spring's @Service annotation to ensure that Spring recognizes it, and it's correctly injected into the application context.",[669,117435,117437],{"className":4107,"code":117436,"language":4109,"meta":674,"style":674},"@Service\n@Endpoint(id = \"random\")\npublic class RandomEndpoint {\n\n @ReadOperation\n public String random() {\n return String.valueOf(new Random().nextInt());\n }\n\n}\n",[676,117438,117439,117445,117462,117473,117477,117484,117494,117517,117521,117525],{"__ignoreMap":674},[679,117440,117441,117443],{"class":681,"line":682},[679,117442,4116],{"class":693},[679,117444,9486],{"class":685},[679,117446,117447,117449,117452,117454,117456,117458,117460],{"class":681,"line":790},[679,117448,4116],{"class":693},[679,117450,117451],{"class":685},"Endpoint",[679,117453,745],{"class":693},[679,117455,11341],{"class":931},[679,117457,6883],{"class":685},[679,117459,100370],{"class":689},[679,117461,1339],{"class":693},[679,117463,117464,117466,117468,117471],{"class":681,"line":892},[679,117465,6073],{"class":685},[679,117467,4512],{"class":685},[679,117469,117470],{"class":880}," RandomEndpoint",[679,117472,884],{"class":693},[679,117474,117475],{"class":681,"line":901},[679,117476,889],{"emptyLinePlaceholder":797},[679,117478,117479,117481],{"class":681,"line":909},[679,117480,6872],{"class":693},[679,117482,117483],{"class":685},"ReadOperation\n",[679,117485,117486,117488,117490,117492],{"class":681,"line":918},[679,117487,6089],{"class":685},[679,117489,9289],{"class":693},[679,117491,5899],{"class":880},[679,117493,2667],{"class":693},[679,117495,117496,117498,117500,117503,117505,117507,117510,117512,117515],{"class":681,"line":935},[679,117497,9444],{"class":685},[679,117499,72976],{"class":693},[679,117501,117502],{"class":880},"valueOf",[679,117504,745],{"class":693},[679,117506,8930],{"class":685},[679,117508,117509],{"class":880}," Random",[679,117511,10541],{"class":693},[679,117513,117514],{"class":880},"nextInt",[679,117516,9431],{"class":693},[679,117518,117519],{"class":681,"line":944},[679,117520,985],{"class":693},[679,117522,117523],{"class":681,"line":959},[679,117524,889],{"emptyLinePlaceholder":797},[679,117526,117527],{"class":681,"line":964},[679,117528,996],{"class":693},[651,117530,117531],{},"Congratulations! You have successfully extended the usability of the Spring Boot Actuator by creating your own custom endpoint.",[4542,117533,117535],{"id":117534},"summing-up-the-spring-boot-actuator-journey","Summing Up the Spring Boot Actuator Journey",[651,117537,117538],{},"The Spring Boot Actuator proves to be an exceptionally versatile tool for Spring Developers, aiding in efficient application monitoring and management when pushed into production with minimal effort. It provides an additional layer of insight into application functionality, thereby leaving no room for guesswork.",[651,117540,117541],{},"Whether it's the out-of-the-box usability or the freedom to extend its capability further by developing your custom endpoints, the Spring Boot Actuator checks all the boxes.",[651,117543,117544],{},"So, if you are planning to embark on your next Spring Boot project, do give the Spring Boot Actuator a whirl. It is one Spring Boot Starter that promises to revamp your coding journey, making it more insightful and streamlined.",[651,117546,117547],{},"Happy coding, folks!",[786,117549,117550],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}",{"title":674,"searchDepth":790,"depth":790,"links":117552},[117553,117554,117555,117556,117557,117558,117559,117560],{"id":117220,"depth":790,"text":117221},{"id":117247,"depth":790,"text":117248},{"id":117269,"depth":790,"text":117270},{"id":117299,"depth":790,"text":117300},{"id":117335,"depth":790,"text":117336},{"id":117352,"depth":790,"text":117353},{"id":117409,"depth":790,"text":117410},{"id":117534,"depth":790,"text":117535},"In this article, we will focus on the most important Spring Boot starter that you should include in all of your projects. The Spring Boot Actuator.",{"slug":117563,"date":117564,"updatedOn":117565,"published":797,"author":798,"tags":117566,"cover":117567,"video":117568,"github":117569,"keywords":117570},"spring-boot-actuator","2023-09-17T17:00:00.000Z","2023-12-14T20:00:00.000Z",[7077],"spring-boot-actuator.png","https://www.youtube.com/embed/4OVe0MWgZ4k","https://github.com/danvega/actuator","Spring Framework, Spring Boot, Spring Boot Actuator",{"title":120,"description":117561},"blog/2023/09/17/spring-boot-actuator","g7kxywUECoqdFA9rTMTKsZPzfWJILIW1ixn9S-cPxnA",{"id":117575,"title":117,"body":117576,"description":118301,"extension":793,"meta":118302,"navigation":797,"path":118,"seo":118310,"stem":118311,"__hash__":118312},"content/blog/2023/12/14/virtual-threads-spring-boot.md",{"type":648,"value":117577,"toc":118290},[117578,117581,117585,117588,117595,117601,117604,117607,117613,117617,117620,117626,117629,117636,117640,117643,117649,117653,117660,117688,117693,117703,117706,117722,117765,117768,117777,117893,117896,118034,118045,118070,118076,118088,118091,118116,118120,118123,118137,118148,118151,118157,118163,118169,118175,118181,118184,118190,118193,118199,118202,118232,118235,118260,118263,118269,118273,118276,118278,118281,118287],[651,117579,117580],{},"If you're reading this article, chances are you have heard about Project Loom and Virtual Threads in Java. If you haven't heard about them yet, don't worry. I will provide a quick introduction to what they are and why they are so important. Additionally, I will explain why I believe this feature is significant for Spring Boot developers and provide guidance on how you can start using it today. By the end of this article, you will have a better understanding of virtual threads and how they can enhance the performance of certain types of Spring Boot applications.",[4542,117582,117584],{"id":117583},"virtual-threads-in-java","Virtual Threads in Java",[651,117586,117587],{},"In Java, each statement inside of a method executes in a thread. Java is Multi-threaded which means more than one thread can be executing a given set of instructions at once. Threads are fundamental to concurrent programming and allow a Java program to perform multiple tasks or operations concurrently, making efficient use of available resources, such as CPU cores.",[651,117589,117590,117591,117594],{},"In Java, a thread (",[676,117592,117593],{},"java.lang.Thread",") serves as a thin wrapper around the operating system thread, commonly known as Platform Threads. The operating system thread is a limited resource that relies on system resources. One major drawback of Platform Threads, in addition to their ubiquity, is their high resource consumption in terms of memory and processing time.",[651,117596,117597],{},[660,117598],{"alt":117599,"src":117600},"Java Threads","/images/blog/2023/12/14/java_threads.png",[651,117602,117603],{},"Now imagine you need to perform a blocking operation such as talking to a database or communicating with a service over HTTP. These operations are consider blocking because the thread responsible for handling this operation needs to wait for the operation to complete.",[651,117605,117606],{},"Before the introduction of virtual threads, there were two options available for addressing scalability issues in concurrent programming in Java. One option was to add more hardware by scaling horizontally or vertically. Another option was to use asynchronous programming, which, like any architectural choice, has its own advantages and disadvantages.",[651,117608,117609],{},[660,117610],{"alt":117611,"src":117612},"Scalability Options","/images/blog/2023/12/14/scaling_options.png",[5909,117614,117616],{"id":117615},"why-virtual-threads-in-java","Why Virtual Threads in Java?",[651,117618,117619],{},"Virtual Threads were introduced in JDK 21 as a solution to the challenges of asynchronous programming. These lightweight threads do not block platform threads, making them highly efficient. In fact, you can spawn millions of Virtual Threads without needing to worry about thread pooling. This allows us to write imperative blocking code without significant concerns about scalability.",[651,117621,117622],{},[660,117623],{"alt":117624,"src":117625},"Virtual Threads","/images/blog/2023/12/14/virtual_threads.png",[651,117627,117628],{},"Of course there are still Platform Threads which are a finite resource so how does this work under the hood? When a virtual thread is attached to a platform and needs to perform a blocking task it is unmounted from the platform thread and moved to the heap. When the blocking operation has completed it is taken from the heap and moved to a waiting list for the platform thread it was initially mounted on.",[651,117630,117631,117632,664],{},"This is really just a high level introduction to Virtual Threads in JDK 21. If you want to understand the goals, non-goals, motivation and more about them I would encourage you to take some time to read through the ",[812,117633,117635],{"href":104628,"rel":117634},[816],"JEP (JDK Enhancement Proposal)",[4542,117637,117639],{"id":117638},"virtual-threads-in-spring","Virtual Threads in Spring",[651,117641,117642],{},"Now that you have an understanding of concurrency in Java and the problem that Virtual Threads are trying to solve, let's discuss it in the context of Spring Boot. If you are building Spring MVC applications that involve blocking operations, such as database communication, writing to an Input Stream, or interacting with an HTTP service, then your application is an ideal candidate to benefit from Virtual Threads.",[651,117644,117645],{},[660,117646],{"alt":117647,"src":117648},"Blocking Requests","/images/blog/2023/12/14/blocking_app.png",[5909,117650,117652],{"id":117651},"spring-boot-virtual-threads-example","Spring Boot Virtual Threads Example",[651,117654,117655,117656,117659],{},"In this section we will create a new Spring Boot Application that will perform a blocking operation by communicating with a REST API over HTTP. To get started head over to ",[812,117657,77478],{"href":30440,"rel":117658},[816]," and create a project with the following properties:",[5316,117661,117662,117667,117673,117678,117683],{},[5332,117663,117664,117666],{},[2939,117665,36767],{},": Maven",[5332,117668,117669,117672],{},[2939,117670,117671],{},"Language",": Java",[5332,117674,117675,117677],{},[2939,117676,7077],{},": 3.2.0+",[5332,117679,117680,117682],{},[2939,117681,36422],{},": 21",[5332,117684,117685,117687],{},[2939,117686,51430],{},": Web",[651,117689,117690],{},[660,117691],{"alt":7117,"src":117692},"/images/blog/2023/12/14/spring-init.png",[651,117694,117695,117696,117698,117699,117702],{},"Next, we will create a controller that handles a ",[676,117697,113821],{}," request and makes a call to an external service called HTTPBin. This service is useful because it provides various public endpoints, including one called ",[676,117700,117701],{},"/block"," that allows us to pause for a specified number of seconds.",[651,117704,117705],{},"To create the controller, follow these steps:",[27665,117707,117708,117714],{},[5332,117709,117710,117711,664],{},"Create a new controller named ",[676,117712,117713],{},"HttpBinController",[5332,117715,117716,117717,23212,117719,664],{},"Annotate the controller with ",[676,117718,12329],{},[676,117720,117721],{},"@RequestMapping(\"/httpbin\")",[669,117723,117725],{"className":4107,"code":117724,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/httpbin\")\npublic class HttpBinController {\n\n}\n",[676,117726,117727,117733,117746,117757,117761],{"__ignoreMap":674},[679,117728,117729,117731],{"class":681,"line":682},[679,117730,4116],{"class":693},[679,117732,9212],{"class":685},[679,117734,117735,117737,117739,117741,117744],{"class":681,"line":790},[679,117736,4116],{"class":693},[679,117738,9275],{"class":685},[679,117740,745],{"class":693},[679,117742,117743],{"class":689},"\"/httpbin\"",[679,117745,1339],{"class":693},[679,117747,117748,117750,117752,117755],{"class":681,"line":892},[679,117749,6073],{"class":685},[679,117751,4512],{"class":685},[679,117753,117754],{"class":880}," HttpBinController",[679,117756,884],{"class":693},[679,117758,117759],{"class":681,"line":901},[679,117760,889],{"emptyLinePlaceholder":797},[679,117762,117763],{"class":681,"line":909},[679,117764,996],{"class":693},[651,117766,117767],{},"To communicate with HttpBin, we can utilize the new RestClient in Spring Boot 3.2. You can use the builder to set the base URL and obtain a new instance for using the Rest Client.",[651,117769,117770,117771,117776],{},"For more information, refer to the ",[812,117772,117775],{"href":117773,"rel":117774},"https://www.danvega.dev/blog/rest-client-first-look",[816],"Rest Client First Look"," blog post.",[669,117778,117780],{"className":4107,"code":117779,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/httpbin\")\npublic class HttpBinController {\n\n private static final Logger log = LoggerFactory.getLogger(HttpBinController.class);\n private final RestClient restClient;\n\n public HttpBinController(RestClient.Builder restClientBuilder) {\n restClient = restClientBuilder.baseUrl(\"https://httpbin.org/\").build();\n }\n\n}\n",[676,117781,117782,117788,117800,117810,117814,117833,117841,117845,117859,117881,117885,117889],{"__ignoreMap":674},[679,117783,117784,117786],{"class":681,"line":682},[679,117785,4116],{"class":693},[679,117787,9212],{"class":685},[679,117789,117790,117792,117794,117796,117798],{"class":681,"line":790},[679,117791,4116],{"class":693},[679,117793,9275],{"class":685},[679,117795,745],{"class":693},[679,117797,117743],{"class":689},[679,117799,1339],{"class":693},[679,117801,117802,117804,117806,117808],{"class":681,"line":892},[679,117803,6073],{"class":685},[679,117805,4512],{"class":685},[679,117807,117754],{"class":880},[679,117809,884],{"class":693},[679,117811,117812],{"class":681,"line":901},[679,117813,889],{"emptyLinePlaceholder":797},[679,117815,117816,117818,117820,117822,117824,117826,117828,117830],{"class":681,"line":909},[679,117817,9232],{"class":685},[679,117819,6092],{"class":685},[679,117821,12768],{"class":685},[679,117823,111111],{"class":693},[679,117825,686],{"class":685},[679,117827,9240],{"class":693},[679,117829,9243],{"class":880},[679,117831,117832],{"class":693},"(HttpBinController.class);\n",[679,117834,117835,117837,117839],{"class":681,"line":918},[679,117836,9232],{"class":685},[679,117838,12768],{"class":685},[679,117840,113669],{"class":693},[679,117842,117843],{"class":681,"line":935},[679,117844,889],{"emptyLinePlaceholder":797},[679,117846,117847,117849,117851,117854,117857],{"class":681,"line":944},[679,117848,6089],{"class":685},[679,117850,117754],{"class":880},[679,117852,117853],{"class":693},"(RestClient.Builder ",[679,117855,117856],{"class":2099},"restClientBuilder",[679,117858,4390],{"class":693},[679,117860,117861,117863,117865,117868,117870,117872,117875,117877,117879],{"class":681,"line":959},[679,117862,113686],{"class":693},[679,117864,686],{"class":685},[679,117866,117867],{"class":693}," restClientBuilder.",[679,117869,100528],{"class":880},[679,117871,745],{"class":693},[679,117873,117874],{"class":689},"\"https://httpbin.org/\"",[679,117876,47213],{"class":693},[679,117878,23612],{"class":880},[679,117880,9317],{"class":693},[679,117882,117883],{"class":681,"line":964},[679,117884,985],{"class":693},[679,117886,117887],{"class":681,"line":977},[679,117888,889],{"emptyLinePlaceholder":797},[679,117890,117891],{"class":681,"line":982},[679,117892,996],{"class":693},[651,117894,117895],{},"Next, create a new method called \"delay\" that takes the number of seconds to block for as a parameter. Use the Rest Client to make a call to the HttpBin service, which does not return a body. Utilize a logger to log the status code and the current thread, allowing us to determine whether we are using normal platform threads or virtual threads.",[669,117897,117899],{"className":4107,"code":117898,"language":4109,"meta":674,"style":674},"@GetMapping(\"/block/{seconds}\")\npublic String delay(@PathVariable int seconds) {\n ResponseEntity\u003CVoid> result = restClient.get()\n .uri(\"/delay/\" + seconds)\n .retrieve()\n .toBodilessEntity();\n\n log.info(\"{} on {}\", result.getStatusCode(), Thread.currentThread());\n\n return Thread.currentThread().toString();\n}\n",[676,117900,117901,117914,117932,117951,117967,117975,117983,117987,118012,118016,118030],{"__ignoreMap":674},[679,117902,117903,117905,117907,117909,117912],{"class":681,"line":682},[679,117904,4116],{"class":693},[679,117906,20852],{"class":685},[679,117908,745],{"class":693},[679,117910,117911],{"class":689},"\"/block/{seconds}\"",[679,117913,1339],{"class":693},[679,117915,117916,117918,117920,117923,117925,117927,117929],{"class":681,"line":790},[679,117917,6073],{"class":685},[679,117919,9289],{"class":693},[679,117921,117922],{"class":880},"delay",[679,117924,73246],{"class":693},[679,117926,92277],{"class":685},[679,117928,14925],{"class":685},[679,117930,117931],{"class":693}," seconds) {\n",[679,117933,117934,117937,117940,117943,117945,117947,117949],{"class":681,"line":892},[679,117935,117936],{"class":693}," ResponseEntity\u003C",[679,117938,117939],{"class":685},"Void",[679,117941,117942],{"class":693},"> result ",[679,117944,686],{"class":685},[679,117946,113761],{"class":693},[679,117948,7626],{"class":880},[679,117950,17545],{"class":693},[679,117952,117953,117955,117957,117959,117962,117964],{"class":681,"line":901},[679,117954,40148],{"class":693},[679,117956,4836],{"class":880},[679,117958,745],{"class":693},[679,117960,117961],{"class":689},"\"/delay/\"",[679,117963,3059],{"class":685},[679,117965,117966],{"class":693}," seconds)\n",[679,117968,117969,117971,117973],{"class":681,"line":909},[679,117970,40148],{"class":693},[679,117972,105458],{"class":880},[679,117974,17545],{"class":693},[679,117976,117977,117979,117981],{"class":681,"line":918},[679,117978,40148],{"class":693},[679,117980,114249],{"class":880},[679,117982,9317],{"class":693},[679,117984,117985],{"class":681,"line":935},[679,117986,889],{"emptyLinePlaceholder":797},[679,117988,117989,117992,117994,117996,117999,118002,118005,118008,118010],{"class":681,"line":944},[679,117990,117991],{"class":693}," log.",[679,117993,9415],{"class":880},[679,117995,745],{"class":693},[679,117997,117998],{"class":689},"\"{} on {}\"",[679,118000,118001],{"class":693},", result.",[679,118003,118004],{"class":880},"getStatusCode",[679,118006,118007],{"class":693},"(), Thread.",[679,118009,104838],{"class":880},[679,118011,9431],{"class":693},[679,118013,118014],{"class":681,"line":959},[679,118015,889],{"emptyLinePlaceholder":797},[679,118017,118018,118020,118022,118024,118026,118028],{"class":681,"line":964},[679,118019,21478],{"class":685},[679,118021,104835],{"class":693},[679,118023,104838],{"class":880},[679,118025,10541],{"class":693},[679,118027,14391],{"class":880},[679,118029,9317],{"class":693},[679,118031,118032],{"class":681,"line":977},[679,118033,996],{"class":693},[651,118035,118036,118037,118039,118040,118044],{},"If you run the application and perform a ",[676,118038,113821],{}," request to ",[812,118041,118042],{"href":118042,"rel":118043},"http://localhost:8080/httpbin/block/3",[816],", you should see the following result in the console. Upon closer inspection, you will notice that the application is running on the main thread. This behavior is consistent with previous versions of Spring Boot + Java.",[669,118046,118048],{"className":5851,"code":118047,"language":5853,"meta":674,"style":674},"2023-12-14T09:39:27.943-05:00 INFO 12176 --- [nio-8080-exec-1] d.d.h.HttpBinController : 200 OK on Thread[#38,http-nio-8080-exec-1,5,main]\n",[676,118049,118050],{"__ignoreMap":674},[679,118051,118052,118055,118057,118060,118062,118065,118067],{"class":681,"line":682},[679,118053,118054],{"class":880},"2023-12-14T09:39:27.943-05:00",[679,118056,18562],{"class":689},[679,118058,118059],{"class":931}," 12176",[679,118061,18568],{"class":931},[679,118063,118064],{"class":693}," [nio-8080-exec-1] d.d.h.HttpBinController ",[679,118066,2391],{"class":931},[679,118068,118069],{"class":693}," 200 OK on Thread[#38,http-nio-8080-exec-1,5,main]\n",[651,118071,118072,118073,118075],{},"Enabling Virtual Threads in your Spring Boot application couldn't be easier. Open the ",[676,118074,16242],{}," file and set the following property.",[669,118077,118079],{"className":76589,"code":118078,"language":35538,"meta":674,"style":674},"spring.threads.virtual.enabled=true\n",[676,118080,118081],{"__ignoreMap":674},[679,118082,118083,118086],{"class":681,"line":682},[679,118084,118085],{"class":685},"spring.threads.virtual.enabled",[679,118087,93485],{"class":693},[651,118089,118090],{},"If you rerun the application and check the console, you should see that we are now running on Virtual Threads.",[669,118092,118094],{"className":5851,"code":118093,"language":5853,"meta":674,"style":674},"2023-12-14T09:43:10.088-05:00 INFO 12418 --- [omcat-handler-0] d.d.h.HttpBinController : 200 OK on VirtualThread[#48,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1\n",[676,118095,118096],{"__ignoreMap":674},[679,118097,118098,118101,118103,118106,118108,118111,118113],{"class":681,"line":682},[679,118099,118100],{"class":880},"2023-12-14T09:43:10.088-05:00",[679,118102,18562],{"class":689},[679,118104,118105],{"class":931}," 12418",[679,118107,18568],{"class":931},[679,118109,118110],{"class":693}," [omcat-handler-0] d.d.h.HttpBinController ",[679,118112,2391],{"class":931},[679,118114,118115],{"class":693}," 200 OK on VirtualThread[#48,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1\n",[5909,118117,118119],{"id":118118},"virtual-threads-in-action-with-apache-benchmark","Virtual Threads in Action with Apache Benchmark",[651,118121,118122],{},"Now that you know how to enable Virtual Threads in a Spring Boot Application (it's quite trivial), let's explore how we can run some initial benchmarks to assess the potential performance improvements in our applications.",[651,118124,118125,118126,118131,118132,118136],{},"If you wish to conduct these initial benchmarks, you can utilize a tool like ",[812,118127,118130],{"href":118128,"rel":118129},"https://httpd.apache.org/docs/2.4/programs/ab.html",[816],"Apache Benchmark",", which comes pre-installed with macOS and is the reason I recommend it. If you are using a different operating system, there are various alternative tools available. To begin, return to your ",[812,118133,16242],{"href":118134,"rel":118135},"http://application.properties/",[816]," file and disable Virtual Threads.",[669,118138,118140],{"className":76589,"code":118139,"language":35538,"meta":674,"style":674},"spring.threads.virtual.enabled=false\n",[676,118141,118142],{"__ignoreMap":674},[679,118143,118144,118146],{"class":681,"line":682},[679,118145,118085],{"class":685},[679,118147,103241],{"class":693},[651,118149,118150],{},"Run the application and open up a terminal to use Apache Benchmark. This first test will send 10 requests that block for 3 seconds. As you would expect this test takes around 30 seconds to complete.",[651,118152,118153],{},[660,118154],{"alt":118155,"src":118156},"BenchMark Test 1","/images/blog/2023/12/14/10_requests.png",[651,118158,118159],{},[660,118160],{"alt":118161,"src":118162},"BenchMark Test 1 Results","/images/blog/2023/12/14/10_requests_results.png",[651,118164,118165,118166,118168],{},"Now, let's run the same test but handle multiple requests at a time. To enable concurrency in Apache Benchmark, you can use the ",[676,118167,14355],{}," argument followed by the number of concurrent connections. In this example, I will run the test with 2 concurrent connections. As a result, the test should take half the time and complete in approximately 15 seconds.",[651,118170,118171],{},[660,118172],{"alt":118173,"src":118174},"BenchMark Test 2","/images/blog/2023/12/14/10_2_requests.png",[651,118176,118177],{},[660,118178],{"alt":118179,"src":118180},"BenchMark Test 2 Results","/images/blog/2023/12/14/10_2_requests_results.png",[651,118182,118183],{},"We recently ran an example that utilized two threads simultaneously. This raises the question: \"How many threads can I run concurrently?\" The answer to this question depends on the machine you are using. In Java, a thread acts as a thin wrapper around a platform or operating system thread. Typically, you have one OS thread per core. Since my machine has 10 cores, I can theoretically run up to 10 threads concurrently.",[651,118185,118186],{},[660,118187],{"alt":118188,"src":118189},"Maximum Cores","/images/blog/2023/12/14/cores.png",[651,118191,118192],{},"That means if we run the following test it should take somewhere around 3 seconds.",[651,118194,118195],{},[660,118196],{"alt":118197,"src":118198},"BenchMark Test 3","/images/blog/2023/12/14/10_10_requests.png",[651,118200,118201],{},"In this next example, let's explicitly set the maximum number of threads we can use at once to 10.",[669,118203,118205],{"className":5851,"code":118204,"language":5853,"meta":674,"style":674},"# tomcat threads defaults to 200, 0 is unlimited\nserver.tomcat.threads.max=10\n\nspring.threads.virtual.enabled=false\n",[676,118206,118207,118212,118220,118224],{"__ignoreMap":674},[679,118208,118209],{"class":681,"line":682},[679,118210,118211],{"class":1400},"# tomcat threads defaults to 200, 0 is unlimited\n",[679,118213,118214,118217],{"class":681,"line":790},[679,118215,118216],{"class":880},"server.tomcat.threads.max",[679,118218,118219],{"class":689},"=10\n",[679,118221,118222],{"class":681,"line":892},[679,118223,889],{"emptyLinePlaceholder":797},[679,118225,118226,118228,118230],{"class":681,"line":901},[679,118227,118085],{"class":880},[679,118229,686],{"class":689},[679,118231,2649],{"class":931},[651,118233,118234],{},"Know that the maximum number of concurrent requests that can be handled at one time. How long would you expect the following benchmark to take?",[669,118236,118238],{"className":5851,"code":118237,"language":5853,"meta":674,"style":674},"ab -n 60 -c 20 http://localhost:8080/httpbin/block/3\n",[676,118239,118240],{"__ignoreMap":674},[679,118241,118242,118245,118248,118251,118254,118257],{"class":681,"line":682},[679,118243,118244],{"class":880},"ab",[679,118246,118247],{"class":931}," -n",[679,118249,118250],{"class":931}," 60",[679,118252,118253],{"class":931}," -c",[679,118255,118256],{"class":931}," 20",[679,118258,118259],{"class":689}," http://localhost:8080/httpbin/block/3\n",[651,118261,118262],{},"Your first guess might be 9 seconds, but that would be incorrect. This is because even though we are sending 20 concurrent requests, our application can only handle 10 at a time. The first 10 requests come in and the system processes them. Since these are blocking requests, the thread must complete before it can handle other requests. If the application handles 10 requests every 3 seconds, this test will take somewhere around 18 seconds (6x3=18).",[651,118264,118265],{},[660,118266],{"alt":118267,"src":118268},"BenchMark Test 4","/images/blog/2023/12/14/60_20_requests.png",[5909,118270,118272],{"id":118271},"where-virtual-threads-dont-make-sense","Where Virtual Threads Don’t Make Sense",[651,118274,118275],{},"We know that Virtual Threads make sense when our applications are performing some type of blocking operation. Where it doesn’t make sense is in a purely CPU intensive application. Another scenario where they don’t make sense is if you’re already using asynchronous programming. In either scenario you can enable them but you won’t see any performance benefits.",[4542,118277,9042],{"id":9041},[651,118279,118280],{},"This blog post discusses the concept of Virtual Threads in Java, their significance for Spring Boot developers, and how they can enhance the performance of certain types of Spring Boot applications. It explains the challenges of asynchronous programming, introduces Virtual Threads as a solution, and provides an example of using Virtual Threads in a Spring Boot application. The post also includes benchmarks to demonstrate the performance improvement achieved with Virtual Threads and highlights the scenarios where Virtual Threads are beneficial.",[651,118282,118283,118284,118286],{},"Thank You,",[41107,118285],{},"\nHappy coding!",[786,118288,118289],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":118291},[118292,118295,118300],{"id":117583,"depth":790,"text":117584,"children":118293},[118294],{"id":117615,"depth":892,"text":117616},{"id":117638,"depth":790,"text":117639,"children":118296},[118297,118298,118299],{"id":117651,"depth":892,"text":117652},{"id":118118,"depth":892,"text":118119},{"id":118271,"depth":892,"text":118272},{"id":9041,"depth":790,"text":9042},"In this article, we will discuss Virtual Threads in Java, their significance for Spring Boot developers, and how they can enhance the performance of certain types of Spring Boot applications.",{"slug":118303,"date":118304,"published":797,"author":798,"tags":118305,"cover":118306,"video":118307,"github":118308,"keywords":118309},"virtual-threads-spring-boot","2023-12-14T17:00:00.000Z",[36422,7077],"spring-boot-virtual-threads.png","https://www.youtube.com/embed/THavIYnlwck","https://github.com/danvega/hello-virtual-threads/","Spring Framework, Spring Boot, Java, virtual threads, Project Loom, virtual threads java, spring virtual threads, virtual threads spring boot tutorial, spring boot 3 2 virtual threads, Spring Boot 3.2, Spring Boot Virtual Threads",{"title":117,"description":118301},"blog/2023/12/14/virtual-threads-spring-boot","eJQpIdvLdDXLPWaZoCl_obDty551UZJs189elmBZYYs",{"id":118314,"title":114,"body":118315,"description":119996,"extension":793,"meta":119997,"navigation":797,"path":115,"seo":120003,"stem":120004,"__hash__":120005},"content/blog/2023/12/20/spring-boot-3-2.md",{"type":648,"value":118316,"toc":119971},[118317,118325,118354,118358,118361,118364,118367,118370,118373,118376,118380,118383,118387,118393,118395,118397,118403,118405,118407,118413,118415,118420,118424,118432,118435,118439,118442,118446,118449,118452,118458,118461,118467,118470,118474,118477,118480,118484,118493,118496,118502,118506,118514,118517,118520,118523,118526,118533,118925,118934,118937,118941,118944,118947,118950,118953,118957,118960,118963,118966,118975,118978,118981,118984,119047,119050,119056,119063,119285,119292,119295,119299,119302,119305,119312,119498,119501,119510,119514,119517,119520,119523,119528,119532,119535,119552,119559,119794,119798,119827,119877,119883,119961,119963,119966,119968],[651,118318,118319,118320,118324],{},"This article highlights some of the new features in the latest releases of Spring Framework 6.1 and Spring Boot 3.2. The new release includes support for Java 21, so if you want to leverage all the new features in the JDK, you should consider upgrading. For a comprehensive overview of the changes, new features, and deprecations, I recommend referring to the ",[812,118321,95704],{"href":118322,"rel":118323},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes",[816],". In this release, I will focus on three main areas:",[5316,118326,118327,118340,118351],{},[5332,118328,118329,118330],{},"Runtime Efficiency\n",[5316,118331,118332,118334,118337],{},[5332,118333,117624],{},[5332,118335,118336],{},"Project CRaC (Coordinated Restore at Checkpoint)",[5332,118338,118339],{},"Project Leydon",[5332,118341,118342,118343],{},"New Client Abstractions\n",[5316,118344,118345,118348],{},[5332,118346,118347],{},"JDBC Client",[5332,118349,118350],{},"REST Client",[5332,118352,118353],{},"Observability Improvements",[4542,118355,118357],{"id":118356},"runtime-efficiency","Runtime Efficiency",[651,118359,118360],{},"I have had the opportunity to travel to conferences and meet with customers, discussing the impressive features of Spring Boot 3 and beyond. One of the prominent themes in Spring Boot 3 is Runtime Efficiency. But what does that really mean, and which features are contributing to it?",[651,118362,118363],{},"Runtime efficiency is focused on addressing the challenges that arise when dealing with various workloads in production. Traditionally, a typical Java-based application would be a long-running process designed to handle high throughput. In this scenario, the JVM excels at optimizing for such workloads. It identifies \"hot spots\" or frequently executed areas of the code and compiles them for further optimizations.",[651,118365,118366],{},"Today, we are developing a wide range of applications with diverse requirements. Some of these applications demand lower memory usage and faster startup times. For others, which involve blocking requests like communication with a database or an HTTP service, scalability is essential.",[651,118368,118369],{},"Java and Spring now provide solutions for building various types of applications. In Spring Boot 3.0, we introduced Ahead-of-Time (AoT) compilation as a prerequisite for creating native images with GraalVM. In Spring Boot 3.2, we are introducing support for Virtual Threads and Project CRaC (Coordinated Restore at Checkpoint).",[5909,118371,117624],{"id":118372},"virtual-threads",[651,118374,118375],{},"Java 21 might be one of the most significant releases we have seen in Java, ever. I don’t say that lightly because I know there have been some amazing releases over the years. This release however was the start of the Project Loom story with Virtual Threads being finalized and bringing us a solution to the scalability issues the thread-per-request model introduces.",[5909,118377,118379],{"id":118378},"threads-in-java","Threads in Java",[651,118381,118382],{},"In Java, each statement inside a method executes in a thread. Java is Multi-threaded which means more than one thread can be executing a given set of instructions at once. Threads are fundamental to concurrent programming and allow a Java program to perform multiple tasks or operations concurrently, making efficient use of available resources, such as CPU cores.",[651,118384,117590,118385,117594],{},[676,118386,117593],{},[651,118388,118389],{},[660,118390],{"alt":118391,"src":118392},"java_threads.png","/images/blog/2023/12/20/java_threads.png",[651,118394,117603],{},[651,118396,117606],{},[651,118398,118399],{},[660,118400],{"alt":118401,"src":118402},"scaling_options.png","/images/blog/2023/12/20//scaling_options.png",[5909,118404,117616],{"id":117615},[651,118406,117619],{},[651,118408,118409],{},[660,118410],{"alt":118411,"src":118412},"virtual_threads.png","/images/blog/2023/12/20//virtual_threads.png",[651,118414,117628],{},[651,118416,117631,118417,664],{},[812,118418,117635],{"href":104628,"rel":118419},[816],[5909,118421,118423],{"id":118422},"virtual-threads-in-spring-boot-example","Virtual Threads in Spring Boot Example",[651,118425,118426,118427,118431],{},"If you're interested in learning more about Virtual Threads in Spring Boot, you can check out ",[812,118428,109096],{"href":118429,"rel":118430},"https://www.danvega.dev/blog/virtual-threads-spring-boot",[816],", which continues the conversation. In this example, you can create a new Spring Boot application, enable virtual threads, and run benchmark tests against it.",[5988,118433],{"id":118434},"THavIYnlwck",[4542,118436,118438],{"id":118437},"what-is-project-crac-coordinated-restore-at-checkpoint","What is Project CRaC (Coordinated Restore at Checkpoint)",[651,118440,118441],{},"In the realm of runtime efficiency, Spring Boot 3.2 introduces an exciting JDK project called Coordinated Restore at Checkpoint (CRaC). With CRaC, projects on Linux can experience faster time to first transaction and achieve optimal code speed with reduced time and resource consumption. By capturing a snapshot of the fully warmed up Java process, CRaC enables the launch of multiple JVMs from this state. It's important to note that not all existing Java programs can seamlessly utilize CRaC, as explicit checkpointing and restoration of resources using the CRaC API are required. However, frameworks like Spring offer native support for CRaC checkpointing right out of the box.",[5909,118443,118445],{"id":118444},"why-restore-from-a-checkpoint","Why Restore from a Checkpoint?",[651,118447,118448],{},"When a Java application runs, the JVM goes through a process of loading, initializing, and optimizing the code used by the application. However, each time the application is restarted, this process needs to be repeated, resulting in time and resource consumption. What if you could eliminate this initialization process and therefore remove the application's startup time and resource consumption?",[651,118450,118451],{},"One of the primary advantages of CRaC is its ability to create checkpoints within the application's execution flow. This allows the application to go through its initialization phase and, once warmed up and ready to go, a checkpoint can be taken. The checkpoint can then be used the next time the application needs to start up, eliminating the need to warm up the execution environment again.",[651,118453,118454],{},[660,118455],{"alt":118456,"src":118457},"Checkpoint","/images/blog/2023/12/20/checkpoint.png",[651,118459,118460],{},"I would encourage you to try this out in your applications and see what kind of performance gains you get. In many scenarios, you can expect up to two orders of magnitude faster performance. That is something to be really excited about!",[651,118462,118463],{},[660,118464],{"alt":118465,"src":118466},"Time to first Operation","/images/blog/2023/12/20/time_to_first_operation.png",[651,118468,118469],{},"When comparing this to something like AOT, there are several advantages. First, you don't need to add hints to the AOT compiler. However, the major advantage, in my opinion, is that natively compiled applications make a closed world assumption at runtime, whereas applications using Project CRaC can still leverage all the dynamic capabilities of the JVM.",[5909,118471,118473],{"id":118472},"spring-boot-project-crac-example","Spring Boot + Project CRaC Example",[651,118475,118476],{},"I mentioned this earlier, but this feature is built on top of a feature in Linux. If you're running Linux locally, you can get this working fairly simply. If you aren't, you need to run the application in a container. The important thing to note is that this is not something you're going to deal with during development. This is a Runtime Efficiency feature and something we can work together with our DevOps teams to implement in our CI/CD process. If you want to see this in action in a Spring Boot 3.2 application, you can check out the video I made with my friend DaShaun below.",[5988,118478],{"id":118479},"sVXUx_Y4hRU",[5909,118481,118483],{"id":118482},"project-leydon-and-class-data-sharing","Project Leydon and Class Data Sharing",[651,118485,118486,118487,118492],{},"This is fairly new project in the ",[812,118488,118491],{"href":118489,"rel":118490},"https://openjdk.org/projects/leyden/",[816],"OpenJDK"," and its initiative is to improve the startup and warmup of Java applications. The Spring Team has already done some initial work at looking at Project Leydon in the JDK and where it might be useful to Spring Developers.",[651,118494,118495],{},"Through that exploration they found some interesting discoveries of the JDK’s CDS (”Class Data Sharing”) feature and it has materialized into a new feature that shipping in Spring Framework 6.1 and Spring Boot 3.2. If you’re interested in learning more check out the blog post below.",[651,118497,118498],{},[812,118499,118500],{"href":118500,"rel":118501},"https://spring.io/blog/2023/12/04/cds-with-spring-framework-6-1",[816],[4542,118503,118505],{"id":118504},"client-abstractions","Client Abstractions",[651,118507,118508,118509,51393,118511,118513],{},"Spring has simplified complex tasks by abstracting the low-level communication layer into an easy-to-use API. This includes interacting with a database through the JDBC Template or Spring Data, as well as communicating with an external service using the ",[676,118510,23551],{},[676,118512,109920],{}," for HTTP requests. In Spring Boot 3.2, two new client abstractions have been introduced to further simplify these processes.",[5909,118515,118347],{"id":118516},"jdbc-client",[651,118518,118519],{},"Interacting with databases, reading and persisting data in Java has historically been complex. Factors like building JDBC URLs, managing database connections, and dealing with real-world application concerns such as connection pools need to be considered. Fortunately, Spring simplifies these tasks through its abstractions. One such abstraction is the JDBC template in Spring.",[651,118521,118522],{},"The JDBC template is a solid abstraction that simplifies database interaction. However, it can become verbose when building simple CRUD services based on resources. It requires a deep understanding of methods for communicating with a database, such as row mappers and column to field mapping, which can be tricky.",[651,118524,118525],{},"Despite these complexities, one main advantage is the complete control over SQL, which developers highly appreciate. This is where the newest addition, the JDBC client, comes in. It offers a fluent API that is easy to understand and read. One exciting feature of the JDBC client is its auto-configuration in Spring Boot 3.2. This means that we can simply request a bean in our application and get an instance of it without any hassle!",[651,118527,118528,118529,118532],{},"Here is an example of a Repository class that contains all of the CRUD methods for reading and persisting data to a database. Note that an instance of the ",[676,118530,118531],{},"JdbcClient"," is autowired in using constructor injection, and the code is made more readable thanks to the fluent API.",[669,118534,118536],{"className":4107,"code":118535,"language":4109,"meta":674,"style":674},"@Repository\npublic class PostRepository {\n\n private final JdbcClient jdbcClient;\n\n public PostRepository(JdbcClient jdbcClient) {\n this.jdbcClient = jdbcClient;\n }\n\n public List\u003CPost> findAll() {\n return jdbcClient.sql(\"SELECT * FROM Post\")\n .query(Post.class)\n .list();\n }\n\n public Optional\u003CPost> findById(String id) {\n return jdbcClient.sql(\"SELECT * FROM Post WHERE id = :id\")\n .param(\"id\", id)\n .query(Post.class)\n .optional();\n }\n\n public void create(Post post) {\n jdbcClient.sql(\"INSERT INTO Post(id,user_id,title,body) values(?,?,?,?)\")\n .params(List.of(post.id(), post.userId(), post.title(), post.body()))\n .update();\n }\n\n public void update(Post post, String id) {\n jdbcClient.sql(\"update Post set user_id = ?, title = ?, body = ? where id = ?\")\n .params(List.of(post.userId(), post.title(), post.body(), id))\n .update();\n }\n\n public void delete(String id) {\n jdbcClient.sql(\"delete from Post where id = :id\")\n .param(\"id\", id)\n .update();\n }\n\n}\n",[676,118537,118538,118544,118554,118558,118566,118570,118582,118592,118596,118600,118614,118629,118637,118645,118649,118653,118671,118686,118698,118706,118714,118718,118722,118736,118750,118779,118787,118791,118795,118813,118826,118850,118858,118862,118866,118880,118893,118905,118913,118917,118921],{"__ignoreMap":674},[679,118539,118540,118542],{"class":681,"line":682},[679,118541,4116],{"class":693},[679,118543,87182],{"class":685},[679,118545,118546,118548,118550,118552],{"class":681,"line":790},[679,118547,6073],{"class":685},[679,118549,4512],{"class":685},[679,118551,93243],{"class":880},[679,118553,884],{"class":693},[679,118555,118556],{"class":681,"line":892},[679,118557,889],{"emptyLinePlaceholder":797},[679,118559,118560,118562,118564],{"class":681,"line":901},[679,118561,9232],{"class":685},[679,118563,12768],{"class":685},[679,118565,116367],{"class":693},[679,118567,118568],{"class":681,"line":909},[679,118569,889],{"emptyLinePlaceholder":797},[679,118571,118572,118574,118576,118578,118580],{"class":681,"line":918},[679,118573,6089],{"class":685},[679,118575,93243],{"class":880},[679,118577,116380],{"class":693},[679,118579,116383],{"class":2099},[679,118581,4390],{"class":693},[679,118583,118584,118586,118588,118590],{"class":681,"line":935},[679,118585,7862],{"class":931},[679,118587,116392],{"class":693},[679,118589,686],{"class":685},[679,118591,116397],{"class":693},[679,118593,118594],{"class":681,"line":944},[679,118595,985],{"class":693},[679,118597,118598],{"class":681,"line":959},[679,118599,889],{"emptyLinePlaceholder":797},[679,118601,118602,118604,118606,118608,118610,118612],{"class":681,"line":964},[679,118603,6089],{"class":685},[679,118605,87217],{"class":693},[679,118607,5105],{"class":685},[679,118609,20881],{"class":693},[679,118611,34142],{"class":880},[679,118613,2667],{"class":693},[679,118615,118616,118618,118620,118622,118624,118627],{"class":681,"line":977},[679,118617,9444],{"class":685},[679,118619,116433],{"class":693},[679,118621,673],{"class":880},[679,118623,745],{"class":693},[679,118625,118626],{"class":689},"\"SELECT * FROM Post\"",[679,118628,1339],{"class":693},[679,118630,118631,118633,118635],{"class":681,"line":982},[679,118632,73482],{"class":693},[679,118634,115846],{"class":880},[679,118636,116451],{"class":693},[679,118638,118639,118641,118643],{"class":681,"line":988},[679,118640,73482],{"class":693},[679,118642,7623],{"class":880},[679,118644,9317],{"class":693},[679,118646,118647],{"class":681,"line":993},[679,118648,985],{"class":693},[679,118650,118651],{"class":681,"line":2129},[679,118652,889],{"emptyLinePlaceholder":797},[679,118654,118655,118657,118659,118661,118663,118665,118667,118669],{"class":681,"line":2140},[679,118656,6089],{"class":685},[679,118658,109520],{"class":693},[679,118660,5105],{"class":685},[679,118662,20881],{"class":693},[679,118664,87658],{"class":880},[679,118666,11400],{"class":693},[679,118668,11341],{"class":2099},[679,118670,4390],{"class":693},[679,118672,118673,118675,118677,118679,118681,118684],{"class":681,"line":2145},[679,118674,9444],{"class":685},[679,118676,116433],{"class":693},[679,118678,673],{"class":880},[679,118680,745],{"class":693},[679,118682,118683],{"class":689},"\"SELECT * FROM Post WHERE id = :id\"",[679,118685,1339],{"class":693},[679,118687,118688,118690,118692,118694,118696],{"class":681,"line":2154},[679,118689,73482],{"class":693},[679,118691,116513],{"class":880},[679,118693,745],{"class":693},[679,118695,93727],{"class":689},[679,118697,114018],{"class":693},[679,118699,118700,118702,118704],{"class":681,"line":2159},[679,118701,73482],{"class":693},[679,118703,115846],{"class":880},[679,118705,116451],{"class":693},[679,118707,118708,118710,118712],{"class":681,"line":2164},[679,118709,73482],{"class":693},[679,118711,116534],{"class":880},[679,118713,9317],{"class":693},[679,118715,118716],{"class":681,"line":3134},[679,118717,985],{"class":693},[679,118719,118720],{"class":681,"line":3139},[679,118721,889],{"emptyLinePlaceholder":797},[679,118723,118724,118726,118728,118730,118732,118734],{"class":681,"line":3144},[679,118725,6089],{"class":685},[679,118727,6095],{"class":685},[679,118729,49468],{"class":880},[679,118731,114052],{"class":693},[679,118733,5100],{"class":2099},[679,118735,4390],{"class":693},[679,118737,118738,118741,118743,118745,118748],{"class":681,"line":3149},[679,118739,118740],{"class":693}," jdbcClient.",[679,118742,673],{"class":880},[679,118744,745],{"class":693},[679,118746,118747],{"class":689},"\"INSERT INTO Post(id,user_id,title,body) values(?,?,?,?)\"",[679,118749,1339],{"class":693},[679,118751,118752,118754,118756,118758,118760,118762,118764,118766,118769,118771,118773,118775,118777],{"class":681,"line":3169},[679,118753,73482],{"class":693},[679,118755,116590],{"class":880},[679,118757,116593],{"class":693},[679,118759,16672],{"class":880},[679,118761,116598],{"class":693},[679,118763,11341],{"class":880},[679,118765,116603],{"class":693},[679,118767,118768],{"class":880},"userId",[679,118770,116603],{"class":693},[679,118772,11750],{"class":880},[679,118774,116603],{"class":693},[679,118776,3006],{"class":880},[679,118778,104226],{"class":693},[679,118780,118781,118783,118785],{"class":681,"line":3185},[679,118782,73482],{"class":693},[679,118784,82237],{"class":880},[679,118786,9317],{"class":693},[679,118788,118789],{"class":681,"line":3194},[679,118790,985],{"class":693},[679,118792,118793],{"class":681,"line":3199},[679,118794,889],{"emptyLinePlaceholder":797},[679,118796,118797,118799,118801,118803,118805,118807,118809,118811],{"class":681,"line":3212},[679,118798,6089],{"class":685},[679,118800,6095],{"class":685},[679,118802,72244],{"class":880},[679,118804,114052],{"class":693},[679,118806,5100],{"class":2099},[679,118808,20006],{"class":693},[679,118810,11341],{"class":2099},[679,118812,4390],{"class":693},[679,118814,118815,118817,118819,118821,118824],{"class":681,"line":3217},[679,118816,118740],{"class":693},[679,118818,673],{"class":880},[679,118820,745],{"class":693},[679,118822,118823],{"class":689},"\"update Post set user_id = ?, title = ?, body = ? where id = ?\"",[679,118825,1339],{"class":693},[679,118827,118828,118830,118832,118834,118836,118838,118840,118842,118844,118846,118848],{"class":681,"line":3222},[679,118829,73482],{"class":693},[679,118831,116590],{"class":880},[679,118833,116593],{"class":693},[679,118835,16672],{"class":880},[679,118837,116598],{"class":693},[679,118839,118768],{"class":880},[679,118841,116603],{"class":693},[679,118843,11750],{"class":880},[679,118845,116603],{"class":693},[679,118847,3006],{"class":880},[679,118849,116746],{"class":693},[679,118851,118852,118854,118856],{"class":681,"line":3227},[679,118853,73482],{"class":693},[679,118855,82237],{"class":880},[679,118857,9317],{"class":693},[679,118859,118860],{"class":681,"line":3232},[679,118861,985],{"class":693},[679,118863,118864],{"class":681,"line":3499},[679,118865,889],{"emptyLinePlaceholder":797},[679,118867,118868,118870,118872,118874,118876,118878],{"class":681,"line":3509},[679,118869,6089],{"class":685},[679,118871,6095],{"class":685},[679,118873,92272],{"class":880},[679,118875,11400],{"class":693},[679,118877,11341],{"class":2099},[679,118879,4390],{"class":693},[679,118881,118882,118884,118886,118888,118891],{"class":681,"line":3516},[679,118883,118740],{"class":693},[679,118885,673],{"class":880},[679,118887,745],{"class":693},[679,118889,118890],{"class":689},"\"delete from Post where id = :id\"",[679,118892,1339],{"class":693},[679,118894,118895,118897,118899,118901,118903],{"class":681,"line":3531},[679,118896,73482],{"class":693},[679,118898,116513],{"class":880},[679,118900,745],{"class":693},[679,118902,93727],{"class":689},[679,118904,114018],{"class":693},[679,118906,118907,118909,118911],{"class":681,"line":3536},[679,118908,73482],{"class":693},[679,118910,82237],{"class":880},[679,118912,9317],{"class":693},[679,118914,118915],{"class":681,"line":3541},[679,118916,985],{"class":693},[679,118918,118919],{"class":681,"line":3546},[679,118920,889],{"emptyLinePlaceholder":797},[679,118922,118923],{"class":681,"line":3551},[679,118924,996],{"class":693},[651,118926,118927,118928,118933],{},"If you’re interested in diving deeper into the JDBC Client you can check out ",[812,118929,118932],{"href":118930,"rel":118931},"https://www.danvega.dev/blog/spring-jdbc-client",[816],"this blog post"," or watch the video below.",[5988,118935],{"id":118936},"JBu5GibEJ4k",[5909,118938,118940],{"id":118939},"jdbc-client-vs-spring-data","JDBC Client vs Spring Data",[651,118942,118943],{},"After discussing the new JDBC Client in Spring Boot 3.2, I was asked about the difference between the JDBC Client and Spring Data JDBC. This presented a good opportunity to explore the various options available in Java and Spring for accessing and persisting data to a database.",[651,118945,118946],{},"To illustrate this, I created an example where we connect to a database in Java and explained the origins of the original JDBC Template abstraction in Spring. Then, I demonstrated how to access the same database in Spring using the JDBC Template. While discussing some of the issues with the JDBC Template, I introduced the new JDBC Client in Spring Boot 3.2 and highlighted how its simple and fluent API simplifies database access.",[651,118948,118949],{},"Finally, we delved into Spring Data and how the repository pattern can handle the mundane CRUD functionality, freeing you to focus on your business requirements.",[5988,118951],{"id":118952},"qLDrfebeXS0",[5909,118954,118956],{"id":118955},"multiple-datasources-in-spring","Multiple DataSources in Spring",[651,118958,118959],{},"Another question that arose with the new JDBC Client in Spring Boot 3.2 was whether it is possible to use multiple JDBC Clients when working with multiple DataSources in Spring. I found this to be a great question and a perfect opportunity to discuss how a DataSource and JDBC Client are configured for you in Spring.",[5988,118961],{"id":118962},"ZKYFGuukhT4",[5909,118964,118350],{"id":118965},"rest-client",[651,118967,118968,118969,118974],{},"Over the years, Spring has offered various solutions for dealing with the complexities of working with web services, particularly RESTful services. In Spring Framework 3.0, the ",[812,118970,118973],{"href":118971,"rel":118972},"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html",[816],"Rest Template"," was introduced as a client for making synchronous HTTP requests. This API simplified the interaction with web services by abstracting the underlying HTTP connection, making it easier for developers to perform HTTP operations and parse responses. However, as requirements grew, so did the API, eventually making it more difficult to use.",[651,118976,118977],{},"In Spring Framework 5, Spring WebFlux was introduced as a way to create fully asynchronous and non-blocking applications. Alongside this release, the new Web Client was introduced to address the limitations of the RestTemplate in a non-blocking and reactive programming environment. Drawing from the lessons learned with the RestTemplate, the Spring team improved upon it with a simple-to-learn, fluent API.",[651,118979,118980],{},"Developers really enjoyed the easy to use fluent API of the Web Client. So much that they were bringing in the reactive stack to their blocking MVC applications just to take advantage of the Web Client. Fast forward to Spring Boot 3.2 and the new Rest Client offers a similar experience in imperative style applications.",[651,118982,118983],{},"To get started with rest client you can create an instance using the Rest Client Builder and setting the base url for your service.",[669,118985,118987],{"className":4107,"code":118986,"language":4109,"meta":674,"style":674},"private final RestClient restClient;\n\npublic PostService(RestClient.Builder builder) {\n this.restClient = builder\n .baseUrl(\"https://jsonplaceholder.typicode.com/\")\n .build();\n}\n",[676,118988,118989,118997,119001,119010,119022,119035,119043],{"__ignoreMap":674},[679,118990,118991,118993,118995],{"class":681,"line":682},[679,118992,21301],{"class":685},[679,118994,12768],{"class":685},[679,118996,113669],{"class":693},[679,118998,118999],{"class":681,"line":790},[679,119000,889],{"emptyLinePlaceholder":797},[679,119002,119003,119005,119007],{"class":681,"line":892},[679,119004,6073],{"class":685},[679,119006,23189],{"class":880},[679,119008,119009],{"class":693},"(RestClient.Builder builder) {\n",[679,119011,119012,119014,119017,119019],{"class":681,"line":901},[679,119013,27825],{"class":931},[679,119015,119016],{"class":693},".restClient ",[679,119018,686],{"class":685},[679,119020,119021],{"class":693}," builder\n",[679,119023,119024,119026,119028,119030,119033],{"class":681,"line":909},[679,119025,40148],{"class":693},[679,119027,100528],{"class":880},[679,119029,745],{"class":693},[679,119031,119032],{"class":689},"\"https://jsonplaceholder.typicode.com/\"",[679,119034,1339],{"class":693},[679,119036,119037,119039,119041],{"class":681,"line":918},[679,119038,40148],{"class":693},[679,119040,23612],{"class":880},[679,119042,9317],{"class":693},[679,119044,119045],{"class":681,"line":935},[679,119046,996],{"class":693},[651,119048,119049],{},"As a developer, one of the features that I really appreciate about the new Rest Client is how intuitive its API is. When I type \"rest client dot\", I am presented with sensible methods like get, post, put, and delete. This eliminates any confusion I had about which overloaded method to choose from, simplifying everything for me.",[651,119051,119052],{},[660,119053],{"alt":119054,"src":119055},"REST Client Fluent API","/images/blog/2023/12/20/rest_client_dot.png",[651,119057,119058,119059,119062],{},"After calling the ",[676,119060,119061],{},"get()"," method, we can use the fluent API to complete our call. As you can see from the methods below, this is very readable, even for someone who has never used it before.",[669,119064,119066],{"className":4107,"code":119065,"language":4109,"meta":674,"style":674},"@Service\npublic class PostService {\n\n private final RestClient restClient;\n\n public PostService(RestClient.Builder builder) {\n this.restClient = builder\n .baseUrl(\"https://jsonplaceholder.typicode.com/\")\n .build();\n }\n\n public List\u003CPost> findAll() {\n return restClient.get()\n .uri(\"/posts\")\n .retrieve()\n .body(new ParameterizedTypeReference\u003C>(){});\n }\n\n public ResponseEntity\u003CPost> findById(Long id) {\n return restClient.get()\n .uri(\"/posts/{id}\", id)\n .retrieve()\n .toEntity(Post.class);\n }\n\n}\n",[676,119067,119068,119074,119084,119088,119096,119100,119112,119122,119134,119142,119146,119150,119164,119174,119186,119194,119207,119211,119215,119234,119244,119256,119264,119273,119277,119281],{"__ignoreMap":674},[679,119069,119070,119072],{"class":681,"line":682},[679,119071,4116],{"class":693},[679,119073,9486],{"class":685},[679,119075,119076,119078,119080,119082],{"class":681,"line":790},[679,119077,6073],{"class":685},[679,119079,4512],{"class":685},[679,119081,23189],{"class":880},[679,119083,884],{"class":693},[679,119085,119086],{"class":681,"line":892},[679,119087,889],{"emptyLinePlaceholder":797},[679,119089,119090,119092,119094],{"class":681,"line":901},[679,119091,9232],{"class":685},[679,119093,12768],{"class":685},[679,119095,113669],{"class":693},[679,119097,119098],{"class":681,"line":909},[679,119099,889],{"emptyLinePlaceholder":797},[679,119101,119102,119104,119106,119108,119110],{"class":681,"line":918},[679,119103,6089],{"class":685},[679,119105,23189],{"class":880},[679,119107,117853],{"class":693},[679,119109,90934],{"class":2099},[679,119111,4390],{"class":693},[679,119113,119114,119116,119118,119120],{"class":681,"line":935},[679,119115,7862],{"class":931},[679,119117,119016],{"class":693},[679,119119,686],{"class":685},[679,119121,119021],{"class":693},[679,119123,119124,119126,119128,119130,119132],{"class":681,"line":944},[679,119125,73482],{"class":693},[679,119127,100528],{"class":880},[679,119129,745],{"class":693},[679,119131,119032],{"class":689},[679,119133,1339],{"class":693},[679,119135,119136,119138,119140],{"class":681,"line":959},[679,119137,73482],{"class":693},[679,119139,23612],{"class":880},[679,119141,9317],{"class":693},[679,119143,119144],{"class":681,"line":964},[679,119145,985],{"class":693},[679,119147,119148],{"class":681,"line":977},[679,119149,889],{"emptyLinePlaceholder":797},[679,119151,119152,119154,119156,119158,119160,119162],{"class":681,"line":982},[679,119153,6089],{"class":685},[679,119155,87217],{"class":693},[679,119157,5105],{"class":685},[679,119159,20881],{"class":693},[679,119161,34142],{"class":880},[679,119163,2667],{"class":693},[679,119165,119166,119168,119170,119172],{"class":681,"line":988},[679,119167,9444],{"class":685},[679,119169,113761],{"class":693},[679,119171,7626],{"class":880},[679,119173,17545],{"class":693},[679,119175,119176,119178,119180,119182,119184],{"class":681,"line":993},[679,119177,73482],{"class":693},[679,119179,4836],{"class":880},[679,119181,745],{"class":693},[679,119183,110889],{"class":689},[679,119185,1339],{"class":693},[679,119187,119188,119190,119192],{"class":681,"line":2129},[679,119189,73482],{"class":693},[679,119191,105458],{"class":880},[679,119193,17545],{"class":693},[679,119195,119196,119198,119200,119202,119204],{"class":681,"line":2140},[679,119197,73482],{"class":693},[679,119199,3006],{"class":880},[679,119201,745],{"class":693},[679,119203,8930],{"class":685},[679,119205,119206],{"class":693}," ParameterizedTypeReference\u003C>(){});\n",[679,119208,119209],{"class":681,"line":2145},[679,119210,985],{"class":693},[679,119212,119213],{"class":681,"line":2154},[679,119214,889],{"emptyLinePlaceholder":797},[679,119216,119217,119219,119222,119224,119226,119228,119230,119232],{"class":681,"line":2159},[679,119218,6089],{"class":685},[679,119220,119221],{"class":693}," ResponseEntity\u003C",[679,119223,5105],{"class":685},[679,119225,20881],{"class":693},[679,119227,87658],{"class":880},[679,119229,11338],{"class":693},[679,119231,11341],{"class":2099},[679,119233,4390],{"class":693},[679,119235,119236,119238,119240,119242],{"class":681,"line":2164},[679,119237,9444],{"class":685},[679,119239,113761],{"class":693},[679,119241,7626],{"class":880},[679,119243,17545],{"class":693},[679,119245,119246,119248,119250,119252,119254],{"class":681,"line":3134},[679,119247,73482],{"class":693},[679,119249,4836],{"class":880},[679,119251,745],{"class":693},[679,119253,114015],{"class":689},[679,119255,114018],{"class":693},[679,119257,119258,119260,119262],{"class":681,"line":3139},[679,119259,73482],{"class":693},[679,119261,105458],{"class":880},[679,119263,17545],{"class":693},[679,119265,119266,119268,119271],{"class":681,"line":3144},[679,119267,73482],{"class":693},[679,119269,119270],{"class":880},"toEntity",[679,119272,114035],{"class":693},[679,119274,119275],{"class":681,"line":3149},[679,119276,985],{"class":693},[679,119278,119279],{"class":681,"line":3169},[679,119280,889],{"emptyLinePlaceholder":797},[679,119282,119283],{"class":681,"line":3185},[679,119284,996],{"class":693},[651,119286,119287,119288,118933],{},"If you’re interested in learning more about the Rest Client you check out ",[812,119289,119291],{"href":117773,"rel":119290},[816],"this post",[5988,119293],{"id":119294},"UDNrJAvKc0k",[5909,119296,119298],{"id":119297},"http-interface-clients","Http Interface Clients",[651,119300,119301],{},"Spring Framework 6 and Spring Boot 3 have introduced HTTP Interface Clients. With the Spring Framework, you can define an HTTP service as a Java interface using HTTP exchange methods. By generating a proxy that implements this interface, you can handle the HTTP exchanges. This simplifies remote access to HTTP services and provides the flexibility to choose an API style, such as synchronous or reactive.",[651,119303,119304],{},"This was great, but it was dependent on the Web Client, so you needed to bring in that dependency into your Spring MVC applications for it to work. In Spring Boot 3.2 this is now based on the new Rest Client if you’re in a MVC application.",[651,119306,119307,119308,119311],{},"Instead of having to write the boilerplate low level code to communicate with another service you can just define the method contracts using the ",[676,119309,119310],{},"@HttpExchange"," annotation or one of the specialized versions of it.",[669,119313,119314],{"className":4107,"code":109966,"language":4109,"meta":674,"style":674},[676,119315,119316,119326,119330,119342,119354,119358,119370,119390,119394,119406,119422,119426,119438,119462,119466,119478,119494],{"__ignoreMap":674},[679,119317,119318,119320,119322,119324],{"class":681,"line":682},[679,119319,6073],{"class":685},[679,119321,6994],{"class":685},[679,119323,109977],{"class":880},[679,119325,884],{"class":693},[679,119327,119328],{"class":681,"line":790},[679,119329,889],{"emptyLinePlaceholder":797},[679,119331,119332,119334,119336,119338,119340],{"class":681,"line":892},[679,119333,6872],{"class":693},[679,119335,100228],{"class":685},[679,119337,745],{"class":693},[679,119339,109994],{"class":689},[679,119341,1339],{"class":693},[679,119343,119344,119346,119348,119350,119352],{"class":681,"line":901},[679,119345,110001],{"class":693},[679,119347,109436],{"class":685},[679,119349,110006],{"class":693},[679,119351,34142],{"class":880},[679,119353,9317],{"class":693},[679,119355,119356],{"class":681,"line":909},[679,119357,889],{"emptyLinePlaceholder":797},[679,119359,119360,119362,119364,119366,119368],{"class":681,"line":918},[679,119361,6872],{"class":693},[679,119363,100228],{"class":685},[679,119365,745],{"class":693},[679,119367,110025],{"class":689},[679,119369,1339],{"class":693},[679,119371,119372,119374,119376,119378,119380,119382,119384,119386,119388],{"class":681,"line":935},[679,119373,110032],{"class":693},[679,119375,109436],{"class":685},[679,119377,20881],{"class":693},[679,119379,86714],{"class":880},[679,119381,73246],{"class":693},[679,119383,92277],{"class":685},[679,119385,88766],{"class":693},[679,119387,11341],{"class":2099},[679,119389,1208],{"class":693},[679,119391,119392],{"class":681,"line":944},[679,119393,889],{"emptyLinePlaceholder":797},[679,119395,119396,119398,119400,119402,119404],{"class":681,"line":959},[679,119397,6872],{"class":693},[679,119399,110059],{"class":685},[679,119401,745],{"class":693},[679,119403,109994],{"class":689},[679,119405,1339],{"class":693},[679,119407,119408,119410,119412,119414,119416,119418,119420],{"class":681,"line":964},[679,119409,3314],{"class":685},[679,119411,49468],{"class":880},[679,119413,73246],{"class":693},[679,119415,96282],{"class":685},[679,119417,109607],{"class":693},[679,119419,52469],{"class":2099},[679,119421,1208],{"class":693},[679,119423,119424],{"class":681,"line":977},[679,119425,889],{"emptyLinePlaceholder":797},[679,119427,119428,119430,119432,119434,119436],{"class":681,"line":982},[679,119429,6872],{"class":693},[679,119431,110092],{"class":685},[679,119433,745],{"class":693},[679,119435,110025],{"class":689},[679,119437,1339],{"class":693},[679,119439,119440,119442,119444,119446,119448,119450,119452,119454,119456,119458,119460],{"class":681,"line":988},[679,119441,3314],{"class":685},[679,119443,72244],{"class":880},[679,119445,73246],{"class":693},[679,119447,96282],{"class":685},[679,119449,109607],{"class":693},[679,119451,52469],{"class":2099},[679,119453,85575],{"class":693},[679,119455,92277],{"class":685},[679,119457,88766],{"class":693},[679,119459,11341],{"class":2099},[679,119461,1208],{"class":693},[679,119463,119464],{"class":681,"line":993},[679,119465,889],{"emptyLinePlaceholder":797},[679,119467,119468,119470,119472,119474,119476],{"class":681,"line":2129},[679,119469,6872],{"class":693},[679,119471,110133],{"class":685},[679,119473,745],{"class":693},[679,119475,110025],{"class":689},[679,119477,1339],{"class":693},[679,119479,119480,119482,119484,119486,119488,119490,119492],{"class":681,"line":2140},[679,119481,3314],{"class":685},[679,119483,92272],{"class":880},[679,119485,73246],{"class":693},[679,119487,92277],{"class":685},[679,119489,88766],{"class":693},[679,119491,11341],{"class":2099},[679,119493,1208],{"class":693},[679,119495,119496],{"class":681,"line":2145},[679,119497,996],{"class":693},[651,119499,119500],{},"I liken this to the Spring Data project where we can use the repository abstraction to get the CRUD functionality out of the box freeing us up to focus on the business requirements of the application.",[651,119502,119503,119504,119509],{},"I had a good conversation on ",[812,119505,119508],{"href":119506,"rel":119507},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0059/",[816],"Spring Office Hours with Olga and Rossen"," from the Spring team about the history of clients in Spring and Http Interfaces.",[4542,119511,119513],{"id":119512},"observability","Observability",[651,119515,119516],{},"Observability was a major theme in Spring Boot 3.0 and will continue to be important for the framework and projects in the ecosystem. But what exactly is it, and why should you care about it?",[651,119518,119519],{},"Observability is the ability to observe the internal state of a running system from the outside. It consists of three pillars: logging, metrics, and traces.",[651,119521,119522],{},"It is not enough to simply build an application and release it into production hoping for the best. You need to have visibility into your application's inner workings so that you can proactively address issues instead of reacting to customer complaints.",[651,119524,119525],{},[660,119526],{"alt":119527,"src":119527},"https://images.unsplash.com/photo-1483919283443-8db97e2bcd81?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb",[5909,119529,119531],{"id":119530},"observation-api","Observation API",[651,119533,119534],{},"One of the most significant changes in Spring Boot 3.0 was the introduction of a unified Observation API. This API, which is used by the framework internally, is now also available to developers. The purpose of this API is to allow us to instrument our applications. For each instrumentation, we can perform the following actions:",[5316,119536,119537,119540,119543,119546,119549],{},[5332,119538,119539],{},"Emit Log Messages",[5332,119541,119542],{},"Start & Stop Timers",[5332,119544,119545],{},"Increment Counters",[5332,119547,119548],{},"Start & Stop Spans",[5332,119550,119551],{},"Signal Errors",[651,119553,119554,119555,119558],{},"If you want to do metrics and tracing, you would typically need to instrument your application twice. This can result in a lot of boilerplate code, which may discourage you from implementing observation in the first place.To create an observation, you can use the ",[676,119556,119557],{},"createNotStarted"," method of the Observation class. This method simplifies the process of observing code and captures all the necessary information. However, be cautious as this approach can lead to code pollution if you instrument code excessively.",[669,119560,119562],{"className":4107,"code":119561,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n private static final Logger log = LoggerFactory.getLogger(Application.class);\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n \n @Bean\n CommandLineRunner commandLineRunner(ObservationRegistry registry) {\n return args -> {\n // Create an Observation and observe your code!\n Observation.createNotStarted(\"user.name\", registry)\n .contextualName(\"getting-user-name\")\n // let's assume that you can have 3 user types\n .lowCardinalityKeyValue(\"userType\", \"userType1\")\n // let's assume that this is an arbitrary number \n .highCardinalityKeyValue(\"userId\", \"1234\") \n // this is a shortcut for starting an observation, opening a scope,\n .observe(() -> log.info(\"Hello\")); \n };\n }\n}\n",[676,119563,119564,119570,119580,119584,119602,119606,119626,119634,119638,119643,119649,119662,119672,119677,119692,119706,119711,119730,119735,119754,119759,119782,119786,119790],{"__ignoreMap":674},[679,119565,119566,119568],{"class":681,"line":682},[679,119567,4116],{"class":693},[679,119569,6068],{"class":685},[679,119571,119572,119574,119576,119578],{"class":681,"line":790},[679,119573,6073],{"class":685},[679,119575,4512],{"class":685},[679,119577,16878],{"class":880},[679,119579,884],{"class":693},[679,119581,119582],{"class":681,"line":892},[679,119583,889],{"emptyLinePlaceholder":797},[679,119585,119586,119588,119590,119592,119594,119596,119598,119600],{"class":681,"line":901},[679,119587,9232],{"class":685},[679,119589,6092],{"class":685},[679,119591,12768],{"class":685},[679,119593,111111],{"class":693},[679,119595,686],{"class":685},[679,119597,9240],{"class":693},[679,119599,9243],{"class":880},[679,119601,112525],{"class":693},[679,119603,119604],{"class":681,"line":909},[679,119605,889],{"emptyLinePlaceholder":797},[679,119607,119608,119610,119612,119614,119616,119618,119620,119622,119624],{"class":681,"line":918},[679,119609,6089],{"class":685},[679,119611,6092],{"class":685},[679,119613,6095],{"class":685},[679,119615,6098],{"class":880},[679,119617,745],{"class":693},[679,119619,4758],{"class":685},[679,119621,16901],{"class":693},[679,119623,6108],{"class":2099},[679,119625,4390],{"class":693},[679,119627,119628,119630,119632],{"class":681,"line":935},[679,119629,6115],{"class":693},[679,119631,6118],{"class":880},[679,119633,16914],{"class":693},[679,119635,119636],{"class":681,"line":944},[679,119637,985],{"class":693},[679,119639,119640],{"class":681,"line":959},[679,119641,119642],{"class":693}," \n",[679,119644,119645,119647],{"class":681,"line":964},[679,119646,6872],{"class":693},[679,119648,16929],{"class":685},[679,119650,119651,119653,119655,119658,119660],{"class":681,"line":977},[679,119652,20982],{"class":693},[679,119654,23712],{"class":880},[679,119656,119657],{"class":693},"(ObservationRegistry ",[679,119659,10738],{"class":2099},[679,119661,4390],{"class":693},[679,119663,119664,119666,119668,119670],{"class":681,"line":982},[679,119665,9444],{"class":685},[679,119667,16952],{"class":693},[679,119669,16955],{"class":685},[679,119671,884],{"class":693},[679,119673,119674],{"class":681,"line":988},[679,119675,119676],{"class":1400}," // Create an Observation and observe your code!\n",[679,119678,119679,119682,119684,119686,119689],{"class":681,"line":993},[679,119680,119681],{"class":693}," Observation.",[679,119683,119557],{"class":880},[679,119685,745],{"class":693},[679,119687,119688],{"class":689},"\"user.name\"",[679,119690,119691],{"class":693},", registry)\n",[679,119693,119694,119696,119699,119701,119704],{"class":681,"line":2129},[679,119695,89730],{"class":693},[679,119697,119698],{"class":880},"contextualName",[679,119700,745],{"class":693},[679,119702,119703],{"class":689},"\"getting-user-name\"",[679,119705,1339],{"class":693},[679,119707,119708],{"class":681,"line":2140},[679,119709,119710],{"class":1400}," // let's assume that you can have 3 user types\n",[679,119712,119713,119715,119718,119720,119723,119725,119728],{"class":681,"line":2145},[679,119714,89730],{"class":693},[679,119716,119717],{"class":880},"lowCardinalityKeyValue",[679,119719,745],{"class":693},[679,119721,119722],{"class":689},"\"userType\"",[679,119724,2797],{"class":693},[679,119726,119727],{"class":689},"\"userType1\"",[679,119729,1339],{"class":693},[679,119731,119732],{"class":681,"line":2154},[679,119733,119734],{"class":1400}," // let's assume that this is an arbitrary number \n",[679,119736,119737,119739,119742,119744,119747,119749,119751],{"class":681,"line":2159},[679,119738,89730],{"class":693},[679,119740,119741],{"class":880},"highCardinalityKeyValue",[679,119743,745],{"class":693},[679,119745,119746],{"class":689},"\"userId\"",[679,119748,2797],{"class":693},[679,119750,117128],{"class":689},[679,119752,119753],{"class":693},") \n",[679,119755,119756],{"class":681,"line":2164},[679,119757,119758],{"class":1400}," // this is a shortcut for starting an observation, opening a scope,\n",[679,119760,119761,119763,119766,119768,119770,119772,119774,119776,119779],{"class":681,"line":3134},[679,119762,89730],{"class":693},[679,119764,119765],{"class":880},"observe",[679,119767,55186],{"class":693},[679,119769,16955],{"class":685},[679,119771,111958],{"class":693},[679,119773,9415],{"class":880},[679,119775,745],{"class":693},[679,119777,119778],{"class":689},"\"Hello\"",[679,119780,119781],{"class":693},")); \n",[679,119783,119784],{"class":681,"line":3139},[679,119785,17018],{"class":693},[679,119787,119788],{"class":681,"line":3144},[679,119789,985],{"class":693},[679,119791,119792],{"class":681,"line":3149},[679,119793,996],{"class":693},[5909,119795,119797],{"id":119796},"observability-improvements-in-spring-boot-32","Observability Improvements in Spring Boot 3.2",[651,119799,119800,119801,119804,119805,119808,119809,119808,119812,119808,119815,119818,119819,119822,119823,119826],{},"Prior to Spring Boot 3.2 you could use the ",[676,119802,119803],{},"@Observed"," annotation but you to do some manual configuration to make this happen. In Spring Boot 3.2 you can now use Micrometers ",[676,119806,119807],{},"[@Timed](https://micrometer.io/docs/concepts#_the_timed_annotation)",", ",[676,119810,119811],{},"@Counted",[676,119813,119814],{},"@NewSpan",[676,119816,119817],{},"@ContinueSpan"," and ",[676,119820,119821],{},"[@Observed](https://micrometer.io/docs/observation#_using_annotations_with_observed)"," annotations. The aspects for them are now auto-configured if you have AspectJ on the classpath. You can do this by adding the ",[676,119824,119825],{},"spring-boot-starter-aop"," dependency to your project.",[669,119828,119830],{"className":9101,"code":119829,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-aop\u003C/artifactId>\n\u003C/dependency>\n",[676,119831,119832,119841,119855,119869],{"__ignoreMap":674},[679,119833,119834,119836,119839],{"class":681,"line":682},[679,119835,4505],{"class":693},[679,119837,119838],{"class":4508},"dependency",[679,119840,4519],{"class":693},[679,119842,119843,119845,119848,119851,119853],{"class":681,"line":790},[679,119844,4524],{"class":693},[679,119846,119847],{"class":4508},"groupId",[679,119849,119850],{"class":693},">org.springframework.boot\u003C/",[679,119852,119847],{"class":4508},[679,119854,4519],{"class":693},[679,119856,119857,119859,119862,119865,119867],{"class":681,"line":892},[679,119858,4524],{"class":693},[679,119860,119861],{"class":4508},"artifactId",[679,119863,119864],{"class":693},">spring-boot-starter-aop\u003C/",[679,119866,119861],{"class":4508},[679,119868,4519],{"class":693},[679,119870,119871,119873,119875],{"class":681,"line":901},[679,119872,4586],{"class":693},[679,119874,119838],{"class":4508},[679,119876,4519],{"class":693},[651,119878,119879,119880,119882],{},"Instrumenting the same code becomes much cleaner with the ",[676,119881,119803],{}," annotation!",[669,119884,119886],{"className":4107,"code":119885,"language":4109,"meta":674,"style":674},"@Bean\n@Observed(name = \"user.name\", contextualName = \"getter-user-name\")\nCommandLineRunner commandLineRunner(ObservationRegistry registry) {\n return args -> {\n log.info(\"Hello, World!\");\n };\n}\n",[676,119887,119888,119894,119921,119930,119940,119953,119957],{"__ignoreMap":674},[679,119889,119890,119892],{"class":681,"line":682},[679,119891,4116],{"class":693},[679,119893,16929],{"class":685},[679,119895,119896,119898,119901,119903,119905,119907,119910,119912,119914,119916,119919],{"class":681,"line":790},[679,119897,4116],{"class":693},[679,119899,119900],{"class":685},"Observed",[679,119902,745],{"class":693},[679,119904,16334],{"class":931},[679,119906,6883],{"class":685},[679,119908,119909],{"class":689}," \"user.name\"",[679,119911,2797],{"class":693},[679,119913,119698],{"class":931},[679,119915,6883],{"class":685},[679,119917,119918],{"class":689}," \"getter-user-name\"",[679,119920,1339],{"class":693},[679,119922,119923,119925,119927],{"class":681,"line":892},[679,119924,105527],{"class":693},[679,119926,23712],{"class":880},[679,119928,119929],{"class":693},"(ObservationRegistry registry) {\n",[679,119931,119932,119934,119936,119938],{"class":681,"line":901},[679,119933,21478],{"class":685},[679,119935,16952],{"class":693},[679,119937,16955],{"class":685},[679,119939,884],{"class":693},[679,119941,119942,119945,119947,119949,119951],{"class":681,"line":909},[679,119943,119944],{"class":693}," log.",[679,119946,9415],{"class":880},[679,119948,745],{"class":693},[679,119950,71855],{"class":689},[679,119952,1208],{"class":693},[679,119954,119955],{"class":681,"line":918},[679,119956,50000],{"class":693},[679,119958,119959],{"class":681,"line":935},[679,119960,996],{"class":693},[4542,119962,9042],{"id":9041},[651,119964,119965],{},"This article covers various topics related to Spring Boot 3.2. It introduces virtual threads as a solution for scalability issues in concurrent programming, discusses the Coordinated Restore at Checkpoint (CRaC) project for faster startup time and reduced resource consumption, explores the new client abstractions for JDBC and REST in Spring Boot, and highlights observability improvements. It also provides links to articles, videos, and blog posts for further reading and examples. I hope you enjoyed this and as always friends",[651,119967,41105],{},[786,119969,119970],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":119972},[119973,119979,119984,119991,119995],{"id":118356,"depth":790,"text":118357,"children":119974},[119975,119976,119977,119978],{"id":118372,"depth":892,"text":117624},{"id":118378,"depth":892,"text":118379},{"id":117615,"depth":892,"text":117616},{"id":118422,"depth":892,"text":118423},{"id":118437,"depth":790,"text":118438,"children":119980},[119981,119982,119983],{"id":118444,"depth":892,"text":118445},{"id":118472,"depth":892,"text":118473},{"id":118482,"depth":892,"text":118483},{"id":118504,"depth":790,"text":118505,"children":119985},[119986,119987,119988,119989,119990],{"id":118516,"depth":892,"text":118347},{"id":118939,"depth":892,"text":118940},{"id":118955,"depth":892,"text":118956},{"id":118965,"depth":892,"text":118350},{"id":119297,"depth":892,"text":119298},{"id":119512,"depth":790,"text":119513,"children":119992},[119993,119994],{"id":119530,"depth":892,"text":119531},{"id":119796,"depth":892,"text":119797},{"id":9041,"depth":790,"text":9042},"This article highlights some of the new features in the latest releases of Spring Framework 6.1 and Spring Boot 3.2.",{"slug":119998,"date":119999,"published":797,"author":798,"tags":120000,"cover":120001,"keywords":120002},"spring-boot-3-2","2023-12-20T17:00:00.000Z",[7077],"whats-new.jpeg","Spring Framework, Spring Boot, Spring Framework 6.1, Spring Boot 3.2, Virtual Threads, Project CRaC, JDBC Client, REST Client, Observability",{"title":114,"description":119996},"blog/2023/12/20/spring-boot-3-2","38RfR7vdID9zFUbj1YEghqszMNhxuHb-qrGGJkDKFz4",{"id":120007,"title":111,"body":120008,"description":120310,"extension":793,"meta":120311,"navigation":797,"path":112,"seo":120318,"stem":120319,"__hash__":120320},"content/blog/2023/12/30/2023-year-in-review.md",{"type":648,"value":120009,"toc":120296},[120010,120013,120016,120042,120044,120047,120050,120054,120060,120063,120066,120070,120093,120098,120103,120121,120126,120128,120133,120136,120139,120147,120151,120154,120156,120159,120162,120164,120167,120175,120178,120184,120193,120195,120204,120207,120210,120215,120226,120231,120257,120261,120267,120280,120282,120290,120293],[651,120011,120012],{},"2023 was an incredible year, and I'm excited to share all about it in this article. I would also love to hear about your highlights from 2023. What were some memorable moments for you?",[651,120014,120015],{},"TOC",[5316,120017,120018,120028,120037,120039],{},[5332,120019,120020,120021],{},"VMware / Broadcom\n",[5316,120022,120023,120026],{},[5332,120024,120025],{},"Conferences & Events",[5332,120027,97345],{},[5332,120029,120030,120031],{},"Java & Spring\n",[5316,120032,120033,120035],{},[5332,120034,36422],{},[5332,120036,23816],{},[5332,120038,15432],{},[5332,120040,120041],{},"Personal Website",[4542,120043,83109],{"id":83108},[651,120045,120046],{},"I am about to begin my third year at VMware, which has now become Broadcom as a result of an acquisition that was announced shortly after I joined the company. This acquisition process has been ongoing for the past 18 months. Despite the news, I made a conscious decision not to let it affect me. I had found my dream job, and I was determined to stick with it. Recently, the acquisition was finalized, and we are now officially a part of Broadcom.",[651,120048,120049],{},"The journey hasn't been easy. We lost many talented individuals, and not being able to work with them has been the most challenging aspect. However, I am grateful to still hold my position as a Spring Developer Advocate, and I am excited about the opportunities that lie ahead at Broadcom.",[5909,120051,120053],{"id":120052},"conferences-and-events","Conferences and Events",[651,120055,120056],{},[660,120057],{"alt":120058,"src":120059},"Conference Badges","/images/blog/2023/12/30/conference_badges.jpeg",[651,120061,120062],{},"It was an amazing year of traveling to conferences and events. I think the obvious highlight for me was SpringOne in Las Vegas. This was the return of in-person SpringOne conference and it was amazing. The keynote was electric and I had 3 talks, a live Spring Office Hours and booth duty. It was a long exhausting week but was so great to be in the same spot as so many coworkers. I met so many people for the first time that I have looked up to.",[651,120064,120065],{},"I also made the trip across the pond to Barcelona Spain for VMware Explore. What an amazing trip that was to be in such an amazing place. I’m hoping I can make it back there soon. Here are some of the conferences and events I traveled to this year. I feel very grateful that I get to travel to these events and talk to developers about all the things I am passionate about.",[651,120067,120068],{},[2939,120069,79609],{},[5316,120071,120072,120075,120078,120081,120084,120087,120090],{},[5332,120073,120074],{},"VMware Explore - Barcelona, Spain",[5332,120076,120077],{},"Connect Tech - Atlanta, GA",[5332,120079,120080],{},"SpringOne at VMware Explore - Las Vegas, NV",[5332,120082,120083],{},"KCDC - Kansas City, MO",[5332,120085,120086],{},"DevNexus - Atlanta, GA",[5332,120088,120089],{},"SpringOne Essentials - San Francisco, CA",[5332,120091,120092],{},"CodeMash - Sandusky, OH",[651,120094,120095],{},[660,120096],{"alt":120097,"src":120097},"https://www.danvega.dev/images/newsletter/2023/08/28/springone-keynote.jpeg",[651,120099,120100],{},[2939,120101,120102],{},"Events",[5316,120104,120105,120107,120110,120113,120116,120118],{},[5332,120106,41250],{},[5332,120108,120109],{},"Seattle",[5332,120111,120112],{},"Austin",[5332,120114,120115],{},"Detroit",[5332,120117,41257],{},[5332,120119,120120],{},"Dallas",[651,120122,120123],{},[660,120124],{"alt":120125,"src":120125},"https://www.danvega.dev/images/newsletter/2023/08/28/graphql_talk.png",[5909,120127,97345],{"id":97344},[651,120129,120130],{},[660,120131],{"alt":97345,"src":120132},"/images/blog/2023/12/30/streamyard-thumbnail_talking_heads.png",[651,120134,120135],{},"We just wrapped up Season 2 of Spring Office Hours and it was a really big year for us. If you don’t know my friend and coworker DaShaun and I started a live stream last year. Our goal for this show was to keep the community informed on what’s happening in the Spring Ecosystem and answer any questions you might have.",[651,120137,120138],{},"This year we got better at preparing for shows, scheduling guests and we even turned our show into a podcast. We kept going back and forth on when the right time might be to turn it into a podcast and finally I said I need to take the advice that I always give to others. It’s never going to be a perfect time to start something new, just do it and iterate on it to improve it. That’s what we did and I am really happy we did that.",[651,120140,120141,120142,120146],{},"We have some plans for the new year which include a new website, improving the podcast and improving our shows on the road. I will share more of that with you when I have something to share but until then we have our ",[812,120143,120145],{"href":101215,"rel":120144},[816],"Season 3 Premier"," on Tuesday January 2nd at 3:30 EDT.",[4542,120148,120150],{"id":120149},"java-spring","Java & Spring",[651,120152,120153],{},"It was an amazing year for both Java and Spring. DaShaun and I say it almost weekly on Spring Office Hours but it truly is an amazing time to be a Java and Spring Developer.",[5909,120155,36422],{"id":4109},[651,120157,120158],{},"It was another big year for Java with 2 new releases. Java 20 was released in March and Java 21 was released in September. The highlight for me was Virtual Threads going final in Java 21 and for me this is one of the biggest releases in Java, ever. I don’t say that lately because I know there have been some pretty important releases in the history of this language. As we begin to deploy different types of workloads into production some of them have different requirements. With the release of Virtual Threads, Project CRaC and GraalVM Java has an answer for any type of application. If you want to learn more about Java 21 I shared my thoughts on Episode 52 of Spring Office Hours.",[5988,120160],{"id":120161},"TAliLDYe20M",[5909,120163,23816],{"id":7055},[651,120165,120166],{},"Spring Boot 3 was released at the end of 2022 so that was all the buzz heading into the new year. There were four major themes which were Java 17 Baseline, Jakarta EE, AOT, and Observability. I spent the majority talking about Spring Boot 3 and Beyond 🚀",[651,120168,120169,120170,120174],{},"Spring Boot 3.1 was released in May and for a point release it had a few really great features. I wrote a ",[812,120171,24879],{"href":120172,"rel":120173},"https://www.danvega.dev/blog/spring-boot-docker-compose",[816]," about the one feature that really got my attention, the Docker Compose module. This feature will create a Docker Compose file for you based on the dependencies you select, start the container and inject the properties into a property source at runtime.",[5988,120176],{"id":120177},"lS1GwdIfk0c",[651,120179,120180,120181,664],{},"Spring Boot 3.2 was released in November and it was packed full of features. In terms of Runtime Efficiency we shipped official support for Virtual Threads through a single property. This is such an important feature for anyone building imperative blocking applications and I wrote up a nice long blog post on it ",[812,120182,18263],{"href":118429,"rel":120183},[816],[651,120185,120186,120187,120192],{},"I’m also a big fan of the new client abstractions in Spring Boot 3.2 in the REST Client and JDBC Client. These go in that developer productivity bucket for me and I really can’t wait for developers to get their hands on both of these. I wrote a blog post on ",[812,120188,120191],{"href":120189,"rel":120190},"https://www.danvega.dev/blog/spring-boot-3-2",[816],"What’s new in Spring Boot 3.2"," if you want to learn more.",[4542,120194,15432],{"id":61224},[651,120196,120197,120198,120203],{},"I wrote about this in my ",[812,120199,120202],{"href":120200,"rel":120201},"https://www.danvega.dev/blog/2022-reflections",[816],"2022 Year in Review"," but I had just crossed the 20,000 Subscriber mark towards the end of the year. I uploaded my first video in October of 2013 and it took me 9 years to get to 20,000. Now I didn’t actually start taking YouTube serious until 5 years ago but none the less it took me a long time to get there.",[651,120205,120206],{},"As I’m writing this I’m closing in on 45,000 Subscribers. So in 1 year I did what it took me 9 years to do. Just goes to show you that YouTube is a long game and once you get that ball rolling down the hill it starts going pretty fast. If I can somehow double again next year I will be closing in on that ever so illusive 100k mark which has been the goal since I started.",[651,120208,120209],{},"This past year I created 52 long form videos and 37 shorts. The 52 is a big number for me because my goal was to create at least 1 per week. I didn’t do that this year but I did end up averaging that which is impressive to me. Here are some stats and my top videos from the year.",[651,120211,120212],{},[2939,120213,120214],{},"Stats",[5316,120216,120217,120220,120223],{},[5332,120218,120219],{},"Views - 1,620,206 - 142% over last year",[5332,120221,120222],{},"Watch Time - 160,000 - 252% over last year",[5332,120224,120225],{},"Subscribers - 21,473 - 135% over last year",[651,120227,120228],{},[2939,120229,120230],{},"Top Videos (Views)",[5316,120232,120233,120241,120249],{},[5332,120234,120235,120240],{},[812,120236,120239],{"href":120237,"rel":120238},"https://youtu.be/UgX5lgv4uVM",[816],"Spring Boot Tutorial for Beginners"," - 230,000",[5332,120242,120243,120248],{},[812,120244,120247],{"href":120245,"rel":120246},"https://youtu.be/us0VjFiHogo",[816],"OAuth2 Login Made Easy in Java"," - 65,000",[5332,120250,120251,120256],{},[812,120252,120255],{"href":120253,"rel":120254},"https://youtu.be/KYNR5js2cXE",[816],"Spring Security JWT"," - 60,000",[4542,120258,120260],{"id":120259},"personal-website-blog-newsletter","Personal Website (Blog / Newsletter )",[651,120262,120263],{},[660,120264],{"alt":120265,"src":120266},"Dan Vega Homepage","/images/blog/2023/12/30/danvega_dev_home.png",[651,120268,120269,120270,120275,120276,664],{},"I felt a great sense of pride when I finally accomplished something I had been talking about for a while: migrating my website to Nuxt 3. Since I was already familiar with Vue and Nuxt, that wasn't the obstacle. Initially, I wanted a fresh look and feel for my website, but I didn't want to start designing it from scratch. That's when I discovered the Spotlight template from ",[812,120271,120274],{"href":120272,"rel":120273},"https://tailwindui.com/templates",[816],"Tailwind UI",", and I knew it was perfect. The only issue was that the template was written in React and designed for a different framework, so I had to adapt it. Fortunately, the process wasn't too challenging. This was the spark I needed, and once ignited, there was no stopping me. I transferred all of my previous content to the new blog, which inspired me to clean up various aspects, including old posts. Overall, I am pleased with how it turned out, and I am excited to create more content for my website in 2024. If you're interested in obtaining the source code for my website, you can find it ",[812,120277,18263],{"href":120278,"rel":120279},"https://github.com/danvega/danvega-dev-nuxt",[816],[5909,120281,97394],{"id":97393},[651,120283,120284,120285,120289],{},"I wrote about where to focus my writing in my ",[812,120286,354],{"href":120287,"rel":120288},"https://www.danvega.dev/blog/happy-new-year-2023",[816]," blog post and I’m right back where I started at the beginning of the year. I wrote 42 newsletters this year which isn’t every single week but it’s almost weekly. I wrote 28 blog posts this year and some of them I am really proud of.",[651,120291,120292],{},"My problem with the newsletter is that it eats up a bunch of my time and I have nothing to tell me if its actually helping anyone. I tell people about what I was up to last week and most of it is content around Java / Spring which I could probably just mention on Spring Office Hours. Yes it might be pushing people to my blog posts and YouTube videos but my newsletter subscriber base isn’t growing. Instead of losing people like I did last year I think I gained 300ish to bring me to around 4300.",[651,120294,120295],{},"I’m not saying that everything has to fall down to vanity metrics but If I’m doing something that isn’t moving the needle I could easily be spending time working on something that will. I think I am going to",{"title":674,"searchDepth":790,"depth":790,"links":120297},[120298,120302,120306,120307],{"id":83108,"depth":790,"text":83109,"children":120299},[120300,120301],{"id":120052,"depth":892,"text":120053},{"id":97344,"depth":892,"text":97345},{"id":120149,"depth":790,"text":120150,"children":120303},[120304,120305],{"id":4109,"depth":892,"text":36422},{"id":7055,"depth":892,"text":23816},{"id":61224,"depth":790,"text":15432},{"id":120259,"depth":790,"text":120260,"children":120308},[120309],{"id":97393,"depth":892,"text":97394},"In this article I take a look back at 2023 and share some of the highlights from the year.",{"slug":120312,"date":120313,"published":797,"author":798,"tags":120314,"cover":120316,"keywords":120317},"2023-year-in-review","2023-12-30T17:00:00.000Z",[120315],"Meta","behnam-norouzi-f_Bo19fq4Oc-unsplash.jpg","2023 year in review, 2023, Dan Vega",{"title":111,"description":120310},"blog/2023/12/30/2023-year-in-review","lIs7U4M1TWguKJKjNlnQrsSSTGx6GVHLjOZfH4Se_qY",{"id":120322,"title":108,"body":120323,"description":120505,"extension":793,"meta":120506,"navigation":797,"path":109,"seo":120512,"stem":120513,"__hash__":120514},"content/blog/2024/01/01/happy-new-year-2024.md",{"type":648,"value":120324,"toc":120496},[120325,120333,120335,120338,120381,120384,120386,120391,120394,120397,120400,120402,120407,120410,120413,120416,120420,120425,120428,120431,120441,120444,120456,120458,120463,120466,120469,120472,120475,120479,120485,120488,120491,120493],[651,120326,120327,120328,120332],{},"It’s hard to believe that another year has passed and we have made it to 2024. If you missed ",[812,120329,97271],{"href":120330,"rel":120331},"https://www.danvega.dev/blog/2023-year-in-review",[816]," I did a year in review on what my 2023 looked like. In this post I want to focus on in the new year. I use this posts to reassess during the year to make sure I stay on track.",[4542,120334,82850],{"id":83080},[651,120336,120337],{},"I’m entering my 3rd year as a Spring Developer Advocate now with Broadcom. As I mentioned in my previous post I’m excited to see what the year ahead looks like. With that I am going to be heads down doing what I love to do. That is connecting with you, talking about Java & Spring. I still can’t believe this is what I get to do every single day. I don’t take it for granted and I appreciate all of your support. These are the different areas that I want to focus on when it comes to learning and presenting on this year:",[5316,120339,120340,120348,120362],{},[5332,120341,120342,120343],{},"Java\n",[5316,120344,120345],{},[5332,120346,120347],{},"Java 21 & 22 New Features",[5332,120349,120350,120351],{},"Kotlin\n",[5316,120352,120353,120356,120359],{},[5332,120354,120355],{},"Language Basics",[5332,120357,120358],{},"Kotlin vs Java",[5332,120360,120361],{},"Building Spring Applications with Kotlin",[5332,120363,120364,120365],{},"Spring\n",[5316,120366,120367,120370,120373,120375,120378],{},[5332,120368,120369],{},"Spring Academy",[5332,120371,120372],{},"Spring AI (AI in general)",[5332,120374,99135],{},[5332,120376,120377],{},"Spring Boot Migrator / OpenRewrite",[5332,120379,120380],{},"Spring Security - Authorization Server",[651,120382,120383],{},"I would love to hear from you on what you’re interested in learning in the new year.",[4542,120385,97394],{"id":97393},[651,120387,120388],{},[660,120389],{"alt":97394,"src":120390},"/images/blog/2024/01/01/photo-1501504905252-473c47e087f8.jpeg",[651,120392,120393],{},"I enjoy writing, and in 2023, I remained consistent with my newsletter and personal blog. However, I now want to expand my reach and audience. I have always had the goal of writing my own book, and I plan to start researching this area in the hopes of beginning the process later this year or early next year.",[651,120395,120396],{},"In the meantime, I would like to write for publications other than my personal blog. I already have a few opportunities lined up that I am really excited about, but if you know of any additional opportunities, please feel free to share them with me.",[651,120398,120399],{},"As for my goals, I aim to write 34 blog posts, with 5-10 of them being for publications other than my blog. I believe this is a reasonable number that will allow me to focus on my top priority, YouTube.",[4542,120401,15432],{"id":61224},[651,120403,120404],{},[660,120405],{"alt":15432,"src":120406},"/images/blog/2024/01/01/photo-1521302200778-33500795e128.jpeg",[651,120408,120409],{},"I mentioned in my 2023 year in review that I am fully committed to YouTube this year. In the first 9 years of my YouTube channel, I gained 20,000 subscribers, and in 2023 alone, I gained 25,000 subscribers. If I can double that in 2024, I will reach a significant milestone of 100,000 subscribers and earn the coveted Silver Play Button.",[651,120411,120412],{},"This is my ultimate goal for the year, so much of what I do will revolve around it. I want to continue improving my video-making process and create content that helps people learn new things alongside me. This includes everything from the equipment I use and my studio setup to how I plan, edit, and publish my videos. There are numerous areas I can enhance, and I will focus on those in 2024.",[651,120414,120415],{},"Additionally, I have received many inquiries for sponsorships, and I hope to partner with some exceptional companies this year. If you are interested in sponsoring a video, please feel free to reach out to me.",[4542,120417,120419],{"id":120418},"conferences-events","Conferences / Events",[651,120421,120422],{},[660,120423],{"alt":120025,"src":120424},"/images/blog/2024/01/01/photo-1587825140708-dfaf72ae4b04.jpeg",[651,120426,120427],{},"The only thing I love more than attending conferences is speaking at them. I enjoy being around developers and hearing about the things they are working on and the problems they are trying to solve.",[651,120429,120430],{},"I feel very fortunate to have traveled to so many awesome places last year. I hope to continue doing that this year, and I already have the following conferences scheduled:",[5316,120432,120433,120435,120438],{},[5332,120434,120092],{},[5332,120436,120437],{},"ConFoo - Montreal, CA",[5332,120439,120440],{},"Great International Developer Summit (GIDS) - Bengaluru, India",[651,120442,120443],{},"I also hope to visit new places this year and speak at conferences I haven't been to before. I have submitted proposals to several new conferences for the first half of the year, so I will keep my fingers crossed and hopefully see you there.",[651,120445,120446,120447,120452,120453,664],{},"In addition, I am making an effort to attend more Java User Groups this year. I have some plans in progress, and I will share them with you as soon as they are confirmed. If you want to stay updated on where I will be, you can check out my ",[812,120448,120451],{"href":120449,"rel":120450},"https://www.danvega.dev/speaking",[816],"speaking page"," or subscribe to my free ",[812,120454,67441],{"href":82778,"rel":120455},[816],[4542,120457,97345],{"id":97344},[651,120459,120460],{},[660,120461],{"alt":97345,"src":120462},"/images/blog/2024/01/01/spring-office-hours.png",[651,120464,120465],{},"I'm entering Season 3 of Spring Office Hours with more excitement than ever before. It's a lot of work, but it's incredibly gratifying to see all that hard work pay off.",[651,120467,120468],{},"While we have been aware of the numbers in terms of downloads, live stream views, and YouTube replays, they haven't been our main focus. However, that will change in Season 3. I plan to pay closer attention to the numbers early in the new year and determine our goals for the show. Once we establish those goals, we can work on implementing new strategies to support them.",[651,120470,120471],{},"In the new year, one of our priorities is to create a dedicated website for Spring Office Hours. This will give us more control over certain aspects and allow us to incorporate features that have been on our list.",[651,120473,120474],{},"Another goal for this year is to improve our ability to record podcasts while on the road. We conducted a few road shows last year, and although they were enjoyable, there is definitely room for improvement in this area. The first step is to understand the necessary equipment and determine how to obtain it. If you have experience in this area, we would greatly appreciate your input.",[4542,120476,120478],{"id":120477},"my-website-danvegadev","My Website (danvega.dev)",[651,120480,120481],{},[660,120482],{"alt":120483,"src":120484},"My Website","/images/blog/2024/01/01/danvega_dev_home.png",[651,120486,120487],{},"I’m really happy that I was able to get my website moved over to Nuxt 3 with a new theme towards the end of 2023. What I really want to focus on next is working on the SEO for my blog posts. I have already started working on this for previous posts and will continue to do this throughout the year.",[651,120489,120490],{},"When I sit down to write a new post I want to figure out what people are searching for on a particular topic and cater my articles to those searches. My thought process here is if I am going to sit down and spend the time to write a blog post It would be nice to get some traffic to my website.",[4542,120492,9042],{"id":9041},[651,120494,120495],{},"As I enter 2024, I'm grateful for the experiences and opportunities that came my way as a Spring Developer Advocate. This year, I'll explore new features in Java 21 and 22, dive into Kotlin, and deepen my understanding of the Spring ecosystem. I aim to expand my reach by writing for publications, grow my YouTube channel to 100,000 subscribers, and collaborate with exceptional companies through sponsorships. Attending conferences, improving Spring Office Hours, optimizing SEO for my blog posts, and sharing my experiences with the community are also on my agenda for this exciting year.",{"title":674,"searchDepth":790,"depth":790,"links":120497},[120498,120499,120500,120501,120502,120503,120504],{"id":83080,"depth":790,"text":82850},{"id":97393,"depth":790,"text":97394},{"id":61224,"depth":790,"text":15432},{"id":120418,"depth":790,"text":120419},{"id":97344,"depth":790,"text":97345},{"id":120477,"depth":790,"text":120478},{"id":9041,"depth":790,"text":9042},"In this article I will take a look at some goals and things I want to focus on in the new year.",{"slug":120507,"date":120508,"published":797,"author":798,"tags":120509,"cover":120510,"video":849,"github":849,"keywords":120511},"happy-new-year-2024","2024-01-01T17:00:00.000Z",[120315],"photo-1701170633885-7209b1bf10ee.jpeg","Happy New Year 2024, New Year 2024, New Year, 2024, Dan Vega",{"title":108,"description":120505},"blog/2024/01/01/happy-new-year-2024","4RsG6Sx2sNW-yI1H1xNToVD7anAtqWe-Li0R5KUy4SA",{"id":120516,"title":105,"body":120517,"description":121163,"extension":793,"meta":121164,"navigation":797,"path":106,"seo":121172,"stem":121173,"__hash__":121174},"content/blog/2024/01/15/developer-advocate.md",{"type":648,"value":120518,"toc":121148},[120519,120527,120530,120534,120537,120543,120546,120550,120553,120556,120561,120564,120567,120575,120584,120592,120600,120608,120617,120620,120626,120630,120639,120644,120647,120650,120694,120697,120702,120705,120710,120713,120718,120721,120725,120730,120733,120737,120740,120766,120770,120773,120777,120782,120785,120790,120793,120796,120804,120809,120812,120820,120825,120828,120833,120836,120838,120841,120846,120851,120859,120862,120865,120871,120874,120911,120918,120927,120932,120935,120940,120945,120948,120986,120991,120994,120997,121001,121004,121006,121012,121015,121059,121061,121065,121081,121085,121102,121106,121120,121124,121140,121142,121145],[651,120520,120521,120522,120526],{},"I recently gave a presentation on what Developer Advocacy is and I thought I would share my thoughts here. If you want to find the slides for that talk you can get them ",[812,120523,18263],{"href":120524,"rel":120525},"https://github.com/danvega/slides/blob/main/developer-relations-playbook.pdf",[816]," and if you’re interested in having me give this talk at your meetup or conference please contact me.",[651,120528,120529],{},"In this article, I'll provide an overview of Developer Relations (DevRel) and guide you on how to get started. If you're considering this as a potential career, I hope to inform and inspire you. Even if you're not planning to pursue this career in the near future, you can still advocate for a product or service you're passionate about in your current organization or on your own. You don't need permission to promote your favorite open-source software!",[4542,120531,120533],{"id":120532},"what-is-developer-relations","What is Developer Relations",[651,120535,120536],{},"When discussing Developer Relations (DevRel), it's important to start with the job title. This profession has existed for a long time under various names. I recall early in my career as a ColdFusion developer, there was a gentleman named Ben Forta. He was a ColdFusion Evangelist for Adobe. He impressed me (and still does) by demonstrating the capabilities of ColdFusion, answering questions, and speaking at conferences. I remember admiring Ben and often wondering to myself, how does one pursue that position as a career path.",[651,120538,120539],{},[660,120540],{"alt":120541,"src":120542},"Evangelism","/images/blog/2024/01/15/evangelism.png",[651,120544,120545],{},"These days you will often hear to these individuals referred to as Developer Advocates. You will often hear the team of advocates referred to as Developer Relations and you may even have more than one of them inside an organization.",[5909,120547,120549],{"id":120548},"how-do-you-define-developer-relations-devrel","How do you Define Developer Relations (DevRel)",[651,120551,120552],{},"Things start to get interesting here. If you ask 10 Developer Advocates to define their role, you're likely to receive 10 different answers with some overlap. This is because there's no standardized definition for a Developer Advocate. Moreover, if you search for job postings, you'll find varying responsibilities under the same title.",[651,120554,120555],{},"This diversity results from the fact that Developer Relations (DevRel) means different things to different companies and even to different departments within the same company. To understand what Developer Advocacy might imply for various companies, let's begin with the definition of advocacy:",[1004,120557,120558],{},[651,120559,120560],{},"“public support for or recommendation of a particular cause or policy.”",[651,120562,120563],{},"To illustrate, some of the most pressing advocacies for young people include climate change, mental health, education, and gender equality. Nevertheless, it's crucial to note that the most effective advocacy will be for a cause you truly care about. You can be an advocate in your communities for anything of these things but you should probably believe in it and or be passionate about it before you advocate for it. It’s hard to advocate for Climate Change if you don’t believe in it.",[651,120565,120566],{},"The is where the combination of Developer and Advocacy comes together. If you’re passionate about Developers and the path they have taken in their career to get where they are you are probably passionate about helping them. I remember struggling as a new developer learning new things and constantly failing and just how hard it was to get through that stage of my career. I remember questioning myself and weather or not I was smart enough to do this. This struggle has led me to a life where I truly enjoy helping others because I remember being in that position and when something finally clicked I remember that feeling of excitement that I finally understood it. I have a few quotes from Developer Advocates that I am excited to share with you around what they think it means to be a developer advocate.",[1004,120568,120569],{},[651,120570,120571,120572],{},"“In my opinion, someone in developer relations serves as an advocate for the tech community within their company.” - ",[2939,120573,120574],{},"Emily Freeman - @editingemily",[651,120576,120577,120578,120583],{},"I greatly appreciate this perspective as, for me, advocacy begins with the community you're serving. These are the individuals you're striving to assist, and I believe Emily encapsulates this perfectly in her insightful article titled \"",[812,120579,120582],{"href":120580,"rel":120581},"https://emilyfreeman.io/blog/developer-relations-more-than-the-art-of-talking-good",[816],"Developer Relations: (More Than) the art of talking good","\". While addressing the issues your community struggles with is a part of it, having empathy for their experiences is equally, if not more, essential.",[1004,120585,120586],{},[651,120587,120588,120589],{},"“When I say that I’m a developer advocate, a lot of people wonder what that means, and they’re “Oh, are you just like a paid shill for GitHub? No, no. If anything, I kind of think it’s the inverse. I’m actually a paid shill for our users, because those are the people I care about, the community members.” - ",[2939,120590,120591],{},"Christina Warren - Senior Developer Advocate at GitHub",[651,120593,120594,120595,664],{},"Christina is an exceptional advocate with a deep understanding of her audience. Although we are paid by a particular company, our primary focus is serving the developers using our products. To hear more from Christina on this topic, you can listen to her guest appearance on the ",[812,120596,120599],{"href":120597,"rel":120598},"https://changelog.com/podcast/518",[816],"Changelog podcast",[1004,120601,120602],{},[651,120603,120604,120605],{},"“So the way that I run my developer relations team focuses on three different things. The first is around education, the second is around community, and the third is around the product.” - ",[2939,120606,120607],{},"Lee Robinson - Vercel",[651,120609,120610,120611,120616],{},"I appreciate this quote by Lee Robinson because it encapsulates our role as Developer Advocates. Lee also appeared as a ",[812,120612,120615],{"href":120613,"rel":120614},"https://changelog.com/podcast/493",[816],"guest on the Changelog podcast",", where he shared his insights on Developer Advocacy.",[651,120618,120619],{},"I align with Lee's definition of a Developer Advocate. To me, it represents the intersection of community and company through education. I aid in educating the community through conference talks, podcasts, videos, articles, and more. Through this process, I identify the challenges our community faces and assist in answering their questions. I also bring this feedback back to my company.",[651,120621,120622],{},[660,120623],{"alt":120624,"src":120625},"Community/Education/Company","/images/blog/2024/01/15/community-education-company.png",[5909,120627,120629],{"id":120628},"where-does-devrel-fit-in-an-organization","Where does DevRel fit in an organization?",[651,120631,120632,120633,120638],{},"The reason you get so many different answers when it comes to defining Developer Relations is because it means something different to different companies. I reached out to ",[812,120634,120637],{"href":120635,"rel":120636},"https://twitter.com/kelseyhightower",[816],"Kelsey Hightower"," on Twitter and asked him how he defines it and this was his response.",[651,120640,120641],{},[660,120642],{"alt":120637,"src":120643},"/images/blog/2024/01/15/kelsey.png",[651,120645,120646],{},"Kelsey is always ready to give his opinion, and I found his response to be incredibly candid and honest. Different companies have different objectives, so the company that employs you will typically define their goals.",[651,120648,120649],{},"So, where does Developer Relations (DevRel) fit within an organization - sales, marketing, or engineering? The answer varies depending on the organization's goals. It's also complex because Developer Relations combines many disciplines. Developer Relations is about:",[5316,120651,120652,120658,120664,120670,120676,120682,120688],{},[5332,120653,120654,120657],{},[2939,120655,120656],{},"Engagement",": Establishing connections with developers and tech audiences",[5332,120659,120660,120663],{},[2939,120661,120662],{},"Community Building",": Fostering and nurturing developer communities",[5332,120665,120666,120669],{},[2939,120667,120668],{},"Advocacy",": Championing developers' needs for superior product experiences",[5332,120671,120672,120675],{},[2939,120673,120674],{},"Organizational Representation",": Promoting our products within the developer community",[5332,120677,120678,120681],{},[2939,120679,120680],{},"Technical Expertise",": Deep understanding of our product to educate and support developers",[5332,120683,120684,120687],{},[2939,120685,120686],{},"Resource Creation",": Developing demos, code samples, tutorials, and more",[5332,120689,120690,120693],{},[2939,120691,120692],{},"Beyond the Basics",": A role that encompasses a broad spectrum of skills and responsibilities",[651,120695,120696],{},"Different organizations may prioritize certain aspects over others. Let's look at a few examples of companies with Developer Relations (DevRel) teams and how their goals might vary.",[651,120698,120699],{},[2939,120700,120701],{},"Startup",[651,120703,120704],{},"If you're part of a startup with new products, your primary goal might be raising product awareness. You could attend conferences and engage in introductory discussions with developers, letting them know you have solutions to their problems. Promoting your products through education should be your main objective. Once people start using your products, gather feedback regarding their questions or challenges. This valuable information can be relayed back to your product teams for improvements.",[651,120706,120707],{},[2939,120708,120709],{},"Developer Tooling",[651,120711,120712],{},"If your company produces developer tooling, Developer Relations (DevRel) is essential for your company's success. It's crucial to engage with developers and gather their feedback. A positive user experience plays a significant role in product adoption, and feedback from beginners to advanced users can facilitate this. In this context, establishing a direct line with the engineering team to share feedback and make product improvements is beneficial. Additionally, education is a key component to ensure developers understand how to use the tools effectively.",[651,120714,120715],{},[2939,120716,120717],{},"Developer APIs",[651,120719,120720],{},"There are often DevRel teams inside of companies where an API is part of a product but not the main product like it was in the previous example. These APIs are a set of tools and protocols used by developers to build software and applications. While the API serves as an important tool, it is not the main product offered by the company. Instead, it acts as an instrumental component that allows developers to interact with the software or platform, enabling them to create an array of applications and functionalities.",[5909,120722,120724],{"id":120723},"measuring-impact","Measuring Impact",[651,120726,120727],{},[660,120728],{"alt":120724,"src":120729},"/images/blog/2024/01/15/measuring-impact.png",[651,120731,120732],{},"Regardless of where DevRel fits within an organization, one of the primary challenges is determining how to measure impact. Collaboration within the team is crucial here, as different individuals may have varying strengths. It's important to work with your managers to define team goals and ascertain your individual role. Tracking your contribution to different departments like sales and marketing throughout this processes is essential.",[5909,120734,120736],{"id":120735},"what-a-developer-advocate-isnt","What a Developer Advocate Isn’t",[651,120738,120739],{},"Now that we have defined what developer relations is I think it’s just as important to define what it isn’t.",[5316,120741,120742,120748,120754,120760],{},[5332,120743,120744,120747],{},[2939,120745,120746],{},"NOT a free vacation"," - While traveling may be a perk of the job, it's not akin to a free vacation. It doesn't guarantee complimentary meals or luxurious accommodation. Often, you have to work within budgets, which could mean dining in airport terminals before flights. While some trips may lead to exotic locations, many will be dictated by customer locations, including brief visits to places that may not be on your personal travel wishlist.",[5332,120749,120750,120753],{},[2939,120751,120752],{},"NOT Just Sales"," - DevRel is not about directly driving sales. Instead, it focuses on long-term community engagement and support, which can indirectly contribute to sales through trust and relationship building.",[5332,120755,120756,120759],{},[2939,120757,120758],{},"NOT Just Marketing"," - While DevRel involves communication and community building, it is not solely a marketing role. It's more about creating genuine relationships and understanding developers' needs than just promoting products.",[5332,120761,120762,120765],{},[2939,120763,120764],{},"NOT Just about writing code"," - While technical expertise is crucial, DevRel is as much about communication, empathy, and community building as it is about coding.",[4542,120767,120769],{"id":120768},"developer-relations-playbook","Developer Relations Playbook",[651,120771,120772],{},"If you're intrigued by what you've heard so far, you may be wondering, \"How do I get into DevRel?\" In this section, we will cover things you can start doing now, including content creation along with various tips and tricks.",[5909,120774,120776],{"id":120775},"career-path","Career Path",[651,120778,120779],{},[660,120780],{"alt":120776,"src":120781},"/images/blog/2024/01/15/career-path.png",[651,120783,120784],{},"These are some things you can start focusing on now, whether a career in DevRel is your aspiration, or if it's just something you want to do for fun.",[651,120786,120787],{},[2939,120788,120789],{},"Be a Developer First",[651,120791,120792],{},"To be a Developer Advocate, I believe the first prerequisite is to be a developer. While there may be exceptions, for the most part, one needs to have developer experience. I’m not going to put a number of years of required experience, as it varies among individuals.",[651,120794,120795],{},"Undergoing the process of becoming a developer, including the challenging nights of studying and trial and error, fosters empathy. It helps understand the experiences of developers in your community. Building things is also important, a sentiment I strongly agree with, as expressed by Scott Hanselman at Microsoft.",[1004,120797,120798],{},[651,120799,120800,120801],{},"“The problem is that there are a lot of young people who have never built anything. How can you advocate for developers if you've never built anything? I tend to disappear for several months, ship something, and then talk about it.” - ",[2939,120802,120803],{},"Scott Hanselman - Microsoft",[651,120805,120806],{},[2939,120807,120808],{},"Share your passion",[651,120810,120811],{},"Start sharing your passion through various mediums such as writing, public speaking, videos, podcasts, and more. I didn't begin creating content because I aspired to be a developer advocate. I did it because I enjoyed learning something new and wanted to share it with the community. I also started when I encountered a problem and couldn't find a solution. Once I did, I wanted to ensure others could quickly find the same solution. Through this process, I became involved in the communities I was part of, which has been one of the best things I have done for my career.",[1004,120813,120814],{},[651,120815,120816,120817],{},"“Once you start down the path of enjoying something and sharing it, you're already well on the way to becoming a developer advocate.” ",[2939,120818,120819],{},"Mark Heckler - Microsoft",[651,120821,7406,120822],{},[2939,120823,120824],{},"tart thinking of yourself as a brand",[651,120826,120827],{},"It’s never too early to start thinking of yourself as a personal brand. This can lead to many unique opportunities down the line including Developer Relations. You can start to build an online presence that allows you to establish and showcase your expertise. When it comes to Social Media I usually recommend picking one where your audience is.",[651,120829,120830],{},[2939,120831,120832],{},"Advocate on your own",[651,120834,120835],{},"By sharing content, you can become a developer advocate for your favorite open-source project, or within your current company. Share what you're passionate about and generate excitement for the languages, frameworks, and projects that inspire you. The good news is you don't need permission to advocate for the tech that you find inspiring.",[5909,120837,43654],{"id":43653},[651,120839,120840],{},"There are multiple methods to build a community through education. This section will highlight a few. Excelling at all of them isn't necessary. Instead, concentrate on mastering one or two before exploring others. Each of these topics could warrant its own article, but they will be briefly summarized here for conciseness.",[651,120842,120843],{},[2939,120844,120845],{},"Public Speaking",[651,120847,120848],{},[660,120849],{"alt":120845,"src":120850},"/images/blog/2024/01/15/public-speaking.jpeg",[1004,120852,120853],{},[651,120854,120855,120856],{},"“Public speaking continues to be one of the number one fears that many people have. There's that common nightmare where you're standing up in front of your classmates at school giving a report and, all of a sudden, you're naked or you've forgotten what you wanted to say next. It takes a real act of courage to stand up and present yourself as an authority, especially to a group of your peers.” - ",[2939,120857,120858],{},"Scott Davis - ThoughtWorks",[651,120860,120861],{},"It certainly requires bravery, but I want to clarify some misconceptions and hopefully inspire you to take that leap. Firstly, you don't need to be an extrovert. I know several introverts, including myself, who find comfort in discussing their passions in front of others. Another misconception is that you need to be an expert on the topic you're speaking about, which is far from the truth. You simply need to be one step ahead of your intended audience. This is why its crucial to be clear on what your presentation will cover and what the expected outcome is.",[651,120863,120864],{},"Additionally, some people believe that if someone has already written an article or presented on a topic, they should not do it. But if you look at the water aisle at your grocery store, you'll find all kinds of water with different packaging. I love the quote, \"There are no unique messages, only unique messengers\". You have a unique perspective and experience to share, so go ahead and share it.",[651,120866,120867],{},[660,120868],{"alt":120869,"src":120870},"Unique Messengers","/images/blog/2024/01/15/water.png",[651,120872,120873],{},"If you want to get better at speaking start small and move your way up",[5316,120875,120876,120882,120888,120893,120899,120905],{},[5332,120877,120878,120881],{},[2939,120879,120880],{},"Lunch and Learns"," - I love Lunch and Learns because its usually with a group of your peers who you are already familiar with. There is no pressure here, just share your experience with a particular tool.",[5332,120883,120884,120887],{},[2939,120885,120886],{},"User Group Meetings"," -Local Meetup groups are always in search of new speakers. We're open to first-time speakers as well. Presentations can range from a brief 5-10 minute informal code share to an hour-long talk, and anything in between.",[5332,120889,120890,120892],{},[2939,120891,26378],{}," - I appreciate podcasts as a means to share your story and express your passions. In this format, there's no need to write any code, which usually eases concerns about appearance.",[5332,120894,120895,120898],{},[2939,120896,120897],{},"Local Conferences"," - If you’re regularly speaking at local meetup groups a local conference might be the next step in your progression. In this case you might see a bunch of people you’re familiar with including coworkers which can make this situation a little less stressful.",[5332,120900,120901,120904],{},[2939,120902,120903],{},"Regional Conferences"," - You're on your way to becoming a pro and it's time to expand your audience and venture onto bigger and brighter stages. This doesn't imply you need to be the keynote speaker at a 20,000-developer conference. Many of the conferences I speak at are wonderful, even with less than 50 people in the room.",[5332,120906,120907,120910],{},[2939,120908,120909],{},"International Conferences"," - Now that you have presented at some regional conferences you can begin to branch out across the world.",[1004,120912,120913],{},[651,120914,120915,120916],{},"“I'm a big fan of developer advocates doing the same thing that stand-up comedians do: you work at a small bar, you do some open mics, and you move your way up to the big rooms. That's often how it's done.” - ",[2939,120917,120803],{},[651,120919,120920,120921,120926],{},"There are also some really great groups out there where you can learn and get better at public speaking. ",[812,120922,120925],{"href":120923,"rel":120924},"https://www.toastmasters.org/",[816],"Toastmaster International"," is an organization that has clubs all over the world where you can meat and improve your craft.",[651,120928,120929],{},[2939,120930,120931],{},"Build Something",[651,120933,120934],{},"As a developer advocate you should strive to lose your foundation, writing code. I understand that you’re not going to be writing code 40 hours a week but you to constantly be working on your craft and building things. This could be writing demos to show off a new feature, answering questions or as a part to your latest tutorial.",[651,120936,120937],{},[2939,120938,120939],{},"Technical Writing",[651,120941,120942],{},[660,120943],{"alt":120939,"src":120944},"/images/blog/2024/01/15/technical-writing.jpeg",[651,120946,120947],{},"Writing is an important tool in the Developer Advocates Toolbox. Here are some different ways that you can work on your technical writing skills:",[5316,120949,120950,120955,120961,120976,120981],{},[5332,120951,120952,120954],{},[2939,120953,40845],{}," - Documentation is an important part of every project but often forgotten about. If you go through a getting started experience or you’re reading the documentation for the latest new feature and you see a gap in the documentation you can contribute to it.",[5332,120956,120957,120960],{},[2939,120958,120959],{},"Social Media -"," Social media is a great place to share your experiences, tips, tricks and more. I love sharing ideas on social media because they get you involved in the community and simple thought or idea could lead to your next demo, article, video or event presentation.",[5332,120962,120963,120966,120967,23212,120971,664],{},[2939,120964,120965],{},"Blog Posts"," - I have blogging for most of my career and it’s been a great way to keep writing. I started my own blog by utilizing open source software and it has grown throughout the years. If you don’t want to start your own blog you can begin writing today by taking advantage of platforms like ",[812,120968,56888],{"href":120969,"rel":120970},"https://dev.to/",[816],[812,120972,120975],{"href":120973,"rel":120974},"https://hashnode.com/",[816],"HashNode",[5332,120977,120978,120980],{},[2939,120979,69849],{}," - Many publications welcome the idea of guest posts. I love this because they often have large audiences and it’s a way to get your name out into the community.",[5332,120982,120983,120985],{},[2939,120984,67442],{}," - I love newsletters because you can pick a cadence and try to stick to it. This will keep you writing by giving yourself some soft of schedule.",[651,120987,120988],{},[2939,120989,120990],{},"Videos",[651,120992,120993],{},"I enjoy creating videos as it's my preferred learning method. My works range from short clips to extensive 15-hour courses. Video creation involves many elements, but that's a discussion for another time. If I were to give one piece of advice, it would be to invest in a quality microphone. For developer content, good audio quality is more critical than having a high-end camera.",[651,120995,120996],{},"I've heard it said that your first 100 videos may not be the best, and I think that's fair. What you should aim to do is identify one area for improvement in each video and focus on that in your next one. Repeat this process with different aspects of your videos, and you'll become proficient in no time.",[651,120998,120999],{},[2939,121000,26378],{},[651,121002,121003],{},"Entering podcasting can be relatively straightforward if you have a good microphone. You can start by being a guest on another podcast. As a host myself, I can affirm that we're always in search of guests. If you're interested in appearing on a podcast, I'd encourage you to contact the hosts and propose a show concept. The worst they can say is no.",[5909,121005,60794],{"id":60793},[651,121007,121008],{},[660,121009],{"alt":121010,"src":121011},"Developer Advocate Tips and Tricks","/images/blog/2024/01/15/tips-tricks.png",[651,121013,121014],{},"These are few tips and tricks that I have picked up over the years.",[5316,121016,121017,121023,121029,121035,121041,121047,121053],{},[5332,121018,121019,121022],{},[2939,121020,121021],{},"Repurpose Content"," - Learn to repurpose content for different mediums. A simple tweet might evolve into a demo, which could then become an article, a video, and eventually a conference talk. If you need a new example and idea for every piece of content you create, it may become challenging.",[5332,121024,121025,121028],{},[2939,121026,121027],{},"Answer Honestly"," - One of my greatest fears was standing on a conference stage and being asked a question I couldn't answer. However, it's acceptable to admit when you don't know something. We can't know everything, and no one will fault you for it. A good response could be, \"I'm not certain, but I will consult with the engineering team and share the answer on Twitter or in a tutorial.”",[5332,121030,121031,121034],{},[2939,121032,121033],{},"Be Curious"," - Part of being a Developer and a Developer Advocate is to always be curious and always be eager to learn new things. Not sure of how something works, put together a demo and try to use products in ways you haven’t thought of before.",[5332,121036,121037,121040],{},[2939,121038,121039],{},"Be Authentic"," - Developers are really good at picking up on BS. Be authentic and transparent about what you’re doing and I believe developers will appreciate that in the long run.",[5332,121042,121043,121046],{},[2939,121044,121045],{},"Avoid Burnout"," - In this profession, it's easy to experience burnout from constantly pursuing the next article, video, book, or conference talk. This highlights the importance of repurposing content and recognizing that producing a new article or video every day is not feasible. I used to feel guilty about not meeting self-imposed goals, but I now understand that my expectations were unrealistic.",[5332,121048,121049,121052],{},[2939,121050,121051],{},"Travel Hacks"," - Traveling for work and traveling for fun are 2 completely different animals. I am lucky to have had a coworker who was eager to show me the ropes. Learn some travel tips like avoiding checked bags to minimize time in the airport to taking advantage of programs like TSA, Clear and Global Entry.",[5332,121054,121055,121058],{},[2939,121056,121057],{},"Enjoy the Ride"," - This is my dream job. How many people can show up every day and really truly say they love what they do. I do and I don’t take it for granted.",[4542,121060,21931],{"id":21930},[651,121062,121063],{},[2939,121064,6016],{},[5316,121066,121067,121074],{},[5332,121068,121069],{},[812,121070,121073],{"href":121071,"rel":121072},"https://amzn.to/49cPjRV",[816],"Developer, Advocate!: Conversations on turning a passion for talking about tech into a career",[5332,121075,121076],{},[812,121077,121080],{"href":121078,"rel":121079},"https://amzn.to/3TWZ2Y5",[816],"The Business Value of Developer Relations: How and Why Technical Communities Are Key To Your Success",[651,121082,121083],{},[2939,121084,69849],{},[5316,121086,121087,121095],{},[5332,121088,121089],{},[2939,121090,121091],{},[812,121092,121094],{"href":120580,"rel":121093},[816],"Developer Relations: (More Than) The Art of Talking Good",[5332,121096,121097],{},[812,121098,121101],{"href":121099,"rel":121100},"https://jamesward.com/2021/09/26/the-seven-artifacts-of-developer-advocacy-projects/",[816],"The Seven Artifacts of Developer Advocacy Projects",[651,121103,121104],{},[2939,121105,26378],{},[5316,121107,121108,121114],{},[5332,121109,121110],{},[812,121111,121113],{"href":120613,"rel":121112},[816],"What even is DevRel (Lee Robinson)",[5332,121115,121116],{},[812,121117,121119],{"href":120597,"rel":121118},[816],"Coming home to GitHub (Christina Warren)",[651,121121,121122],{},[2939,121123,41457],{},[5316,121125,121126,121133],{},[5332,121127,121128],{},[812,121129,121132],{"href":121130,"rel":121131},"https://developerrelations.com/",[816],"https://developerrelations.com",[5332,121134,121135],{},[812,121136,121139],{"href":121137,"rel":121138},"https://www.learndevrel.com/",[816],"https://www.learndevrel.com",[4542,121141,9042],{"id":9041},[651,121143,121144],{},"It’s important to remember that everything in this article is based on my experience and it might be different for you. Developer Relations (DevRel) and Developer Advocacy involve advocating for the tech community within a company, focusing on education, community, and product. DevRel fits differently within organizations depending on their goals, encompassing engagement, community building, advocacy, organizational representation, technical expertise, and resource creation. Measuring impact is a challenge and requires collaboration within the team.",[651,121146,121147],{},"A Developer Advocate is not just about sales, marketing, or writing code, but about long-term community engagement, genuine relationships, and understanding developers' needs. The career path into DevRel involves being a developer first, sharing passion, thinking of oneself as a brand, and advocating on one's own. Content creation can involve public speaking, building something, technical writing, videos, and podcasts. Tips for success include repurposing content, answering honestly, being curious and authentic, avoiding burnout, and enjoying the ride.",{"title":674,"searchDepth":790,"depth":790,"links":121149},[121150,121156,121161,121162],{"id":120532,"depth":790,"text":120533,"children":121151},[121152,121153,121154,121155],{"id":120548,"depth":892,"text":120549},{"id":120628,"depth":892,"text":120629},{"id":120723,"depth":892,"text":120724},{"id":120735,"depth":892,"text":120736},{"id":120768,"depth":790,"text":120769,"children":121157},[121158,121159,121160],{"id":120775,"depth":892,"text":120776},{"id":43653,"depth":892,"text":43654},{"id":60793,"depth":892,"text":60794},{"id":21930,"depth":790,"text":21931},{"id":9041,"depth":790,"text":9042},"In this article, I'll provide an overview of Developer Relations (DevRel) and guide you on how to get started. If you're considering this as a potential career, I hope to inform and inspire you.",{"slug":121165,"date":121166,"updatedOn":121167,"published":797,"author":798,"tags":121168,"cover":121170,"keywords":121171},"developer-advocate","2024-01-15T17:00:00.000Z","2024-01-17T17:00:00.000Z",[121169],"DevRel","./developer-advocate.png","Developer Relations, Developer Advocate, DevRel, Developer Evangelist, Spring Developer Advocate, Java Developer Advocate",{"title":105,"description":121163},"blog/2024/01/15/developer-advocate","_VPfB_xgJlCHLROlzU4fL8agkLe12Kkl_AAIzR0vsu4",{"id":121176,"title":102,"body":121177,"description":121359,"extension":793,"meta":121360,"navigation":797,"path":103,"seo":121365,"stem":121366,"__hash__":121367},"content/blog/2024/01/21/java-champion.md",{"type":648,"value":121178,"toc":121345},[121179,121188,121194,121197,121201,121204,121207,121211,121214,121217,121220,121223,121227,121230,121233,121236,121239,121242,121246,121249,121252,121255,121259,121262,121265,121268,121271,121274,121278,121281,121283,121286,121289,121292,121296,121299,121302,121306,121309,121312,121317,121325,121328,121330,121333,121336,121339,121342],[651,121180,121181,121182,121187],{},"On Monday, January 8th, I woke up expecting just another day. Little did I know that this would become a day etched in my memory forever. The day started with me helping my girls prepare for school. Once I settled into work and checked a few emails, I noticed a notification for a tweet mentioning me. At 8:46 AM, I received news from the ",[812,121183,121186],{"href":121184,"rel":121185},"https://twitter.com/Java_Champions/status/1744354879487758359",[816],"Java Champions Twitter account","; I had been named a Java Champion.",[651,121189,121190],{},[660,121191],{"alt":121192,"src":121193},"Java Champions Tweet","/images/blog/2024/01/21/tweet.png",[651,121195,121196],{},"In this article, I'll share my story as it led to the title I now hold. If you're only interested in learning about the Java Champions program, feel free to skip to the end.",[4542,121198,121200],{"id":121199},"my-story","My Story",[651,121202,121203],{},"My story begins when I was a senior in high school in 1996, at the dawn of the internet era. My mother enrolled me in a typing class, much to my dismay. I remember telling her that I had no plans to become a writer or a secretary. Little did I know how significant that class would turn out to be. By the end of it, I had learned to type approximately 68 words per minute.",[651,121205,121206],{},"Shortly after graduation, my mother gave me an old computer. After making some upgrades, I got it up and running. This sparked my fascination with computers, how they were assembled, and the software that powered them.",[5909,121208,121210],{"id":121209},"my-college-journey","My College Journey",[651,121212,121213],{},"As a landscaper, I was accustomed to hard work. My routine involved landscaping in the summer and plowing in the winter. Being out late in the cold was exhausting, and I grew to dislike it. Towards the end of one summer, I realized I needed a change, but I was unsure of my options. I had always enjoyed math, science, and mechanical engineering in high school, but without a scholarship or funds for college, it seemed out of reach.",[651,121215,121216],{},"Eventually, I discovered Remington College, a two-year technical college. I applied, got accepted, and started my journey. Although I don't remember the specifics of the financials, I recall the lengthy process of paying off my student loan.",[651,121218,121219],{},"During college, I learned about a programming language called Java. My classmates often complained about the complexity of starting with an Object-Oriented Language like Java instead of something simpler. But, naive and eager to learn, I embraced the challenge.",[651,121221,121222],{},"In March 2000, I graduated. While studying, I taught myself how to build web applications using HTML/CSS in FrontPage, eventually transitioning to Dreamweaver and learning ColdFusion. I found clients online and undertook side jobs building websites. My most significant project was for an online luggage retailer, for whom I built a complete e-commerce application.",[5909,121224,121226],{"id":121225},"heading-out-west","Heading out West",[651,121228,121229],{},"I used my connections to join a California-based startup called Five9 as their seventh employee. My initial role was a level 2 support specialist, acting as a liaison between customers and engineers. As the company grew, I increasingly worked with engineers, helping to develop our primary product: call center software using VOIP.",[651,121231,121232],{},"I learned a lot in this role, particularly from repeatedly answering the same customer queries. This led me to develop training videos to address common problems. This was pre-YouTube era and, in hindsight, my first attempt to convert my knowledge into content to help others.",[651,121234,121235],{},"I also worked on a few of our websites that used Struts, which was my first exposure to a Java Framework and I thoroughly enjoyed it.",[651,121237,121238],{},"Living in Oakland, near Berkeley College, I used my free time to expand my knowledge in Java and ColdFusion. I hired a tutor from Craigslist, a Berkeley TA, to deepen my understanding of the Java programming language. Concurrently, I explored web building using ColdFusion, which became a significant part of my early career.",[651,121240,121241],{},"I cherished my time at Five9 and living in the Bay Area. However, after two years, I was homesick and didn't see a long-term future there, so I decided to return to the Cleveland area.",[5909,121243,121245],{"id":121244},"web-development-career","Web Development Career",[651,121247,121248],{},"I started my career at a company called Letternine, which primarily focused on contract web development. They had a custom CMS built with ColdFusion for creating custom solutions for clients. As an integral part of a small team, I had the opportunity to work on some exciting projects. This is where I began engaging with the community, using an open-source blogging platform called BlogCFC. I frequently blogged about my work and developments within the ColdFusion community.",[651,121250,121251],{},"Later, I joined another company in the Cleveland area, STERIS. I undertook web development on various platforms, including our customer-facing website and an e-commerce platform written in Java + Oracle. My role involved a significant amount of integration with this platform. I stayed there for four years and greatly enjoyed my time.",[651,121253,121254],{},"Throughout my career in ColdFusion, I made some good friends in the community. One such friend was Jason Delmore, who used to be the product manager for ColdFusion. Jason moved to an insurance company called Markel as the Director of Technology and brought me into the organization. I worked on a few mission-critical applications and was part of the team that migrated some applications over to Java/Groovy/Grails. I had a great time working there, and I still have some close friends from that period. During my stint at Markel, I got married, and I was fortunate to have Jason at my bachelor party and Jason, Sam, and Lance at my wedding.",[5909,121256,121258],{"id":121257},"coding-bootcamp","Coding Bootcamp",[651,121260,121261],{},"After about 15 years of coding, I sought a new challenge. Having coded for so long, I began pondering the next steps. While some transition into management, I discovered a different path. I learned about a new coding bootcamp, Tech Elevator, which had just started in the Cleveland area. I was already familiar with them, having participated in panel discussions and meetup groups they held.",[651,121263,121264],{},"Interestingly, I found out that the co-founder, David, and I attended the same high school. The world is indeed small.",[651,121266,121267],{},"I joined them as a Curriculum Developer, tasked with developing course content for both instructors and students. This role allowed me to utilize my content creation skills. I learned a lot from David and my colleagues, greatly improving my technical writing skills. Occasionally, I also taught in the classroom, sharing the knowledge I had included in the curriculum.",[651,121269,121270],{},"However, the most rewarding part of this experience was the lives we helped transform. Witnessing students from all backgrounds enter our program and depart with a new career they were genuinely excited about was truly amazing. The daily interaction with the students made coming to work exhilarating.",[651,121272,121273],{},"Unfortunately, the COVID-19 pandemic hit. Despite Tech Elevator's successful transition to a remote program, it wasn't the same for me. Hence, I decided to seize another opportunity.",[5909,121275,121277],{"id":121276},"enterprise-java-architect","Enterprise Java Architect",[651,121279,121280],{},"I took a position as an Enterprise Java Architect at a company called Briebug. By the time I left, I had been promoted to Principal Architect. In this role, I worked for one of the world's largest logistics companies, with many responsibilities. I worked on several mission-critical systems that processed millions of transactions per hour. These systems were distributed and built on Java, Spring Boot, Spring Cloud, among other projects in this ecosystem. I also interviewed potential Java and Spring candidates and helped develop our interview process. Despite my enjoyment of the work and my colleagues, I decided to take a new opportunity when my dream job came calling.",[5909,121282,82850],{"id":83080},[651,121284,121285],{},"I first learned about an opening in the Developer Advocacy team at VMware from a friend. They were specifically looking for an advocate for Spring, a role that seemed perfect for me. I had been an unofficial Spring advocate for some time, having worked with and taught Spring, so this was my dream job.",[651,121287,121288],{},"Securing this position was not straightforward and required a six-month process. However, as the saying goes, nothing worth having comes easily. Eventually, I was hired as a Spring Developer Advocate. This new role allowed me to educate our customers and the community through various mediums. I was tasked with producing YouTube videos, writing technical articles about Spring Ecosystem projects, speaking at conferences, and more.",[651,121290,121291],{},"Being actively involved in the Java community for the past two years has significantly contributed to my receipt of the Java Champion award. I genuinely enjoy what I do every day, and I never take that for granted.",[5909,121293,121295],{"id":121294},"thoughts-on-my-career","Thoughts on my Career",[651,121297,121298],{},"At no point in my career have I been coasting. I've always wanted to do this, but it hasn't been easy. Every step of my journey has required extensive learning, late-night coding sessions, and failure. Success has never come easily, which is why I have a deep appreciation for it. This experience has also fostered a sense of respect for my peers and empathy towards them, as I understand the challenges they have faced.",[651,121300,121301],{},"I have a deep passion for learning and teaching others. I hope to continue this throughout my career.",[4542,121303,121305],{"id":121304},"what-is-a-java-champion","What is a Java Champion?",[651,121307,121308],{},"So, that's my story. With this context, you might understand why I'm so thrilled about this title being bestowed upon me. It's a result of my hard work throughout my career.",[651,121310,121311],{},"You might be wondering, what is a Java Champion? This is someone who advocates for the Java programming language within the community. They can do this through various mediums such as writing books, speaking at conferences, contributing to open-source projects, and more. There is an insightful quote from the JavaOne Conference in 2009 that I believe aptly describes a Java Champion:",[1004,121313,121314],{},[651,121315,121316],{},"\"The Java Champions community was started by Sun at the 2005 JavaOne conference to recognize key influencers in the Java community. Java Champions are influential Rock Star presenters and Java technology educators, authors, and consultants; Java platform event organizers; and others within the Java technology ecosystem. For the third consecutive year, Java Champions have contributed to the JavaOne conference process as technical reviewers for paper submissions, have been recognized for their achievements, and have shared their thoughts about the state of the Java platform at their BOF sessions.”",[651,121318,121319,121320,121324],{},"Oracle currently oversees the ",[812,121321,121323],{"href":97457,"rel":121322},[816],"Java Champions Program"," but does not vote on who is awarded this title. Becoming a Java Champion requires nomination by a current champion, followed by a voting process. I won't delve into the details of that process just yet. The Java Champions Program comprises some of the most talented individuals in our community, so being nominated and voted in by them is truly an honor.",[651,121326,121327],{},"Out of the 10-14 million Java developers worldwide, I am the 385th to be named a Java Champion. This recognition makes all my hard work over the years worth it. Many people have asked me what benefits come with being a Java Champion. Besides receiving a nice polo and jacket, which I will proudly share once I receive them, the most significant benefit is the recognition that comes with the title. I will wear this recognition like a badge of honor throughout my career.",[4542,121329,89039],{"id":89038},[651,121331,121332],{},"The reward for hard work is often more hard work. Through this nomination process, I've gained some insights. Firstly, I need to create a space to keep track of all my career achievements. It's important to remember the work that has shaped my career. Consequently, I plan to create a page to showcase my career highlights.",[651,121334,121335],{},"Secondly, I've realized that despite writing for my blog and newsletter, I aspire to contribute more to other publications. One of my long-term goals is to write a book, but I am not ready for that just yet. Instead, I aim to write for other outlets this year, with some projects already underway.",[651,121337,121338],{},"Lastly, I want to be more involved in the Java Community. I'm exploring ways to achieve this, and it will certainly be a focus for me this year and in the future.",[4542,121340,121341],{"id":83146},"Thank you",[651,121343,121344],{},"I want to express my gratitude to everyone who reached out to me with such kind words. I have read all of your messages and sincerely appreciate your sentiments. My hope is to carry this title with honor and to serve the Java community well.",{"title":674,"searchDepth":790,"depth":790,"links":121346},[121347,121356,121357,121358],{"id":121199,"depth":790,"text":121200,"children":121348},[121349,121350,121351,121352,121353,121354,121355],{"id":121209,"depth":892,"text":121210},{"id":121225,"depth":892,"text":121226},{"id":121244,"depth":892,"text":121245},{"id":121257,"depth":892,"text":121258},{"id":121276,"depth":892,"text":121277},{"id":83080,"depth":892,"text":82850},{"id":121294,"depth":892,"text":121295},{"id":121304,"depth":790,"text":121305},{"id":89038,"depth":790,"text":89039},{"id":83146,"depth":790,"text":121341},"On Monday, January 8th, I woke up expecting just another day. Little did I know that this would become a day etched in my memory forever. In this post I would like to tell you about my career and how I became I Java Champion.",{"slug":79569,"date":121361,"published":797,"author":798,"tags":121362,"cover":121363,"keywords":121364},"2024-01-21T17:00:00.000Z",[36422,120315],"./java-champion-cover.png","Java, Java Champion, My Story, Career, Dan Vega",{"title":102,"description":121359},"blog/2024/01/21/java-champion","kO4wXw6KVxG2cHD8wobvwuXyFAy4yVIs9hk5dmkfTv4",{"id":121369,"title":99,"body":121370,"description":121374,"extension":793,"meta":122322,"navigation":797,"path":100,"seo":122328,"stem":122329,"__hash__":122330},"content/blog/2024/06/03/spring-io-2024.md",{"type":648,"value":121371,"toc":122306},[121372,121375,121378,121381,121384,121388,121391,121394,121400,121404,121407,121410,121413,121416,121419,121422,121425,121429,121432,121435,121440,121444,121450,121453,121456,121459,121462,121469,121475,121480,121484,121487,121490,121493,121496,121500,121503,121507,121510,121517,121528,121570,121573,121577,121580,121587,121603,121607,121616,121802,121805,121884,121887,121946,121950,121956,121959,122139,122147,122154,122158,122164,122173,122289,122292,122294,122297,122303],[651,121373,121374],{},"I just returned from my very first Spring I/O and I thought this would be a great opportunity to tell you about one of the best conferences I have eve been to. I am very fortunate to be able to travel the world visiting customers and conferences but this was by far one of my favorite trips ever.",[651,121376,121377],{},"Usually when I visit conferences there is a dedicated track for Java and or Spring so one of the first things I do is try and find my people. I look for the Java / Spring developers and I hang out with them because I love to listen to what they are working on or what they are interested in.",[651,121379,121380],{},"This time I didn't have to find my people, they were EVERYWHERE! It was really exciting to hear everyone talking about all the cool things they are doing with Java & Spring and I felt right at home.",[651,121382,121383],{},"The other thing that was really exciting for me is the amount of love I felt from all of you. I had so many people come up to me and tell me how much they appreciate all the content I get to work on. The major theme was that all the videos that I work on are really helping people learn Java and Spring.\nI knew this already from all the amazing comments I get online and I appreciate them, but it's totally different hearing that from people in person. To know that I am helping people get that first job, promotion or just leveling up from real people was something I will never forget. In fact, it has rejuvenated me to know that what I am doing is making a real difference and I need to do more of it.",[4542,121385,121387],{"id":121386},"spring-io-morning-session-and-keynote","Spring I/O Morning Session and Keynote",[651,121389,121390],{},"The conference kicked off early Thursday morning for me with the opening sessions and keynote. The opening session was a welcome to everyone to the conference, and it started with an amazing drone show. Next the conference organizer Sergi (alongside Sanderson Jones who was amazing by the way ) kicked off the conference by welcoming everyone with some amazing stats about who was in attendance. It was really great to see so many Spring developers represented by so many countries across the world.",[651,121392,121393],{},"After that they got a big round of applause for all the speakers and sponsors of the conference. This was my opportunity to tell everyone about the amazing things we are doing at Tanzu, the division I work for inside of Broadcom. I had a 2-minute script that I wrote and rehearsed. I was a little a nervous because scripting something and remembering it was not something I have done a lot of especially in front of 2,000 people. In the end I thought I did a good job of representing Spring and Tanzu, so I hope you enjoyed it.",[651,121395,121396],{},[660,121397],{"alt":121398,"src":121399},"Dan Welcome Session","/images/blog/2024/06/03/dan_sponsored.jpeg",[5909,121401,121403],{"id":121402},"keynote","Keynote",[651,121405,121406],{},"Next up was the keynote, and it was kicked off by none other than Juergan Holler who took the opportunity to share some of the results form the State of Spring Survey we held this year. It was really great to hear that a majority of developers were running Java 17+ and Spring Boot 3.0+ in production. He even made a comment that I really enjoyed that Java 17 was the new Java 8. This means that as a community we are running newer versions of Java and this makes me happy. He also made it a point that the Spring team spent a lot of time this year going through the backlogs and fixing and closing a lot of issues.",[651,121408,121409],{},"We also had a chance ot hear from so many presenters on all the new features in Spring and some of the different projects in the ecosystem. First up was Sebastien Deluze to talk about all the exciting features around the theme of runtime efficiency. We had a chance to hear about all the exciting things we can do to improve performance and even got a sneak peek at some things coming in Spring Boot 3.4.",[651,121411,121412],{},"Next up we got a demo from Stéphane Nicoll / Brian Clozel. I absolutely love when these 2 present on anything because they have such a great chemistry and flow to their presentations. They do a great job of live coding and walking through everything they are doing.",[651,121414,121415],{},"Next Cora, my coworker gave us an introduction to Spring Modulith and why we should take a look at this project for our next application. I thought she did an amazing job of laying out the problems with current architectures and what Spring Modulith will solve. I was also excited to hear about a new feature that supports migrating legacy applications incrementally to Spring Modulith which I think is going to be a game changer.",[651,121417,121418],{},"Finally, we had a really great presentation by Christian Tzolov and Josh Long on Spring AI. They took the infamous Pet Clinic application and turned it into a pet agency, a way to adopt your next pet. I thought they did an amazing job of showing off the new features in Spring AI while keeping it interesting. Josh did his live coding thing while telling an amazing story about his dog.",[651,121420,121421],{},"If you want to watch the replay of the keynote you can do so below. I am interested in hearing what you thought of the keynote and what presentations / announcements you found exciting.",[5988,121423],{"id":121424},"XUz4LKZx83g",[4542,121426,121428],{"id":121427},"speaker-dinner","Speaker Dinner",[651,121430,121431],{},"I left on Tuesday around 2:50 EDT and after a long delay in Washington and a trip through customs I finally arrived at my hotel on Wednesday around 4 PM. This left me 2 hours to rehearse my presentation at the welcome session and my session that would take place right after lunch on Thursday. We met right outside the hotel and jumped on a bus that would take us to the venue for the speaker dinner.",[651,121433,121434],{},"The speaker dinner was amazing with drinks, great food and friendly faces. I got to meet so many people that I have talked to online for years and this was the first time I got to meet them in person. I also got to meet so many coworkers for the first time and that was special. I ended up leaving after a couple of hours because I was exhausted from the jet lag and I needed to be up early the next morning.",[651,121436,121437],{},[660,121438],{"alt":121428,"src":121439},"/images/blog/2024/06/03/speaker_dinner.jpeg",[4542,121441,121443],{"id":121442},"a-spring-developers-guide-to-navigating-the-frontend-landscape","A Spring Developers Guide to Navigating the Frontend Landscape",[651,121445,121446],{},[660,121447],{"alt":121448,"src":121449},"Frontend Landscape","/images/blog/2024/06/03/frontend_landscape.png",[651,121451,121452],{},"My only session of the conference was a tour of how to build a frontend for Spring Developers. The premise behind this talk was you are a Spring Developer and you need to build a frontend for you application, how do you go about doing so.",[651,121454,121455],{},"I only had 50 minutes, so I wanted to share some of the options you have for making this happen. I covered my passion for building for the web, how to do this in Java and some of your options for doing this in JavaScript.",[651,121457,121458],{},"The idea here is that the landscape of frontend development changes often and I wanted to leave you with some modern options for building frontend applications. We took a look at options in for building a frontend in Java like Spring MVC and Vaadin.",[651,121460,121461],{},"Next we took at look at the every changing landscape in the world of JavaScript. This includes package managers, build tools and runtimes. We also took some time to talk about JavaScript Frameworks and looked at examples of how to create a single project that includes both the frontend and backend. We wrapped up the frontend by introducing HTMX and showing off some code and how htmx lets you add dynamic behavior to your application WITHOUT writing JavaScript.",[651,121463,121464,121465,664],{},"Finally, we discussed some of the things you should be thinking of to pick the right tool for the right job. I think it went really well and I got some really nice feedback. Thank you to everyone who showed up in person, I really appreciate it. If you want to grab the code for all the demos I walked through you can find it ",[812,121466,18263],{"href":121467,"rel":121468},"https://github.com/spring-developer-frontend-landscape",[816],[651,121470,121471],{},[660,121472],{"alt":121473,"src":121474},"My Session","/images/blog/2024/06/03/mytalk_01.jpeg",[651,121476,121477],{},[660,121478],{"alt":121473,"src":121479},"/images/blog/2024/06/03/mytalk_02.jpg",[4542,121481,121483],{"id":121482},"spring-office-hours-live","Spring Office Hours Live",[651,121485,121486],{},"When DaShaun and I are both in the same place it’s always nice to try and schedule a live version of Spring Office Hours. We have done it in the past, but it isn't always easy because we don't have the equipment to travel with to produce a high quality show. This time we were able to get a really nice camera with a 35mm lens and 2 great microphones from the conference. It was a lot of fun having a live audience with us as we took the opportunity to talk about all the things happening at Spring I/O.",[5988,121488],{"id":121489},"SkDMkI22qBQ",[651,121491,121492],{},"If you’re interested in hearing our wrap up of the conference and a deeper dive into the State of Spring Survey Results you can check out S3E21 below.",[5988,121494],{"id":121495},"bNTmRpPbas8",[4542,121497,121499],{"id":121498},"sessions","Sessions",[651,121501,121502],{},"I was able to attend a bunch of sessions and this was one of the best lineups of speakers and sessions I have ever seen. Normally I can pick out a few sessions that I want to attend but this was packed full of sessions that I could not miss. While I attended and enjoyed a bunch of sessions these are a few of them and the highlights that I want to share with you. When these sessions are published to YouTube I will return to this blog post and add the recordings but until then you will just have to take my word that they were all amazing 🤩",[4542,121504,121506],{"id":121505},"spring-security-architecture-principles","Spring Security Architecture Principles",[651,121508,121509],{},"One of the first sessions I attended was \"Spring Security Architecture Principles\" by Daniel Garnier-Moiroux. I have been working with Daniel for 2.5 years now, and it was really great to finally meet him and see him speak in person. Daniel is an amazing presenter and does a great job of mixing slides along with his live coding skills. In this talk Daniel took us back to the basics and explained some of the underlying fundamentals of Spring Security.",[651,121511,121512,121513,121516],{},"Security is hard, and it is used to cover so many different scenarios and I think our default is how do we do \"x\" in Spring Security which leads to Google/ChatGPT/StackOverflow. This can be confusing if you don't understand the base concepts like filters, authentication and authorization. One of the tips that Daniel shared with the audience was how to find out where in the security chain things are failing. He shared this with me personally awhile back, and it is my go to \"cheat code\". If you turn on ",[676,121514,121515],{},"TRACE"," logging for Spring Security its easy to find out where in the filter chain things are failing.",[669,121518,121520],{"className":76589,"code":121519,"language":35538,"meta":674,"style":674},"logging.level.org.springframework.security=TRACE\n",[676,121521,121522],{"__ignoreMap":674},[679,121523,121524,121526],{"class":681,"line":682},[679,121525,107740],{"class":685},[679,121527,107743],{"class":693},[669,121529,121531],{"className":67476,"code":121530,"language":56308,"meta":674,"style":674},"logging:\n level:\n org:\n springframework:\n security: TRACE\n",[676,121532,121533,121539,121546,121553,121560],{"__ignoreMap":674},[679,121534,121535,121537],{"class":681,"line":682},[679,121536,56407],{"class":4508},[679,121538,3327],{"class":693},[679,121540,121541,121544],{"class":681,"line":790},[679,121542,121543],{"class":4508}," level",[679,121545,3327],{"class":693},[679,121547,121548,121551],{"class":681,"line":892},[679,121549,121550],{"class":4508}," org",[679,121552,3327],{"class":693},[679,121554,121555,121558],{"class":681,"line":901},[679,121556,121557],{"class":4508}," springframework",[679,121559,3327],{"class":693},[679,121561,121562,121565,121567],{"class":681,"line":909},[679,121563,121564],{"class":4508}," security",[679,121566,4282],{"class":693},[679,121568,121569],{"class":689},"TRACE\n",[651,121571,121572],{},"I also learned that a presentation is not a presentation without Emoji's 🤣",[5909,121574,121576],{"id":121575},"going-aot-everything-you-need-to-know-about-graalvm-for-java-applications","Going AOT: Everything you need to know about GraalVM for Java applications",[651,121578,121579],{},"Next up was a session on GraalVM by Alina Yurenko who is a developer advocate for Oracle Labs. I have had the opportunity to meet Alina on Zoom a few times, but it was really great to finally meet her in person. She did a really great job of explaining some of the history of GraalVM, what it is and where its heading. I thought her slides were amazing and I learned a few things I didn't know going on.",[651,121581,121582,121583],{},"First off you can use the GraalVM JDK for JIT compilation and possibly see some performance improvements. I'll have to wait for the recording to see what those improvements were because I was locked in this conference and wasn't taking notes. She also spoke about some work that they are doing behind the scenes to improve build times. Imagine the framework and all the third-party libraries were cached after the first build and all that you had to worry about what was your own code. This could make a huge difference on build times for native images, and I'm here for it! I also learned that you can install early access builds of GraalVM JDK right from ",[812,121584,42841],{"href":121585,"rel":121586},"https://sdkman.io/",[816],[669,121588,121590],{"className":7993,"code":121589,"language":7995,"meta":674,"style":674},"sdk install java 23.ea.9-graal\n",[676,121591,121592],{"__ignoreMap":674},[679,121593,121594,121596,121598,121600],{"class":681,"line":682},[679,121595,42899],{"class":880},[679,121597,24571],{"class":689},[679,121599,42904],{"class":689},[679,121601,121602],{"class":689}," 23.ea.9-graal\n",[5909,121604,121606],{"id":121605},"introducing-spring-ai","Introducing Spring AI",[651,121608,121609,121610,121615],{},"Last session of day 1 was an Introduction to Spring AI with Christian Tzolov & Mark Pollack. If you follow me you know I'm a big fan of Spring AI, so I was really interested in hearing about all the new features that dropped in ",[812,121611,121614],{"href":121612,"rel":121613},"https://spring.io/blog/2024/05/30/spring-ai-1-0-0-m1-released",[816],"1.0.0 M1",". I think the biggest new feature was the new chat client's fluent API design. If you have used something like the Web Client or Rest Client this will look pretty familiar. This cuts down on some code and makes it much more readable.",[669,121617,121619],{"className":4107,"code":121618,"language":4109,"meta":674,"style":674},"@RestController\npublic class ChatController {\n\n private final ChatClient chatClient;\n\n public ChatController(ChatClient.Builder builder) {\n this.chatClient = builder\n .defaultSystem(\"All of your answers should be in the voice of a pirate.\")\n .build();\n }\n\n @GetMapping(\"\")\n public String home(@RequestParam(value = \"message\", defaultValue = \"Tell me a dad joke about Star Wars\") String message) {\n return chatClient.prompt()\n .user(message)\n .call()\n .content();\n }\n}\n",[676,121620,121621,121627,121638,121642,121651,121655,121668,121679,121693,121701,121705,121709,121721,121757,121768,121777,121786,121794,121798],{"__ignoreMap":674},[679,121622,121623,121625],{"class":681,"line":682},[679,121624,4116],{"class":693},[679,121626,9212],{"class":685},[679,121628,121629,121631,121633,121636],{"class":681,"line":790},[679,121630,6073],{"class":685},[679,121632,4512],{"class":685},[679,121634,121635],{"class":880}," ChatController",[679,121637,884],{"class":693},[679,121639,121640],{"class":681,"line":892},[679,121641,889],{"emptyLinePlaceholder":797},[679,121643,121644,121646,121648],{"class":681,"line":901},[679,121645,9232],{"class":685},[679,121647,12768],{"class":685},[679,121649,121650],{"class":693}," ChatClient chatClient;\n",[679,121652,121653],{"class":681,"line":909},[679,121654,889],{"emptyLinePlaceholder":797},[679,121656,121657,121659,121661,121664,121666],{"class":681,"line":918},[679,121658,6089],{"class":685},[679,121660,121635],{"class":880},[679,121662,121663],{"class":693},"(ChatClient.Builder ",[679,121665,90934],{"class":2099},[679,121667,4390],{"class":693},[679,121669,121670,121672,121675,121677],{"class":681,"line":935},[679,121671,7862],{"class":931},[679,121673,121674],{"class":693},".chatClient ",[679,121676,686],{"class":685},[679,121678,119021],{"class":693},[679,121680,121681,121683,121686,121688,121691],{"class":681,"line":944},[679,121682,73482],{"class":693},[679,121684,121685],{"class":880},"defaultSystem",[679,121687,745],{"class":693},[679,121689,121690],{"class":689},"\"All of your answers should be in the voice of a pirate.\"",[679,121692,1339],{"class":693},[679,121694,121695,121697,121699],{"class":681,"line":959},[679,121696,73482],{"class":693},[679,121698,23612],{"class":880},[679,121700,9317],{"class":693},[679,121702,121703],{"class":681,"line":964},[679,121704,985],{"class":693},[679,121706,121707],{"class":681,"line":977},[679,121708,889],{"emptyLinePlaceholder":797},[679,121710,121711,121713,121715,121717,121719],{"class":681,"line":982},[679,121712,6872],{"class":693},[679,121714,20852],{"class":685},[679,121716,745],{"class":693},[679,121718,3579],{"class":689},[679,121720,1339],{"class":693},[679,121722,121723,121725,121727,121729,121731,121733,121735,121737,121739,121742,121744,121746,121748,121751,121753,121755],{"class":681,"line":988},[679,121724,6089],{"class":685},[679,121726,9289],{"class":693},[679,121728,12642],{"class":880},[679,121730,73246],{"class":693},[679,121732,73249],{"class":685},[679,121734,745],{"class":693},[679,121736,19934],{"class":931},[679,121738,6883],{"class":685},[679,121740,121741],{"class":689}," \"message\"",[679,121743,2797],{"class":693},[679,121745,73254],{"class":931},[679,121747,6883],{"class":685},[679,121749,121750],{"class":689}," \"Tell me a dad joke about Star Wars\"",[679,121752,73262],{"class":693},[679,121754,20198],{"class":2099},[679,121756,4390],{"class":693},[679,121758,121759,121761,121764,121766],{"class":681,"line":993},[679,121760,9444],{"class":685},[679,121762,121763],{"class":693}," chatClient.",[679,121765,55494],{"class":880},[679,121767,17545],{"class":693},[679,121769,121770,121772,121774],{"class":681,"line":2129},[679,121771,73482],{"class":693},[679,121773,9575],{"class":880},[679,121775,121776],{"class":693},"(message)\n",[679,121778,121779,121781,121784],{"class":681,"line":2140},[679,121780,73482],{"class":693},[679,121782,121783],{"class":880},"call",[679,121785,17545],{"class":693},[679,121787,121788,121790,121792],{"class":681,"line":2145},[679,121789,73482],{"class":693},[679,121791,47833],{"class":880},[679,121793,9317],{"class":693},[679,121795,121796],{"class":681,"line":2154},[679,121797,985],{"class":693},[679,121799,121800],{"class":681,"line":2159},[679,121801,996],{"class":693},[651,121803,121804],{},"Another big feature was the addition of advisors. You can use advisors when you need to append or augment the prompt with contextual data. This could be in the form of prompt stuffing or RAG when you need to add context to the conversation.",[669,121806,121808],{"className":4107,"code":121807,"language":4109,"meta":674,"style":674},"ChatResponse response = ChatClient.builder(chatModel)\n .build().prompt()\n .advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))\n .user(userText)\n .call()\n .chatResponse();\n",[676,121809,121810,121825,121837,121858,121867,121875],{"__ignoreMap":674},[679,121811,121812,121815,121817,121820,121822],{"class":681,"line":682},[679,121813,121814],{"class":693},"ChatResponse response ",[679,121816,686],{"class":685},[679,121818,121819],{"class":693}," ChatClient.",[679,121821,90934],{"class":880},[679,121823,121824],{"class":693},"(chatModel)\n",[679,121826,121827,121829,121831,121833,121835],{"class":681,"line":790},[679,121828,21331],{"class":693},[679,121830,23612],{"class":880},[679,121832,10541],{"class":693},[679,121834,55494],{"class":880},[679,121836,17545],{"class":693},[679,121838,121839,121841,121844,121846,121848,121851,121854,121856],{"class":681,"line":892},[679,121840,21331],{"class":693},[679,121842,121843],{"class":880},"advisors",[679,121845,745],{"class":693},[679,121847,8930],{"class":685},[679,121849,121850],{"class":880}," QuestionAnswerAdvisor",[679,121852,121853],{"class":693},"(vectorStore, SearchRequest.",[679,121855,75058],{"class":880},[679,121857,104226],{"class":693},[679,121859,121860,121862,121864],{"class":681,"line":901},[679,121861,21331],{"class":693},[679,121863,9575],{"class":880},[679,121865,121866],{"class":693},"(userText)\n",[679,121868,121869,121871,121873],{"class":681,"line":909},[679,121870,21331],{"class":693},[679,121872,121783],{"class":880},[679,121874,17545],{"class":693},[679,121876,121877,121879,121882],{"class":681,"line":918},[679,121878,21331],{"class":693},[679,121880,121881],{"class":880},"chatResponse",[679,121883,9317],{"class":693},[651,121885,121886],{},"You have to remember that these LLMs are all stateless APIs. We forget this because we use a tool like ChatGPT, and it remembers our previous conversation. This is a feature of ChatGPT, not the LLM. Spring AI adds the ability to have Chat Memory. This is another reason to use advisors and this makes it easy to take advantage of the new chat memory feature.",[669,121888,121890],{"className":4107,"code":121889,"language":4109,"meta":674,"style":674}," public ChatController(ChatClient.Builder builder) {\n this.chatClient = builder\n .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))\n .build();\n }\n",[676,121891,121892,121901,121911,121934,121942],{"__ignoreMap":674},[679,121893,121894,121896,121898],{"class":681,"line":682},[679,121895,6089],{"class":685},[679,121897,121635],{"class":880},[679,121899,121900],{"class":693},"(ChatClient.Builder builder) {\n",[679,121902,121903,121905,121907,121909],{"class":681,"line":790},[679,121904,7862],{"class":931},[679,121906,121674],{"class":693},[679,121908,686],{"class":685},[679,121910,119021],{"class":693},[679,121912,121913,121915,121918,121920,121922,121925,121927,121929,121932],{"class":681,"line":892},[679,121914,73482],{"class":693},[679,121916,121917],{"class":880},"defaultAdvisors",[679,121919,745],{"class":693},[679,121921,8930],{"class":685},[679,121923,121924],{"class":880}," MessageChatMemoryAdvisor",[679,121926,745],{"class":693},[679,121928,8930],{"class":685},[679,121930,121931],{"class":880}," InMemoryChatMemory",[679,121933,104226],{"class":693},[679,121935,121936,121938,121940],{"class":681,"line":901},[679,121937,73482],{"class":693},[679,121939,23612],{"class":880},[679,121941,9317],{"class":693},[679,121943,121944],{"class":681,"line":909},[679,121945,985],{"class":693},[5909,121947,121949],{"id":121948},"graphql-java-spring-the-latest-features","GraphQL Java & Spring: The latest features",[651,121951,121952],{},[660,121953],{"alt":121954,"src":121955},"Rossen Stoyanchev","/images/blog/2024/06/03/rossen.jpeg",[651,121957,121958],{},"I made sure I was up early on day 2 to get some breakfast and be the first one in the room to hear Rossen Stoyanchev talk about the latest features in Spring for GraphQL. He started off by showing a schema that included an Activity interface that was implemented by Run, Swim and Rowing. There are a lot of new features around this setup including a reworked inspection report that can now correctly report unmapped fields. Another feature I didn't realize was there is the GraphQL support in IntelliJ IDEA. If you look in the gutter for a query you can jump to the data fetcher for the query and vice versa. A small win but a pretty cool one if you ask me.",[669,121960,121962],{"className":66259,"code":121961,"language":66261,"meta":674,"style":674},"type Query {\n athlete(id: ID) : Athlete\n search(text: String): [SearchItem!]!\n}\n\ninterface Activity {\n id: ID!\n description: String!\n comments: [Comment!]\n}\n\ntype Run implements Activity {\n id: ID!\n description: String!\n comments: [Comment!]\n elevation: Int # In meters.\n}\n",[676,121963,121964,121974,121993,122018,122022,122026,122034,122045,122056,122070,122074,122078,122091,122101,122111,122123,122135],{"__ignoreMap":674},[679,121965,121966,121969,121972],{"class":681,"line":682},[679,121967,121968],{"class":685},"type",[679,121970,121971],{"class":931}," Query",[679,121973,884],{"class":693},[679,121975,121976,121979,121981,121983,121985,121987,121990],{"class":681,"line":790},[679,121977,121978],{"class":2099}," athlete",[679,121980,745],{"class":693},[679,121982,11341],{"class":2099},[679,121984,4282],{"class":693},[679,121986,22662],{"class":931},[679,121988,121989],{"class":693},") : ",[679,121991,121992],{"class":931},"Athlete\n",[679,121994,121995,121998,122000,122002,122004,122006,122009,122012,122014,122016],{"class":681,"line":892},[679,121996,121997],{"class":2099}," search",[679,121999,745],{"class":693},[679,122001,11464],{"class":2099},[679,122003,4282],{"class":693},[679,122005,4758],{"class":931},[679,122007,122008],{"class":693},"): [",[679,122010,122011],{"class":931},"SearchItem",[679,122013,1223],{"class":685},[679,122015,21854],{"class":693},[679,122017,83719],{"class":685},[679,122019,122020],{"class":681,"line":901},[679,122021,996],{"class":693},[679,122023,122024],{"class":681,"line":909},[679,122025,889],{"emptyLinePlaceholder":797},[679,122027,122028,122030,122032],{"class":681,"line":918},[679,122029,6630],{"class":685},[679,122031,96023],{"class":931},[679,122033,884],{"class":693},[679,122035,122036,122039,122041,122043],{"class":681,"line":935},[679,122037,122038],{"class":2099}," id",[679,122040,4282],{"class":693},[679,122042,22662],{"class":931},[679,122044,83719],{"class":685},[679,122046,122047,122050,122052,122054],{"class":681,"line":944},[679,122048,122049],{"class":2099}," description",[679,122051,4282],{"class":693},[679,122053,4758],{"class":931},[679,122055,83719],{"class":685},[679,122057,122058,122061,122063,122066,122068],{"class":681,"line":959},[679,122059,122060],{"class":2099}," comments",[679,122062,67544],{"class":693},[679,122064,122065],{"class":931},"Comment",[679,122067,1223],{"class":685},[679,122069,67550],{"class":693},[679,122071,122072],{"class":681,"line":964},[679,122073,996],{"class":693},[679,122075,122076],{"class":681,"line":977},[679,122077,889],{"emptyLinePlaceholder":797},[679,122079,122080,122082,122085,122087,122089],{"class":681,"line":982},[679,122081,121968],{"class":685},[679,122083,122084],{"class":931}," Run",[679,122086,4661],{"class":685},[679,122088,96023],{"class":931},[679,122090,884],{"class":693},[679,122092,122093,122095,122097,122099],{"class":681,"line":988},[679,122094,122038],{"class":2099},[679,122096,4282],{"class":693},[679,122098,22662],{"class":931},[679,122100,83719],{"class":685},[679,122102,122103,122105,122107,122109],{"class":681,"line":993},[679,122104,122049],{"class":2099},[679,122106,4282],{"class":693},[679,122108,4758],{"class":931},[679,122110,83719],{"class":685},[679,122112,122113,122115,122117,122119,122121],{"class":681,"line":2129},[679,122114,122060],{"class":2099},[679,122116,67544],{"class":693},[679,122118,122065],{"class":931},[679,122120,1223],{"class":685},[679,122122,67550],{"class":693},[679,122124,122125,122128,122130,122132],{"class":681,"line":2140},[679,122126,122127],{"class":2099}," elevation",[679,122129,4282],{"class":693},[679,122131,86608],{"class":931},[679,122133,122134],{"class":1400}," # In meters.\n",[679,122136,122137],{"class":681,"line":2145},[679,122138,996],{"class":693},[651,122140,122141,122142,122146],{},"Another big feature is federation and this has been something on my radar for a while now. I haven't had a chance to go through this repository yet but if you want to see how it works you can ",[812,122143,17991],{"href":122144,"rel":122145},"https://github.com/apollographql/federation-jvm-spring-example",[816],". The idea is that we would have a single gateway entry for our entire GraphQL API and then fan out to different services for each part of the API. This would allow each team to be responsible for their own piece of the API. Netflix does this really well and at the scale only Netflix can. I'm really excited to take a look at this and put together a tutorial.",[651,122148,122149,122150,664],{},"Rossen also pointed out the integration with the DGS Framework from Netflix. This made a lot of sense for both sides, and it's exciting to see this collaboration going forward. If you want to read more about this you can check it out ",[812,122151,18263],{"href":122152,"rel":122153},"https://netflix.github.io/dgs/spring-graphql-integration/",[816],[5909,122155,122157],{"id":122156},"java-meets-typescript-full-stack-web-development-with-spring-boot","Java meets TypeScript: Full-Stack Web Development with Spring Boot",[651,122159,122160],{},[660,122161],{"alt":122162,"src":122163},"Marcus Hellberg","/images/blog/2024/06/03/marcus.jpeg",[651,122165,122166,122167,122172],{},"I got a chance to sit in on Marcus Hellberg's session on ",[812,122168,122171],{"href":122169,"rel":122170},"https://hilla.dev/",[816],"Hilla",". I'm a big fan of Marcus and Vaadin the company behind this project. This project gives you a really nice streamlined way to build full-stack Spring applications with Spring Boot + React. What's great about this is you can expose some endpoints on the Spring side and then get type safety on the frontend with TypeScript types generated for you. Another awesome result of this is I can almost seamlessly call my Java class from TypeScript.",[669,122174,122176],{"className":25132,"code":122175,"language":25134,"meta":674,"style":674},"function CustomerList() {\n // Customer type is automatically generated by Hilla\n const [customers, setCustomers] = useState\u003CCustomer[]>([]);\n\n useEffect(() => {\n CustomerService.getCustomers().then(setCustomers);\n }, []);\n\n return (\n \u003CComboBox items={customers} />\n )\n}\n",[676,122177,122178,122187,122192,122221,122225,122236,122251,122256,122260,122266,122281,122285],{"__ignoreMap":674},[679,122179,122180,122182,122185],{"class":681,"line":682},[679,122181,55109],{"class":685},[679,122183,122184],{"class":880}," CustomerList",[679,122186,2667],{"class":693},[679,122188,122189],{"class":681,"line":790},[679,122190,122191],{"class":1400}," // Customer type is automatically generated by Hilla\n",[679,122193,122194,122196,122198,122201,122203,122206,122208,122210,122213,122215,122218],{"class":681,"line":892},[679,122195,54021],{"class":685},[679,122197,4270],{"class":693},[679,122199,122200],{"class":931},"customers",[679,122202,2797],{"class":693},[679,122204,122205],{"class":931},"setCustomers",[679,122207,51966],{"class":693},[679,122209,686],{"class":685},[679,122211,122212],{"class":880}," useState",[679,122214,4505],{"class":693},[679,122216,122217],{"class":880},"Customer",[679,122219,122220],{"class":693},"[]>([]);\n",[679,122222,122223],{"class":681,"line":901},[679,122224,889],{"emptyLinePlaceholder":797},[679,122226,122227,122230,122232,122234],{"class":681,"line":909},[679,122228,122229],{"class":880}," useEffect",[679,122231,55186],{"class":693},[679,122233,21350],{"class":685},[679,122235,884],{"class":693},[679,122237,122238,122241,122244,122246,122248],{"class":681,"line":918},[679,122239,122240],{"class":693}," CustomerService.",[679,122242,122243],{"class":880},"getCustomers",[679,122245,10541],{"class":693},[679,122247,46728],{"class":880},[679,122249,122250],{"class":693},"(setCustomers);\n",[679,122252,122253],{"class":681,"line":935},[679,122254,122255],{"class":693}," }, []);\n",[679,122257,122258],{"class":681,"line":944},[679,122259,889],{"emptyLinePlaceholder":797},[679,122261,122262,122264],{"class":681,"line":959},[679,122263,21478],{"class":685},[679,122265,59384],{"class":693},[679,122267,122268,122270,122273,122276,122278],{"class":681,"line":964},[679,122269,4904],{"class":693},[679,122271,122272],{"class":931},"ComboBox",[679,122274,122275],{"class":880}," items",[679,122277,686],{"class":685},[679,122279,122280],{"class":693},"{customers} />\n",[679,122282,122283],{"class":681,"line":977},[679,122284,35523],{"class":693},[679,122286,122287],{"class":681,"line":982},[679,122288,996],{"class":693},[651,122290,122291],{},"I really enjoyed this presentation and I have seen enough to know that I want to put together a demo for Hilla.",[4542,122293,9042],{"id":9041},[651,122295,122296],{},"I know it's not easy to get to conferences with budgets these days but if you have to pick 1 or 2 out of the year to go to this has to be on your list. Not only is Barcelona one of the most beautiful cities I have ever been to but this conference has to be on your bucket list. The speakers are top-notch, the sessions are amazing and Sergi does an amazing job at putting on one of the best conferences I have ever been to. At the end of the conference they announced the dates for next year's conference and I hope I will be there and I hope to see you all of there as well.",[651,122298,122299],{},[660,122300],{"alt":122301,"src":122302},"Next year","/images/blog/2024/06/03/spring_io_2025.jpg",[786,122304,122305],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":122307},[122308,122311,122312,122313,122314,122315,122321],{"id":121386,"depth":790,"text":121387,"children":122309},[122310],{"id":121402,"depth":892,"text":121403},{"id":121427,"depth":790,"text":121428},{"id":121442,"depth":790,"text":121443},{"id":121482,"depth":790,"text":121483},{"id":121498,"depth":790,"text":121499},{"id":121505,"depth":790,"text":121506,"children":122316},[122317,122318,122319,122320],{"id":121575,"depth":892,"text":121576},{"id":121605,"depth":892,"text":121606},{"id":121948,"depth":892,"text":121949},{"id":122156,"depth":892,"text":122157},{"id":9041,"depth":790,"text":9042},{"slug":122323,"date":122324,"published":797,"author":798,"tags":122325,"cover":122326,"keywords":122327},"spring-io-2024","2024-06-03T17:00:00.000Z",[7077],"./spring_io_cover.jpeg","Spring Framework, Spring Boot, Spring I/O, Java, Conferences",{"title":99,"description":121374},"blog/2024/06/03/spring-io-2024","lU65m-zqm_DU8tEbymHc5ib90t7YtQYsugFVgfVJ0oo",{"id":122332,"title":96,"body":122333,"description":123544,"extension":793,"meta":123545,"navigation":797,"path":97,"seo":123554,"stem":123555,"__hash__":123556},"content/blog/2024/06/20/spring-ai-m1.md",{"type":648,"value":122334,"toc":123529},[122335,122338,122342,122349,122372,122375,122387,122390,122392,122395,122399,122405,122444,122447,122454,122457,122461,122476,122505,122509,122523,122602,122611,122701,122705,122712,122727,122730,122734,122737,122744,122778,122792,122946,122949,122965,122968,123142,123146,123152,123253,123259,123276,123279,123282,123285,123288,123297,123470,123475,123492,123495,123511,123513,123516,123519,123522,123526],[651,122336,122337],{},"In this post we're talking about the newest release of Spring AI, which is version 1.0.0 Milestone 1. I had the great pleasure of being in Barcelona for Spring I/O during the release, and I got a firsthand look at some of the new features. I have to say, I'm really impressed. I thought Spring AI was an easy project to use before, but this update makes it even easier to use and more concise. Today, we'll go through the release notes for 1.0.0 M1 and create a new project to showcase a few of these exciting new features.",[4542,122339,122341],{"id":122340},"release-notes-overview","Release Notes Overview",[651,122343,122344,122345,122348],{},"First things first, let's quickly glance at the ",[812,122346,95704],{"href":121612,"rel":122347},[816]," for 1.0.0 Milestone 1 (released on May 30, 2024). We'll be using some of these updates in our project. Some of the notable changes in this release are:",[5316,122350,122351,122354,122357,122360,122363,122366,122369],{},[5332,122352,122353],{},"ChatClient Fluent API",[5332,122355,122356],{},"Structured Output",[5332,122358,122359],{},"Advisors",[5332,122361,122362],{},"In-Memory Conversational History",[5332,122364,122365],{},"New & Updated AI Models",[5332,122367,122368],{},"New Vector Stores",[5332,122370,122371],{},"Testcontainers Support",[5909,122373,122353],{"id":122374},"chatclient-fluent-api",[651,122376,122377,122378,51393,122380,122382,122383,122386],{},"If you're familiar with other client classes in Spring, like the ",[676,122379,109920],{},[676,122381,113232],{},", the new ",[676,122384,122385],{},"ChatClient"," fluent API will feel very familiar. We start with a builder to get an instance, set some defaults, and then use the fluent API to handle various tasks.",[651,122388,122389],{},"Instead of creating prompts, templates, and managing outputs separately, everything is centralized in this fluent API. For example, to create a prompt and make a call, you can declare a record and immediately get a typed response without extra fussing around.",[4542,122391,95749],{"id":54715},[651,122393,122394],{},"Let's get our hands dirty by creating a new project using some of the new APIs.",[5909,122396,122398],{"id":122397},"step-1-initialize-the-project","Step 1: Initialize the Project",[651,122400,95283,122401,122404],{},[812,122402,77478],{"href":7115,"rel":122403},[816]," and fill out the required metadata:",[5316,122406,122407,122412,122417,122422,122427,122433,122439],{},[5332,122408,122409,122411],{},[2939,122410,91908],{},": dev.dan.vega",[5332,122413,122414,122416],{},[2939,122415,91912],{},": hello-m1",[5332,122418,122419,122421],{},[2939,122420,23058],{},": HelloM1",[5332,122423,122424,122426],{},[2939,122425,12285],{},": Demo project for Spring AI 1.0.0 M1",[5332,122428,122429,122432],{},[2939,122430,122431],{},"Package Name",": dev.danvega.hellom1",[5332,122434,122435,122438],{},[2939,122436,122437],{},"Packaging",": Jar",[5332,122440,122441,117682],{},[2939,122442,122443],{},"Java Version",[651,122445,122446],{},"Add the following dependencies:",[5316,122448,122449,122451],{},[5332,122450,80827],{},[5332,122452,122453],{},"OpenAI",[651,122455,122456],{},"Generate the project and open it in your favorite IDE (I'm using IntelliJ Ultimate Edition).",[5909,122458,122460],{"id":122459},"step-2-set-up-your-environment","Step 2: Set Up Your Environment",[651,122462,122463,122464,122469,122470,122472,122473,664],{},"You will need an ",[812,122465,122468],{"href":122466,"rel":122467},"https://platform.openai.com/",[816],"OpenAI API key"," to run the project. Once you have a key you will need to set it in ",[676,122471,16242],{},". You could hard code it for a quick test but don't leave it there long. A better approach is to use an environment variable, so you don't leak your API key when you check it into source code control. You will also want to define the model that you want to use and for this example we will use Open AI's latest model ",[676,122474,122475],{},"gpt-4o",[669,122477,122479],{"className":76589,"code":122478,"language":35538,"meta":674,"style":674},"spring.application.name=hello-m1\nspring.ai.openai.api-key=${OPENAI_API_KEY}\nspring.ai.openai.chat.options.model=gpt-4o\n",[676,122480,122481,122489,122497],{"__ignoreMap":674},[679,122482,122483,122486],{"class":681,"line":682},[679,122484,122485],{"class":685},"spring.application.name",[679,122487,122488],{"class":693},"=hello-m1\n",[679,122490,122491,122494],{"class":681,"line":790},[679,122492,122493],{"class":685},"spring.ai.openai.api-key",[679,122495,122496],{"class":693},"=${OPENAI_API_KEY}\n",[679,122498,122499,122502],{"class":681,"line":892},[679,122500,122501],{"class":685},"spring.ai.openai.chat.options.model",[679,122503,122504],{"class":693},"=gpt-4o\n",[5909,122506,122508],{"id":122507},"step-3-write-some-code","Step 3: Write Some Code",[651,122510,122511,122512,122515,122516,122518,122519,122522],{},"Let's write some code to demonstrate the new features of Spring AI. We'll start by creating a ",[676,122513,122514],{},"ChatController",". As we learned earlier to get an instance of the ",[676,122517,122385],{}," you can ask Spring to autowire a bean of type ",[676,122520,122521],{},"ChatClient.Builder"," and there is a default chat client that gets created for you.",[669,122524,122526],{"className":4107,"code":122525,"language":4109,"meta":674,"style":674},"@RestController\npublic class ChatController {\n\n private final ChatClient chatClient;\n\n public ChatController(ChatClient.Builder builder) {\n this.chatClient = builder\n .build();\n }\n\n}\n",[676,122527,122528,122534,122544,122548,122556,122560,122572,122582,122590,122594,122598],{"__ignoreMap":674},[679,122529,122530,122532],{"class":681,"line":682},[679,122531,4116],{"class":693},[679,122533,9212],{"class":685},[679,122535,122536,122538,122540,122542],{"class":681,"line":790},[679,122537,6073],{"class":685},[679,122539,4512],{"class":685},[679,122541,121635],{"class":880},[679,122543,884],{"class":693},[679,122545,122546],{"class":681,"line":892},[679,122547,889],{"emptyLinePlaceholder":797},[679,122549,122550,122552,122554],{"class":681,"line":901},[679,122551,9232],{"class":685},[679,122553,12768],{"class":685},[679,122555,121650],{"class":693},[679,122557,122558],{"class":681,"line":909},[679,122559,889],{"emptyLinePlaceholder":797},[679,122561,122562,122564,122566,122568,122570],{"class":681,"line":918},[679,122563,6089],{"class":685},[679,122565,121635],{"class":880},[679,122567,121663],{"class":693},[679,122569,90934],{"class":2099},[679,122571,4390],{"class":693},[679,122573,122574,122576,122578,122580],{"class":681,"line":935},[679,122575,7862],{"class":931},[679,122577,121674],{"class":693},[679,122579,686],{"class":685},[679,122581,119021],{"class":693},[679,122583,122584,122586,122588],{"class":681,"line":944},[679,122585,73482],{"class":693},[679,122587,23612],{"class":880},[679,122589,9317],{"class":693},[679,122591,122592],{"class":681,"line":959},[679,122593,985],{"class":693},[679,122595,122596],{"class":681,"line":964},[679,122597,889],{"emptyLinePlaceholder":797},[679,122599,122600],{"class":681,"line":977},[679,122601,996],{"class":693},[651,122603,122604,122605,122607,122608,664],{},"Once you have an instance of a ",[676,122606,122385],{}," you can use the fluent API to set the user message, make a call to the LLM and then declare the response type. In previous versions of Spring AI you had to chain some method calls to get the response as a string, but now you can simply call ",[676,122609,122610],{},".content()",[669,122612,122614],{"className":4107,"code":122613,"language":4109,"meta":674,"style":674},"@GetMapping(\"/\")\npublic String joke(@RequestParam(value = \"message\", defaultValue = \"Tell me a dad joke about dogs\") String message) {\n return chatClient.prompt()\n .user(message)\n .call()\n .content(); // short for getResult().getOutput().getContent();\n}\n",[676,122615,122616,122628,122660,122670,122678,122686,122697],{"__ignoreMap":674},[679,122617,122618,122620,122622,122624,122626],{"class":681,"line":682},[679,122619,4116],{"class":693},[679,122621,20852],{"class":685},[679,122623,745],{"class":693},[679,122625,10032],{"class":689},[679,122627,1339],{"class":693},[679,122629,122630,122632,122634,122636,122638,122640,122642,122644,122646,122648,122650,122652,122654,122657],{"class":681,"line":790},[679,122631,6073],{"class":685},[679,122633,9289],{"class":693},[679,122635,100415],{"class":880},[679,122637,73246],{"class":693},[679,122639,73249],{"class":685},[679,122641,745],{"class":693},[679,122643,19934],{"class":931},[679,122645,6883],{"class":685},[679,122647,121741],{"class":689},[679,122649,2797],{"class":693},[679,122651,73254],{"class":931},[679,122653,6883],{"class":685},[679,122655,122656],{"class":689}," \"Tell me a dad joke about dogs\"",[679,122658,122659],{"class":693},") String message) {\n",[679,122661,122662,122664,122666,122668],{"class":681,"line":892},[679,122663,21478],{"class":685},[679,122665,121763],{"class":693},[679,122667,55494],{"class":880},[679,122669,17545],{"class":693},[679,122671,122672,122674,122676],{"class":681,"line":901},[679,122673,40148],{"class":693},[679,122675,9575],{"class":880},[679,122677,121776],{"class":693},[679,122679,122680,122682,122684],{"class":681,"line":909},[679,122681,40148],{"class":693},[679,122683,121783],{"class":880},[679,122685,17545],{"class":693},[679,122687,122688,122690,122692,122694],{"class":681,"line":918},[679,122689,40148],{"class":693},[679,122691,47833],{"class":880},[679,122693,96071],{"class":693},[679,122695,122696],{"class":1400},"// short for getResult().getOutput().getContent();\n",[679,122698,122699],{"class":681,"line":935},[679,122700,996],{"class":693},[5909,122702,122704],{"id":122703},"step-4-test-the-application","Step 4: Test the Application",[651,122706,122707,122708,51393,122710,664],{},"Use an HTTP client tool like Postman or IntelliJ's built-in HTTP client to test the API. Alternatively, you can use ",[676,122709,41991],{},[676,122711,51396],{},[669,122713,122715],{"className":7993,"code":122714,"language":7995,"meta":674,"style":674},"$ http :8080/\n",[676,122716,122717],{"__ignoreMap":674},[679,122718,122719,122721,122724],{"class":681,"line":682},[679,122720,30246],{"class":880},[679,122722,122723],{"class":689}," http",[679,122725,122726],{"class":689}," :8080/\n",[651,122728,122729],{},"You should get response that includes a funny dad joke about dogs. If something goes wrong take a look at the console output and make sure your properties are set up correctly.",[4542,122731,122733],{"id":122732},"structured-output-handling","Structured Output Handling",[651,122735,122736],{},"One of the coolest new features is the ability to handle structured output. Let's create an example where we ask for an actor's filmography and get paginated results.",[651,122738,122739,122740,122743],{},"First, create a ",[676,122741,122742],{},"record"," for the structured output:",[669,122745,122747],{"className":4107,"code":122746,"language":4109,"meta":674,"style":674},"public record ActorFilms(String actor, List\u003CString> movies) { \n \n}\n",[676,122748,122749,122770,122774],{"__ignoreMap":674},[679,122750,122751,122753,122755,122758,122761,122763,122765,122767],{"class":681,"line":682},[679,122752,6073],{"class":685},[679,122754,86928],{"class":685},[679,122756,122757],{"class":880}," ActorFilms",[679,122759,122760],{"class":693},"(String actor, List",[679,122762,4505],{"class":685},[679,122764,4758],{"class":693},[679,122766,5860],{"class":685},[679,122768,122769],{"class":693}," movies) { \n",[679,122771,122772],{"class":681,"line":790},[679,122773,119642],{"class":693},[679,122775,122776],{"class":681,"line":892},[679,122777,996],{"class":693},[651,122779,122780,122781,122783,122784,122787,122788,122791],{},"Next, create a new endpoint in the ",[676,122782,122514],{}," that will respond to ",[676,122785,122786],{},"/films",". Instead of using the content method to return a string you can use the ",[676,122789,122790],{},".enttity()"," method to map the response to a type. In this case we want the response in the form of record type which includes the actors name a list of movies they have appeared in.",[669,122793,122795],{"className":4107,"code":122794,"language":4109,"meta":674,"style":674},"@RestController\npublic class ActorController {\n\n private final ChatClient chatClient;\n\n public ActorController(ChatClient.Builder builder) {\n this.chatClient = builder\n .build();\n }\n\n @GetMapping(\"/films\")\n public ActorFilms getActorFilms() {\n return chatClient.prompt()\n .user(\"Generate a filmography for a Anthony Hopkins.\")\n .call()\n .entity(ActorFilms.class);\n }\n\n}\n",[676,122796,122797,122803,122814,122818,122826,122830,122842,122852,122860,122864,122868,122881,122893,122903,122916,122924,122934,122938,122942],{"__ignoreMap":674},[679,122798,122799,122801],{"class":681,"line":682},[679,122800,4116],{"class":693},[679,122802,9212],{"class":685},[679,122804,122805,122807,122809,122812],{"class":681,"line":790},[679,122806,6073],{"class":685},[679,122808,4512],{"class":685},[679,122810,122811],{"class":880}," ActorController",[679,122813,884],{"class":693},[679,122815,122816],{"class":681,"line":892},[679,122817,889],{"emptyLinePlaceholder":797},[679,122819,122820,122822,122824],{"class":681,"line":901},[679,122821,9232],{"class":685},[679,122823,12768],{"class":685},[679,122825,121650],{"class":693},[679,122827,122828],{"class":681,"line":909},[679,122829,889],{"emptyLinePlaceholder":797},[679,122831,122832,122834,122836,122838,122840],{"class":681,"line":918},[679,122833,6089],{"class":685},[679,122835,122811],{"class":880},[679,122837,121663],{"class":693},[679,122839,90934],{"class":2099},[679,122841,4390],{"class":693},[679,122843,122844,122846,122848,122850],{"class":681,"line":935},[679,122845,7862],{"class":931},[679,122847,121674],{"class":693},[679,122849,686],{"class":685},[679,122851,119021],{"class":693},[679,122853,122854,122856,122858],{"class":681,"line":944},[679,122855,73482],{"class":693},[679,122857,23612],{"class":880},[679,122859,9317],{"class":693},[679,122861,122862],{"class":681,"line":959},[679,122863,985],{"class":693},[679,122865,122866],{"class":681,"line":964},[679,122867,889],{"emptyLinePlaceholder":797},[679,122869,122870,122872,122874,122876,122879],{"class":681,"line":977},[679,122871,6872],{"class":693},[679,122873,20852],{"class":685},[679,122875,745],{"class":693},[679,122877,122878],{"class":689},"\"/films\"",[679,122880,1339],{"class":693},[679,122882,122883,122885,122888,122891],{"class":681,"line":982},[679,122884,6089],{"class":685},[679,122886,122887],{"class":693}," ActorFilms ",[679,122889,122890],{"class":880},"getActorFilms",[679,122892,2667],{"class":693},[679,122894,122895,122897,122899,122901],{"class":681,"line":988},[679,122896,9444],{"class":685},[679,122898,121763],{"class":693},[679,122900,55494],{"class":880},[679,122902,17545],{"class":693},[679,122904,122905,122907,122909,122911,122914],{"class":681,"line":993},[679,122906,73482],{"class":693},[679,122908,9575],{"class":880},[679,122910,745],{"class":693},[679,122912,122913],{"class":689},"\"Generate a filmography for a Anthony Hopkins.\"",[679,122915,1339],{"class":693},[679,122917,122918,122920,122922],{"class":681,"line":2129},[679,122919,73482],{"class":693},[679,122921,121783],{"class":880},[679,122923,17545],{"class":693},[679,122925,122926,122928,122931],{"class":681,"line":2140},[679,122927,73482],{"class":693},[679,122929,122930],{"class":880},"entity",[679,122932,122933],{"class":693},"(ActorFilms.class);\n",[679,122935,122936],{"class":681,"line":2145},[679,122937,985],{"class":693},[679,122939,122940],{"class":681,"line":2154},[679,122941,889],{"emptyLinePlaceholder":797},[679,122943,122944],{"class":681,"line":2159},[679,122945,996],{"class":693},[651,122947,122948],{},"Run the application again and use your HTTP client to hit the new endpoint:",[669,122950,122954],{"className":122951,"code":122952,"language":122953,"meta":674,"style":674},"language-sh shiki shiki-themes github-light github-dark github-light","$ http localhost:8080/films\n","sh",[676,122955,122956],{"__ignoreMap":674},[679,122957,122958,122960,122962],{"class":681,"line":682},[679,122959,30246],{"class":880},[679,122961,122723],{"class":689},[679,122963,122964],{"class":689}," localhost:8080/films\n",[651,122966,122967],{},"You should see a JSON response with the actor's name and a list of movies.",[669,122969,122971],{"className":28439,"code":122970,"language":28441,"meta":674,"style":674},"{\n \"actor\": \"Anthony Hopkins\",\n \"movies\": [\n \"The Silence of the Lambs\",\n \"Hannibal\",\n \"Red Dragon\",\n \"The Remains of the Day\",\n \"Nixon\",\n \"The Mask of Zorro\",\n \"Meet Joe Black\",\n \"Thor\",\n \"The Father\",\n \"Legends of the Fall\",\n \"Bram Stoker's Dracula\",\n \"The Elephant Man\",\n \"Magic\",\n \"The World's Fastest Indian\",\n \"Fracture\",\n \"The Rite\",\n \"Westworld (TV series)\",\n \"Instinct\",\n \"Proof\",\n \"The Two Popes\"\n ]\n}\n\n",[676,122972,122973,122977,122989,122996,123003,123010,123017,123024,123031,123038,123045,123052,123059,123066,123073,123080,123087,123094,123101,123108,123115,123122,123129,123134,123138],{"__ignoreMap":674},[679,122974,122975],{"class":681,"line":682},[679,122976,28448],{"class":693},[679,122978,122979,122982,122984,122987],{"class":681,"line":790},[679,122980,122981],{"class":931}," \"actor\"",[679,122983,4282],{"class":693},[679,122985,122986],{"class":689},"\"Anthony Hopkins\"",[679,122988,12083],{"class":693},[679,122990,122991,122994],{"class":681,"line":892},[679,122992,122993],{"class":931}," \"movies\"",[679,122995,28491],{"class":693},[679,122997,122998,123001],{"class":681,"line":901},[679,122999,123000],{"class":689}," \"The Silence of the Lambs\"",[679,123002,12083],{"class":693},[679,123004,123005,123008],{"class":681,"line":909},[679,123006,123007],{"class":689}," \"Hannibal\"",[679,123009,12083],{"class":693},[679,123011,123012,123015],{"class":681,"line":918},[679,123013,123014],{"class":689}," \"Red Dragon\"",[679,123016,12083],{"class":693},[679,123018,123019,123022],{"class":681,"line":935},[679,123020,123021],{"class":689}," \"The Remains of the Day\"",[679,123023,12083],{"class":693},[679,123025,123026,123029],{"class":681,"line":944},[679,123027,123028],{"class":689}," \"Nixon\"",[679,123030,12083],{"class":693},[679,123032,123033,123036],{"class":681,"line":959},[679,123034,123035],{"class":689}," \"The Mask of Zorro\"",[679,123037,12083],{"class":693},[679,123039,123040,123043],{"class":681,"line":964},[679,123041,123042],{"class":689}," \"Meet Joe Black\"",[679,123044,12083],{"class":693},[679,123046,123047,123050],{"class":681,"line":977},[679,123048,123049],{"class":689}," \"Thor\"",[679,123051,12083],{"class":693},[679,123053,123054,123057],{"class":681,"line":982},[679,123055,123056],{"class":689}," \"The Father\"",[679,123058,12083],{"class":693},[679,123060,123061,123064],{"class":681,"line":988},[679,123062,123063],{"class":689}," \"Legends of the Fall\"",[679,123065,12083],{"class":693},[679,123067,123068,123071],{"class":681,"line":993},[679,123069,123070],{"class":689}," \"Bram Stoker's Dracula\"",[679,123072,12083],{"class":693},[679,123074,123075,123078],{"class":681,"line":2129},[679,123076,123077],{"class":689}," \"The Elephant Man\"",[679,123079,12083],{"class":693},[679,123081,123082,123085],{"class":681,"line":2140},[679,123083,123084],{"class":689}," \"Magic\"",[679,123086,12083],{"class":693},[679,123088,123089,123092],{"class":681,"line":2145},[679,123090,123091],{"class":689}," \"The World's Fastest Indian\"",[679,123093,12083],{"class":693},[679,123095,123096,123099],{"class":681,"line":2154},[679,123097,123098],{"class":689}," \"Fracture\"",[679,123100,12083],{"class":693},[679,123102,123103,123106],{"class":681,"line":2159},[679,123104,123105],{"class":689}," \"The Rite\"",[679,123107,12083],{"class":693},[679,123109,123110,123113],{"class":681,"line":2164},[679,123111,123112],{"class":689}," \"Westworld (TV series)\"",[679,123114,12083],{"class":693},[679,123116,123117,123120],{"class":681,"line":3134},[679,123118,123119],{"class":689}," \"Instinct\"",[679,123121,12083],{"class":693},[679,123123,123124,123127],{"class":681,"line":3139},[679,123125,123126],{"class":689}," \"Proof\"",[679,123128,12083],{"class":693},[679,123130,123131],{"class":681,"line":3144},[679,123132,123133],{"class":689}," \"The Two Popes\"\n",[679,123135,123136],{"class":681,"line":3149},[679,123137,52918],{"class":693},[679,123139,123140],{"class":681,"line":3169},[679,123141,996],{"class":693},[4542,123143,123145],{"id":123144},"streaming-responses","Streaming Responses",[651,123147,123148,123149,123151],{},"To improve the user experience, especially for long responses, we can stream responses as they're generated. Modify the ",[676,123150,122514],{}," to include a streaming endpoint:",[669,123153,123155],{"className":4107,"code":123154,"language":4109,"meta":674,"style":674},"@GetMapping(\"/stream\")\npublic Flux\u003CString> stream(@RequestParam(\n value = \"message\",\n defaultValue = \"I'm visiting San Francisco next month, what are 10 places I must visit?\") String message) {\n return chatClient.prompt()\n .user(message)\n .stream()\n .content();\n}\n",[676,123156,123157,123170,123192,123203,123215,123225,123233,123241,123249],{"__ignoreMap":674},[679,123158,123159,123161,123163,123165,123168],{"class":681,"line":682},[679,123160,4116],{"class":693},[679,123162,20852],{"class":685},[679,123164,745],{"class":693},[679,123166,123167],{"class":689},"\"/stream\"",[679,123169,1339],{"class":693},[679,123171,123172,123174,123177,123179,123181,123183,123186,123188,123190],{"class":681,"line":790},[679,123173,6073],{"class":685},[679,123175,123176],{"class":693}," Flux",[679,123178,4505],{"class":685},[679,123180,4758],{"class":693},[679,123182,5860],{"class":685},[679,123184,123185],{"class":880}," stream",[679,123187,73246],{"class":693},[679,123189,73249],{"class":685},[679,123191,21337],{"class":693},[679,123193,123194,123197,123199,123201],{"class":681,"line":892},[679,123195,123196],{"class":931}," value",[679,123198,6883],{"class":685},[679,123200,121741],{"class":689},[679,123202,12083],{"class":693},[679,123204,123205,123208,123210,123213],{"class":681,"line":901},[679,123206,123207],{"class":931}," defaultValue",[679,123209,6883],{"class":685},[679,123211,123212],{"class":689}," \"I'm visiting San Francisco next month, what are 10 places I must visit?\"",[679,123214,122659],{"class":693},[679,123216,123217,123219,123221,123223],{"class":681,"line":909},[679,123218,21478],{"class":685},[679,123220,121763],{"class":693},[679,123222,55494],{"class":880},[679,123224,17545],{"class":693},[679,123226,123227,123229,123231],{"class":681,"line":918},[679,123228,40148],{"class":693},[679,123230,9575],{"class":880},[679,123232,121776],{"class":693},[679,123234,123235,123237,123239],{"class":681,"line":935},[679,123236,40148],{"class":693},[679,123238,87323],{"class":880},[679,123240,17545],{"class":693},[679,123242,123243,123245,123247],{"class":681,"line":944},[679,123244,40148],{"class":693},[679,123246,47833],{"class":880},[679,123248,9317],{"class":693},[679,123250,123251],{"class":681,"line":959},[679,123252,996],{"class":693},[651,123254,123255,123256,123258],{},"Again, run the application and use ",[676,123257,51396],{}," to call the streaming endpoint:",[669,123260,123262],{"className":122951,"code":123261,"language":122953,"meta":674,"style":674},"$ http --stream 8080/stream\n",[676,123263,123264],{"__ignoreMap":674},[679,123265,123266,123268,123270,123273],{"class":681,"line":682},[679,123267,30246],{"class":880},[679,123269,122723],{"class":689},[679,123271,123272],{"class":931}," --stream",[679,123274,123275],{"class":689}," 8080/stream\n",[651,123277,123278],{},"You'll start receiving responses as they're generated, improving the perceived performance.",[4542,123280,122362],{"id":123281},"in-memory-conversational-history",[651,123283,123284],{},"It's easy to forget that the web is stateless and doesn't remember our previous conversations. We forget this because we\nused products like ChatGPT, and it is able to remember our previous conversations allowing us to continue the conversation with the LLM. You need to remember that this is the product ChatGPT, not the LLM GPT-4o.",[651,123286,123287],{},"If you were to build out a simple example where you sent a message notifying the LLM of your name and then in the very next message you asked it \"What is my name?\" it will not be able to tell you. However, If you can send along conversational context it will be able to answer that question.",[651,123289,88071,123290,123293,123294,123296],{},[676,123291,123292],{},"StatefulContoller"," and this time when you're building an instance of the ",[676,123295,122385],{}," set a new default advisor that will be able to remember previous conversations:",[669,123298,123300],{"className":4107,"code":123299,"language":4109,"meta":674,"style":674},"@RestController\npublic class StatefulController {\n\n private final ChatClient chatClient;\n\n public StatefulController(ChatClient.Builder builder) {\n this.chatClient = builder\n .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))\n .build();\n }\n\n @GetMapping(\"/chat\")\n public String home(@RequestParam String message) {\n return chatClient.prompt()\n .user(message)\n .call()\n .content();\n }\n\n}\n",[676,123301,123302,123308,123319,123323,123331,123335,123347,123357,123377,123385,123389,123393,123406,123424,123434,123442,123450,123458,123462,123466],{"__ignoreMap":674},[679,123303,123304,123306],{"class":681,"line":682},[679,123305,4116],{"class":693},[679,123307,9212],{"class":685},[679,123309,123310,123312,123314,123317],{"class":681,"line":790},[679,123311,6073],{"class":685},[679,123313,4512],{"class":685},[679,123315,123316],{"class":880}," StatefulController",[679,123318,884],{"class":693},[679,123320,123321],{"class":681,"line":892},[679,123322,889],{"emptyLinePlaceholder":797},[679,123324,123325,123327,123329],{"class":681,"line":901},[679,123326,9232],{"class":685},[679,123328,12768],{"class":685},[679,123330,121650],{"class":693},[679,123332,123333],{"class":681,"line":909},[679,123334,889],{"emptyLinePlaceholder":797},[679,123336,123337,123339,123341,123343,123345],{"class":681,"line":918},[679,123338,6089],{"class":685},[679,123340,123316],{"class":880},[679,123342,121663],{"class":693},[679,123344,90934],{"class":2099},[679,123346,4390],{"class":693},[679,123348,123349,123351,123353,123355],{"class":681,"line":935},[679,123350,7862],{"class":931},[679,123352,121674],{"class":693},[679,123354,686],{"class":685},[679,123356,119021],{"class":693},[679,123358,123359,123361,123363,123365,123367,123369,123371,123373,123375],{"class":681,"line":944},[679,123360,73482],{"class":693},[679,123362,121917],{"class":880},[679,123364,745],{"class":693},[679,123366,8930],{"class":685},[679,123368,121924],{"class":880},[679,123370,745],{"class":693},[679,123372,8930],{"class":685},[679,123374,121931],{"class":880},[679,123376,104226],{"class":693},[679,123378,123379,123381,123383],{"class":681,"line":959},[679,123380,73482],{"class":693},[679,123382,23612],{"class":880},[679,123384,9317],{"class":693},[679,123386,123387],{"class":681,"line":964},[679,123388,985],{"class":693},[679,123390,123391],{"class":681,"line":977},[679,123392,889],{"emptyLinePlaceholder":797},[679,123394,123395,123397,123399,123401,123404],{"class":681,"line":982},[679,123396,6872],{"class":693},[679,123398,20852],{"class":685},[679,123400,745],{"class":693},[679,123402,123403],{"class":689},"\"/chat\"",[679,123405,1339],{"class":693},[679,123407,123408,123410,123412,123414,123416,123418,123420,123422],{"class":681,"line":988},[679,123409,6089],{"class":685},[679,123411,9289],{"class":693},[679,123413,12642],{"class":880},[679,123415,73246],{"class":693},[679,123417,73249],{"class":685},[679,123419,9289],{"class":693},[679,123421,20198],{"class":2099},[679,123423,4390],{"class":693},[679,123425,123426,123428,123430,123432],{"class":681,"line":993},[679,123427,9444],{"class":685},[679,123429,121763],{"class":693},[679,123431,55494],{"class":880},[679,123433,17545],{"class":693},[679,123435,123436,123438,123440],{"class":681,"line":2129},[679,123437,73482],{"class":693},[679,123439,9575],{"class":880},[679,123441,121776],{"class":693},[679,123443,123444,123446,123448],{"class":681,"line":2140},[679,123445,73482],{"class":693},[679,123447,121783],{"class":880},[679,123449,17545],{"class":693},[679,123451,123452,123454,123456],{"class":681,"line":2145},[679,123453,73482],{"class":693},[679,123455,47833],{"class":880},[679,123457,9317],{"class":693},[679,123459,123460],{"class":681,"line":2154},[679,123461,985],{"class":693},[679,123463,123464],{"class":681,"line":2159},[679,123465,889],{"emptyLinePlaceholder":797},[679,123467,123468],{"class":681,"line":2164},[679,123469,996],{"class":693},[651,123471,123255,123472,123474],{},[676,123473,51396],{}," to call the chat endpoint:",[669,123476,123478],{"className":122951,"code":123477,"language":122953,"meta":674,"style":674},"$ http 8080/chat message==\"My name is Dan\"\n",[676,123479,123480],{"__ignoreMap":674},[679,123481,123482,123484,123486,123489],{"class":681,"line":682},[679,123483,30246],{"class":880},[679,123485,122723],{"class":689},[679,123487,123488],{"class":689}," 8080/chat",[679,123490,123491],{"class":689}," message==\"My name is Dan\"\n",[651,123493,123494],{},"After it responds run another example where you ask it your name and it should be able to respond.",[669,123496,123498],{"className":122951,"code":123497,"language":122953,"meta":674,"style":674},"$ http 8080/chat message==\"What is my name?\"\n",[676,123499,123500],{"__ignoreMap":674},[679,123501,123502,123504,123506,123508],{"class":681,"line":682},[679,123503,30246],{"class":880},[679,123505,122723],{"class":689},[679,123507,123488],{"class":689},[679,123509,123510],{"class":689}," message==\"What is my name?\"\n",[4542,123512,9042],{"id":9041},[651,123514,123515],{},"That's a wrap! Spring AI 1.0.0 M1 brings in a host of new features that make it easier than ever to build intelligent applications. The new fluent API, structured output and in memory advisors are just a few examples of how this release simplifies the development process.",[651,123517,123518],{},"Everything we went through today was so easy to get started with building intelligent applications with Spring AI, and I'm really excited about the future of this project. Be sure to explore and experiment with these new features. They are designed to make your development life easier and more efficient.",[651,123520,123521],{},"I hope you enjoyed this detailed walkthrough. Stay tuned for more updates and tutorials on Spring AI, and always friends...",[651,123523,41105,123524,69920],{},[41107,123525],{},[786,123527,123528],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":123530},[123531,123534,123540,123541,123542,123543],{"id":122340,"depth":790,"text":122341,"children":123532},[123533],{"id":122374,"depth":892,"text":122353},{"id":54715,"depth":790,"text":95749,"children":123535},[123536,123537,123538,123539],{"id":122397,"depth":892,"text":122398},{"id":122459,"depth":892,"text":122460},{"id":122507,"depth":892,"text":122508},{"id":122703,"depth":892,"text":122704},{"id":122732,"depth":790,"text":122733},{"id":123144,"depth":790,"text":123145},{"id":123281,"depth":790,"text":122362},{"id":9041,"depth":790,"text":9042},"In this article you learn about what is new in the latest release of Spring AI, 1.0.0 M1. The milestone comes packed full of features headlined by the new Chat Client that has a fluent API.",{"slug":123546,"date":123547,"published":797,"author":798,"tags":123548,"cover":123550,"keywords":123551,"video":123552,"github":123553},"spring-ai-m1","2024-06-20T17:00:00.000Z",[7077,123549],"Spring AI","./spring_ai_m1_03.png","Spring Framework, Spring Boot, Spring AI, AI, Artificial Intelligence","https://youtube.com/embed/De9a-TaJImI","https://github.com/danvega/spring-into-ai",{"title":96,"description":123544},"blog/2024/06/20/spring-ai-m1","OL_olD_V5myYnDC1lXeZASuhBl1eK6vlYbLRgixG6VM",{"id":123558,"title":93,"body":123559,"description":124459,"extension":793,"meta":124460,"navigation":797,"path":94,"seo":124467,"stem":124468,"__hash__":124469},"content/blog/2024/07/10/java-gpt-4o.md",{"type":648,"value":123560,"toc":124442},[123561,123564,123568,123571,123575,123578,123583,123587,123590,123613,123683,123688,123692,123695,123720,123781,123785,123788,123792,123795,123854,123858,123865,123962,123966,123972,124028,124032,124035,124041,124045,124048,124073,124077,124080,124083,124087,124090,124103,124108,124110,124113,124121,124437,124440],[651,123562,123563],{},"OpenAI has just released its newest model, GPT-4o, also known as Omni model. This advanced model is notably available through the API. As a Java developer, there are several highlights you would be excited about – 50% lower pricing, two times faster latency, and five times the rate limits. I’ve performed some preliminary testing, and it seems impressively fast. So, let’s explore how we can use this API in a pure Java program.",[4542,123565,123567],{"id":123566},"no-external-dependencies-no-problem","No External Dependencies? No Problem!",[651,123569,123570],{},"In this tutorial, we won’t be using any external dependencies; it’s just going to be a straightforward Java app. Our goal is to create a new Java application that communicates with OpenAI's GPT-40.",[4542,123572,123574],{"id":123573},"tools-of-the-trade-intellij","Tools of the Trade: Intellij",[651,123576,123577],{},"I'm starting with Intellij's ultimate edition, although the community edition should work fine as well. However, always use the IDE or text editor you are most comfortable with because that's the best editor for you.",[651,123579,123580],{},[660,123581],{"alt":674,"src":123582},"https://image.mux.com/wM5LGAD9LU6kUAN6N5SMMNb02b2yMxu01cciFoGVjf600E/thumbnail.png?time=35.434782608696",[4542,123584,123586],{"id":123585},"setting-up-your-java-project","Setting Up Your Java Project",[651,123588,123589],{},"Here’s a step-by-step guide to creating your Java project. Let's dive in.",[27665,123591,123592,123601],{},[5332,123593,123594,123597,123598,664],{},[2939,123595,123596],{},"Create a New Project",": Open the new wizard in Intellij and create a new Java project named ",[676,123599,123600],{},"hello GPT 40",[5332,123602,123603,123606,123607,123609,123610,664],{},[2939,123604,123605],{},"Maven Build Tool",": Though we'll not use any external dependencies for this video, we start with Maven since you'll likely require other dependencies later. Set your group ID to ",[676,123608,92925],{}," and the artifact ID to ",[676,123611,123612],{},"hello-gpt-4o",[669,123614,123616],{"className":4107,"code":123615,"language":4109,"meta":674,"style":674},"public class Application {\n \n public static void main(String[] args) {\n String apiKey = System.getenv(\"OPENAI_API_KEY\");\n }\n \n}\n",[676,123617,123618,123628,123632,123652,123671,123675,123679],{"__ignoreMap":674},[679,123619,123620,123622,123624,123626],{"class":681,"line":682},[679,123621,6073],{"class":685},[679,123623,4512],{"class":685},[679,123625,16878],{"class":880},[679,123627,884],{"class":693},[679,123629,123630],{"class":681,"line":790},[679,123631,119642],{"class":693},[679,123633,123634,123636,123638,123640,123642,123644,123646,123648,123650],{"class":681,"line":892},[679,123635,6089],{"class":685},[679,123637,6092],{"class":685},[679,123639,6095],{"class":685},[679,123641,6098],{"class":880},[679,123643,745],{"class":693},[679,123645,4758],{"class":685},[679,123647,16901],{"class":693},[679,123649,6108],{"class":2099},[679,123651,4390],{"class":693},[679,123653,123654,123657,123659,123661,123664,123666,123669],{"class":681,"line":901},[679,123655,123656],{"class":693}," String apiKey ",[679,123658,686],{"class":685},[679,123660,108725],{"class":693},[679,123662,123663],{"class":880},"getenv",[679,123665,745],{"class":693},[679,123667,123668],{"class":689},"\"OPENAI_API_KEY\"",[679,123670,1208],{"class":693},[679,123672,123673],{"class":681,"line":909},[679,123674,985],{"class":693},[679,123676,123677],{"class":681,"line":918},[679,123678,119642],{"class":693},[679,123680,123681],{"class":681,"line":935},[679,123682,996],{"class":693},[651,123684,123685],{},[660,123686],{"alt":674,"src":123687},"https://image.mux.com/wM5LGAD9LU6kUAN6N5SMMNb02b2yMxu01cciFoGVjf600E/thumbnail.png?time=135.83333333333",[4542,123689,123691],{"id":123690},"setting-up-the-api-key","Setting Up the API Key",[651,123693,123694],{},"First, you'll need an OpenAI API key. Here’s how you can set it up:",[27665,123696,123697,123708,123714],{},[5332,123698,123699,123702,123703,123707],{},[2939,123700,123701],{},"Get Your API Key",": Head over to ",[812,123704,123706],{"href":122466,"rel":123705},[816],"OpenAI's website",", log into your account or create a new one, and navigate to the API keys section.",[5332,123709,123710,123713],{},[2939,123711,123712],{},"Create an API Key",": Generate a new API key and copy it.",[5332,123715,123716,123719],{},[2939,123717,123718],{},"Environment Variable",": Instead of hardcoding your API key, use environment variables. This ensures your keys aren't exposed in your codebase.",[669,123721,123723],{"className":4107,"code":123722,"language":4109,"meta":674,"style":674},"var apiKey = System.getenv(\"OPENAI_API_KEY\");\n\nif(apiKey == null){\n throw new IllegalStateException(\"Missing OpenAI API key!\");\n}\n",[676,123724,123725,123744,123748,123761,123777],{"__ignoreMap":674},[679,123726,123727,123729,123732,123734,123736,123738,123740,123742],{"class":681,"line":682},[679,123728,73698],{"class":685},[679,123730,123731],{"class":693}," apiKey ",[679,123733,686],{"class":685},[679,123735,108725],{"class":693},[679,123737,123663],{"class":880},[679,123739,745],{"class":693},[679,123741,123668],{"class":689},[679,123743,1208],{"class":693},[679,123745,123746],{"class":681,"line":790},[679,123747,889],{"emptyLinePlaceholder":797},[679,123749,123750,123752,123755,123757,123759],{"class":681,"line":892},[679,123751,1217],{"class":685},[679,123753,123754],{"class":693},"(apiKey ",[679,123756,2304],{"class":685},[679,123758,2307],{"class":931},[679,123760,9533],{"class":693},[679,123762,123763,123765,123767,123770,123772,123775],{"class":681,"line":901},[679,123764,49013],{"class":685},[679,123766,2054],{"class":685},[679,123768,123769],{"class":880}," IllegalStateException",[679,123771,745],{"class":693},[679,123773,123774],{"class":689},"\"Missing OpenAI API key!\"",[679,123776,1208],{"class":693},[679,123778,123779],{"class":681,"line":909},[679,123780,996],{"class":693},[4542,123782,123784],{"id":123783},"constructing-the-api-request","Constructing the API Request",[651,123786,123787],{},"Here we build the body of the request and the actual request itself:",[5909,123789,123791],{"id":123790},"create-the-request-body","Create the Request Body",[651,123793,123794],{},"The request body is a JSON string that includes the model, messages, and other parameters. Thanks to Java's multiline Strings you can easily build out a JSON request body.",[669,123796,123798],{"className":4107,"code":123797,"language":4109,"meta":674,"style":674},"var body = \"\"\"\n{\n \"model\": \"gpt-4o\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a good dad joke about cats\"\n }\n ]\n}\"\"\";\n",[676,123799,123800,123811,123815,123820,123825,123829,123834,123839,123843,123847],{"__ignoreMap":674},[679,123801,123802,123804,123807,123809],{"class":681,"line":682},[679,123803,73698],{"class":685},[679,123805,123806],{"class":693}," body ",[679,123808,686],{"class":685},[679,123810,105376],{"class":689},[679,123812,123813],{"class":681,"line":790},[679,123814,28448],{"class":689},[679,123816,123817],{"class":681,"line":892},[679,123818,123819],{"class":689}," \"model\": \"gpt-4o\",\n",[679,123821,123822],{"class":681,"line":901},[679,123823,123824],{"class":689}," \"messages\": [\n",[679,123826,123827],{"class":681,"line":909},[679,123828,101453],{"class":689},[679,123830,123831],{"class":681,"line":918},[679,123832,123833],{"class":689}," \"role\": \"user\",\n",[679,123835,123836],{"class":681,"line":935},[679,123837,123838],{"class":689}," \"content\": \"Tell me a good dad joke about cats\"\n",[679,123840,123841],{"class":681,"line":944},[679,123842,1290],{"class":689},[679,123844,123845],{"class":681,"line":959},[679,123846,52918],{"class":689},[679,123848,123849,123852],{"class":681,"line":964},[679,123850,123851],{"class":689},"}\"\"\"",[679,123853,1186],{"class":693},[5909,123855,123857],{"id":123856},"constructing-the-http-request","Constructing the HTTP Request",[651,123859,123860,123861,123864],{},"We use Java's built-in ",[676,123862,123863],{},"HttpClient"," to send the request.",[669,123866,123868],{"className":4107,"code":123867,"language":4109,"meta":674,"style":674},"HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(\"https://api.openai.com/v1/chat/completions\"))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(body))\n .build();\n",[676,123869,123870,123885,123903,123920,123939,123954],{"__ignoreMap":674},[679,123871,123872,123875,123877,123880,123883],{"class":681,"line":682},[679,123873,123874],{"class":693},"HttpRequest request ",[679,123876,686],{"class":685},[679,123878,123879],{"class":693}," HttpRequest.",[679,123881,123882],{"class":880},"newBuilder",[679,123884,17545],{"class":693},[679,123886,123887,123889,123891,123894,123896,123898,123901],{"class":681,"line":790},[679,123888,21331],{"class":693},[679,123890,4836],{"class":880},[679,123892,123893],{"class":693},"(URI.",[679,123895,5697],{"class":880},[679,123897,745],{"class":693},[679,123899,123900],{"class":689},"\"https://api.openai.com/v1/chat/completions\"",[679,123902,40143],{"class":693},[679,123904,123905,123907,123909,123911,123914,123916,123918],{"class":681,"line":892},[679,123906,21331],{"class":693},[679,123908,91684],{"class":880},[679,123910,745],{"class":693},[679,123912,123913],{"class":689},"\"Content-Type\"",[679,123915,2797],{"class":693},[679,123917,79927],{"class":689},[679,123919,1339],{"class":693},[679,123921,123922,123924,123926,123928,123930,123932,123934,123936],{"class":681,"line":901},[679,123923,21331],{"class":693},[679,123925,91684],{"class":880},[679,123927,745],{"class":693},[679,123929,91689],{"class":689},[679,123931,2797],{"class":693},[679,123933,91694],{"class":689},[679,123935,3059],{"class":685},[679,123937,123938],{"class":693}," apiKey)\n",[679,123940,123941,123943,123945,123948,123951],{"class":681,"line":909},[679,123942,21331],{"class":693},[679,123944,93764],{"class":880},[679,123946,123947],{"class":693},"(HttpRequest.BodyPublishers.",[679,123949,123950],{"class":880},"ofString",[679,123952,123953],{"class":693},"(body))\n",[679,123955,123956,123958,123960],{"class":681,"line":918},[679,123957,21331],{"class":693},[679,123959,23612],{"class":880},[679,123961,9317],{"class":693},[4542,123963,123965],{"id":123964},"sending-the-request","Sending the Request",[651,123967,123968,123969,123971],{},"Finally, we send the request using the ",[676,123970,123863],{}," and handle the response.",[669,123973,123975],{"className":4107,"code":123974,"language":4109,"meta":674,"style":674},"var client = HttpClient.newHttpClient();\nvar response = client.send(request, HttpResponse.BodyHandlers.ofString());\nSystem.out.println(response.body());\n",[676,123976,123977,123994,124015],{"__ignoreMap":674},[679,123978,123979,123981,123984,123986,123989,123992],{"class":681,"line":682},[679,123980,73698],{"class":685},[679,123982,123983],{"class":693}," client ",[679,123985,686],{"class":685},[679,123987,123988],{"class":693}," HttpClient.",[679,123990,123991],{"class":880},"newHttpClient",[679,123993,9317],{"class":693},[679,123995,123996,123998,124001,124003,124006,124008,124011,124013],{"class":681,"line":790},[679,123997,73698],{"class":685},[679,123999,124000],{"class":693}," response ",[679,124002,686],{"class":685},[679,124004,124005],{"class":693}," client.",[679,124007,9717],{"class":880},[679,124009,124010],{"class":693},"(request, HttpResponse.BodyHandlers.",[679,124012,123950],{"class":880},[679,124014,9431],{"class":693},[679,124016,124017,124019,124021,124024,124026],{"class":681,"line":892},[679,124018,78405],{"class":693},[679,124020,1729],{"class":880},[679,124022,124023],{"class":693},"(response.",[679,124025,3006],{"class":880},[679,124027,9431],{"class":693},[4542,124029,124031],{"id":124030},"running-the-program","Running the Program",[651,124033,124034],{},"Compile and run your program. You should get a quick response with a dad joke about cats:",[669,124036,124039],{"className":124037,"code":124038,"language":11464},[16247],"Why did the cat sit on the computer? Because it wanted to keep an eye on the mouse.\n",[676,124040,124038],{"__ignoreMap":674},[4542,124042,124044],{"id":124043},"important-tips-for-real-world-applications","Important Tips for Real-World Applications",[651,124046,124047],{},"Here are some additional considerations for production-level applications:",[27665,124049,124050,124056,124062,124068],{},[5332,124051,124052,124055],{},[2939,124053,124054],{},"API Key Management",": Avoid hardcoding API keys. Use secure methods to store and retrieve them.",[5332,124057,124058,124061],{},[2939,124059,124060],{},"Error Handling",": Implement robust error handling mechanisms.",[5332,124063,124064,124067],{},[2939,124065,124066],{},"Rate Limits",": Respect OpenAI’s rate limits to avoid service disruptions.",[5332,124069,124070,124072],{},[2939,124071,51430],{},": For more complex JSON parsing tasks, consider using libraries like Jackson.",[5909,124074,124076],{"id":124075},"beyond-basics-advanced-techniques","Beyond Basics: Advanced Techniques",[651,124078,124079],{},"As you dive deeper into building AI applications, you may encounter advanced challenges. Techniques such as creating prompt templates, integrating with vector databases for retrieval-augmented generation (RAG), and setting up function calls for real-time data retrieval can be essential.",[1004,124081,124082],{},"\n\"There’s a lot that goes into building AI applications, and understanding how to work with OpenAI's GPT-40 is just the beginning.\"\n",[4542,124084,124086],{"id":124085},"leveraging-libraries-and-frameworks","Leveraging Libraries and Frameworks",[651,124088,124089],{},"Two noteworthy projects are worth mentioning:",[27665,124091,124092,124098],{},[5332,124093,124094,124097],{},[2939,124095,124096],{},"Langford Chain J",": A Java-centric equivalent of LangChain, tailored for deeper AI integrations.",[5332,124099,124100,124102],{},[2939,124101,123549],{},": If you are already using Spring for backend applications, this can seamlessly integrate AI capabilities into your projects.",[651,124104,124105],{},[660,124106],{"alt":674,"src":124107},"https://image.mux.com/wM5LGAD9LU6kUAN6N5SMMNb02b2yMxu01cciFoGVjf600E/thumbnail.png?time=590.57971014493",[4542,124109,9042],{"id":9041},[651,124111,124112],{},"This was a brief introduction to working with GPT-40 in a pure Java environment. By following these steps, you should be able to set up and interact with OpenAI's API effectively. Happy coding, and don’t forget to explore more advanced techniques as you expand your AI capabilities!",[651,124114,124115,124116,664],{},"Here is the full code example for your reference, and you can find the ",[812,124117,124120],{"href":124118,"rel":124119},"https://github.com/danvega/hello-gpt",[816],"repo here",[669,124122,124124],{"className":4107,"code":124123,"language":4109,"meta":674,"style":674},"package dev.danvega;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Application {\n\n public static void main(String[] args) throws IOException, InterruptedException {\n var apiKey = System.getenv(\"OPENAI_API_KEY\");\n var body = \"\"\"\n {\n \"model\": \"gpt-4o\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me a good dad joke about cats\"\n }\n ]\n }\"\"\";\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(\"https://api.openai.com/v1/chat/completions\"))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(body))\n .build();\n\n var client = HttpClient.newHttpClient();\n var response = client.send(request, HttpResponse.BodyHandlers.ofString());\n System.out.println(response.body());\n }\n \n}\n",[676,124125,124126,124133,124137,124144,124151,124158,124165,124172,124176,124186,124190,124215,124233,124243,124248,124253,124258,124263,124268,124273,124278,124283,124290,124294,124307,124323,124339,124357,124369,124377,124381,124395,124413,124425,124429,124433],{"__ignoreMap":674},[679,124127,124128,124130],{"class":681,"line":682},[679,124129,2543],{"class":685},[679,124131,124132],{"class":693}," dev.danvega;\n",[679,124134,124135],{"class":681,"line":790},[679,124136,889],{"emptyLinePlaceholder":797},[679,124138,124139,124141],{"class":681,"line":892},[679,124140,1999],{"class":685},[679,124142,124143],{"class":693}," java.io.IOException;\n",[679,124145,124146,124148],{"class":681,"line":901},[679,124147,1999],{"class":685},[679,124149,124150],{"class":693}," java.net.URI;\n",[679,124152,124153,124155],{"class":681,"line":909},[679,124154,1999],{"class":685},[679,124156,124157],{"class":693}," java.net.http.HttpClient;\n",[679,124159,124160,124162],{"class":681,"line":918},[679,124161,1999],{"class":685},[679,124163,124164],{"class":693}," java.net.http.HttpRequest;\n",[679,124166,124167,124169],{"class":681,"line":935},[679,124168,1999],{"class":685},[679,124170,124171],{"class":693}," java.net.http.HttpResponse;\n",[679,124173,124174],{"class":681,"line":944},[679,124175,889],{"emptyLinePlaceholder":797},[679,124177,124178,124180,124182,124184],{"class":681,"line":959},[679,124179,6073],{"class":685},[679,124181,4512],{"class":685},[679,124183,16878],{"class":880},[679,124185,884],{"class":693},[679,124187,124188],{"class":681,"line":964},[679,124189,889],{"emptyLinePlaceholder":797},[679,124191,124192,124194,124196,124198,124200,124202,124204,124206,124208,124210,124212],{"class":681,"line":977},[679,124193,6089],{"class":685},[679,124195,6092],{"class":685},[679,124197,6095],{"class":685},[679,124199,6098],{"class":880},[679,124201,745],{"class":693},[679,124203,4758],{"class":685},[679,124205,16901],{"class":693},[679,124207,6108],{"class":2099},[679,124209,2378],{"class":693},[679,124211,9580],{"class":685},[679,124213,124214],{"class":693}," IOException, InterruptedException {\n",[679,124216,124217,124219,124221,124223,124225,124227,124229,124231],{"class":681,"line":982},[679,124218,94003],{"class":685},[679,124220,123731],{"class":693},[679,124222,686],{"class":685},[679,124224,108725],{"class":693},[679,124226,123663],{"class":880},[679,124228,745],{"class":693},[679,124230,123668],{"class":689},[679,124232,1208],{"class":693},[679,124234,124235,124237,124239,124241],{"class":681,"line":988},[679,124236,94003],{"class":685},[679,124238,123806],{"class":693},[679,124240,686],{"class":685},[679,124242,105376],{"class":689},[679,124244,124245],{"class":681,"line":993},[679,124246,124247],{"class":689}," {\n",[679,124249,124250],{"class":681,"line":2129},[679,124251,124252],{"class":689}," \"model\": \"gpt-4o\",\n",[679,124254,124255],{"class":681,"line":2140},[679,124256,124257],{"class":689}," \"messages\": [\n",[679,124259,124260],{"class":681,"line":2145},[679,124261,124262],{"class":689}," {\n",[679,124264,124265],{"class":681,"line":2154},[679,124266,124267],{"class":689}," \"role\": \"user\",\n",[679,124269,124270],{"class":681,"line":2159},[679,124271,124272],{"class":689}," \"content\": \"Tell me a good dad joke about cats\"\n",[679,124274,124275],{"class":681,"line":2164},[679,124276,124277],{"class":689}," }\n",[679,124279,124280],{"class":681,"line":3134},[679,124281,124282],{"class":689}," ]\n",[679,124284,124285,124288],{"class":681,"line":3139},[679,124286,124287],{"class":689}," }\"\"\"",[679,124289,1186],{"class":693},[679,124291,124292],{"class":681,"line":3144},[679,124293,889],{"emptyLinePlaceholder":797},[679,124295,124296,124299,124301,124303,124305],{"class":681,"line":3149},[679,124297,124298],{"class":693}," HttpRequest request ",[679,124300,686],{"class":685},[679,124302,123879],{"class":693},[679,124304,123882],{"class":880},[679,124306,17545],{"class":693},[679,124308,124309,124311,124313,124315,124317,124319,124321],{"class":681,"line":3169},[679,124310,73482],{"class":693},[679,124312,4836],{"class":880},[679,124314,123893],{"class":693},[679,124316,5697],{"class":880},[679,124318,745],{"class":693},[679,124320,123900],{"class":689},[679,124322,40143],{"class":693},[679,124324,124325,124327,124329,124331,124333,124335,124337],{"class":681,"line":3185},[679,124326,73482],{"class":693},[679,124328,91684],{"class":880},[679,124330,745],{"class":693},[679,124332,123913],{"class":689},[679,124334,2797],{"class":693},[679,124336,79927],{"class":689},[679,124338,1339],{"class":693},[679,124340,124341,124343,124345,124347,124349,124351,124353,124355],{"class":681,"line":3194},[679,124342,73482],{"class":693},[679,124344,91684],{"class":880},[679,124346,745],{"class":693},[679,124348,91689],{"class":689},[679,124350,2797],{"class":693},[679,124352,91694],{"class":689},[679,124354,3059],{"class":685},[679,124356,123938],{"class":693},[679,124358,124359,124361,124363,124365,124367],{"class":681,"line":3199},[679,124360,73482],{"class":693},[679,124362,93764],{"class":880},[679,124364,123947],{"class":693},[679,124366,123950],{"class":880},[679,124368,123953],{"class":693},[679,124370,124371,124373,124375],{"class":681,"line":3212},[679,124372,73482],{"class":693},[679,124374,23612],{"class":880},[679,124376,9317],{"class":693},[679,124378,124379],{"class":681,"line":3217},[679,124380,889],{"emptyLinePlaceholder":797},[679,124382,124383,124385,124387,124389,124391,124393],{"class":681,"line":3222},[679,124384,94003],{"class":685},[679,124386,123983],{"class":693},[679,124388,686],{"class":685},[679,124390,123988],{"class":693},[679,124392,123991],{"class":880},[679,124394,9317],{"class":693},[679,124396,124397,124399,124401,124403,124405,124407,124409,124411],{"class":681,"line":3227},[679,124398,94003],{"class":685},[679,124400,124000],{"class":693},[679,124402,686],{"class":685},[679,124404,124005],{"class":693},[679,124406,9717],{"class":880},[679,124408,124010],{"class":693},[679,124410,123950],{"class":880},[679,124412,9431],{"class":693},[679,124414,124415,124417,124419,124421,124423],{"class":681,"line":3232},[679,124416,9592],{"class":693},[679,124418,1729],{"class":880},[679,124420,124023],{"class":693},[679,124422,3006],{"class":880},[679,124424,9431],{"class":693},[679,124426,124427],{"class":681,"line":3499},[679,124428,985],{"class":693},[679,124430,124431],{"class":681,"line":3509},[679,124432,119642],{"class":693},[679,124434,124435],{"class":681,"line":3516},[679,124436,996],{"class":693},[651,124438,124439],{},"By following this guide, you now have the tools and knowledge to build and integrate Java applications with OpenAI’s GPT-4o.",[786,124441,73542],{},{"title":674,"searchDepth":790,"depth":790,"links":124443},[124444,124445,124446,124447,124448,124452,124453,124454,124457,124458],{"id":123566,"depth":790,"text":123567},{"id":123573,"depth":790,"text":123574},{"id":123585,"depth":790,"text":123586},{"id":123690,"depth":790,"text":123691},{"id":123783,"depth":790,"text":123784,"children":124449},[124450,124451],{"id":123790,"depth":892,"text":123791},{"id":123856,"depth":892,"text":123857},{"id":123964,"depth":790,"text":123965},{"id":124030,"depth":790,"text":124031},{"id":124043,"depth":790,"text":124044,"children":124455},[124456],{"id":124075,"depth":892,"text":124076},{"id":124085,"depth":790,"text":124086},{"id":9041,"depth":790,"text":9042},"In this tutorial you will learn how to access OpenAI's newest model, GPT-4o in Java without using any dependencies. As a Java developer, there are several highlights you would be excited about – 50% lower pricing, two times faster latency, and five times the rate limits.",{"slug":124461,"date":124462,"published":797,"author":798,"tags":124463,"cover":124464,"video":124465,"keywords":124466},"java-gpt-4o","2024-07-10T17:00:00.000Z",[36422,122453,97466],"./java-gpt-4o-cover.jpg","https://www.youtube.com/embed/EDJLHWcFvpQ","Java, OpenAI, GPT-4o, Maven",{"title":93,"description":124459},"blog/2024/07/10/java-gpt-4o","UODuu9jsNHcK4zdCjnzpnULvXhz-rCwsFMKdDAbJvDk",{"id":124471,"title":90,"body":124472,"description":125774,"extension":793,"meta":125775,"navigation":797,"path":91,"seo":125782,"stem":125783,"__hash__":125784},"content/blog/2024/07/18/spring-boot-tailwind.md",{"type":648,"value":124473,"toc":125760},[124474,124483,124487,124490,124493,124497,124505,124529,124533,124540,124553,124556,124560,124563,124587,124590,124593,124606,124615,124631,124634,124649,124662,124725,124729,124739,124766,124769,124773,124776,124813,124833,124836,124850,124857,124861,124871,125069,125072,125076,125079,125146,125149,125151,125154,125164,125171,125175,125182,125188,125237,125242,125710,125713,125726,125735,125737,125740,125757],[651,124475,124476,124477,124482],{},"As a Spring Boot developer, you may have found yourself in a situation where you need to add some styling to your web application. One popular CSS framework that has gained a lot of traction in recent years is ",[812,124478,124481],{"href":124479,"rel":124480},"https://tailwindcss.com/",[816],"Tailwind CSS",". In this article, we'll explore how you can integrate Tailwind CSS into your Spring Boot application, ensuring that you only include the styles you need in your production build.",[4542,124484,124486],{"id":124485},"what-is-tailwind-css","What is Tailwind CSS?",[651,124488,124489],{},"Tailwind CSS is a utility-first CSS framework that provides a set of pre-defined classes that you can use to style your HTML elements. Unlike traditional CSS frameworks like Bootstrap, which provide a set of pre-designed components, Tailwind CSS focuses on providing low-level utility classes that you can combine to create your own custom designs.",[651,124491,124492],{},"One of the key benefits of Tailwind CSS is that it allows you to write your styles directly in your HTML, which can make it easier to reason about your application's styling and reduce the amount of CSS you need to write. However, this approach can also lead to a larger CSS file being shipped to production, which is why we'll be exploring a build process to optimize our CSS output.",[4542,124494,124496],{"id":124495},"getting-started-prerequisites","Getting Started & Prerequisites",[651,124498,124499,124500,664],{},"First things first, check out this repo which has all the instructions we'll go through today. You will need to have Node and NPM installed. To verify that they are installed you can run the following commands. If you need to install them I would suggest using a tool like ",[812,124501,124504],{"href":124502,"rel":124503},"https://github.com/nvm-sh/nvm",[816],"Node Version Manager",[669,124506,124508],{"className":7993,"code":124507,"language":7995,"meta":674,"style":674},"node -v # v20.2.0\nnpm -v # 9.8.1\n",[676,124509,124510,124520],{"__ignoreMap":674},[679,124511,124512,124514,124517],{"class":681,"line":682},[679,124513,27318],{"class":880},[679,124515,124516],{"class":931}," -v",[679,124518,124519],{"class":1400}," # v20.2.0\n",[679,124521,124522,124524,124526],{"class":681,"line":790},[679,124523,24568],{"class":880},[679,124525,124516],{"class":931},[679,124527,124528],{"class":1400}," # 9.8.1\n",[4542,124530,124532],{"id":124531},"setting-up-a-spring-boot-project-with-tailwind-css","Setting Up a Spring Boot Project with Tailwind CSS",[651,124534,124535,124536,124539],{},"To get started, we'll create a new Spring Boot project using the ",[812,124537,7117],{"href":7115,"rel":124538},[816],". We'll choose the following options:",[5316,124541,124542,124545,124547,124550],{},[5332,124543,124544],{},"Project: Maven Project",[5332,124546,102049],{},[5332,124548,124549],{},"Spring Boot: Latest version",[5332,124551,124552],{},"Dependencies: Web, Thymeleaf, and Spring Boot DevTools",[651,124554,124555],{},"Once we have our project set up, we'll need to create a new folder called \"frontend\" in the \"src/main\" directory. This is where we'll be doing our Tailwind CSS setup and development.",[5909,124557,124559],{"id":124558},"installing-tailwind-css","Installing Tailwind CSS",[651,124561,124562],{},"The easiest way to get up and running with Tailwind is to include the CDN.",[669,124564,124566],{"className":4496,"code":124565,"language":4498,"meta":674,"style":674},"\u003Cscript src=\"https://cdn.tailwindcss.com\">\u003C/script>\n",[676,124567,124568],{"__ignoreMap":674},[679,124569,124570,124572,124574,124576,124578,124581,124583,124585],{"class":681,"line":682},[679,124571,4505],{"class":693},[679,124573,47668],{"class":4508},[679,124575,5361],{"class":880},[679,124577,686],{"class":693},[679,124579,124580],{"class":689},"\"https://cdn.tailwindcss.com\"",[679,124582,4563],{"class":693},[679,124584,47668],{"class":4508},[679,124586,4519],{"class":693},[651,124588,124589],{},"You can just use the CDN if you're prototyping something out but if this is a real world project you don't want to do that. When you include the CDN you get all the Tailwind CSS utility classes even if you're not using them. What you will want to do is create a new npm project, so you can introduce a build process.",[651,124591,124592],{},"Inside the \"frontend\" folder, we'll need to initialize a new npm project by running the following command in the terminal:",[669,124594,124596],{"className":7993,"code":124595,"language":7995,"meta":674,"style":674},"npm init -y\n",[676,124597,124598],{"__ignoreMap":674},[679,124599,124600,124602,124604],{"class":681,"line":682},[679,124601,24568],{"class":880},[679,124603,36742],{"class":689},[679,124605,44809],{"class":931},[651,124607,124608,124609,106018,124611,124614],{},"This will create a new ",[676,124610,55253],{},[676,124612,124613],{},"/src/main/frontend"," directory. Next, we'll install Tailwind CSS as a development dependency:",[669,124616,124618],{"className":7993,"code":124617,"language":7995,"meta":674,"style":674},"npm install -D tailwindcss\n",[676,124619,124620],{"__ignoreMap":674},[679,124621,124622,124624,124626,124628],{"class":681,"line":682},[679,124623,24568],{"class":880},[679,124625,24571],{"class":689},[679,124627,42645],{"class":931},[679,124629,124630],{"class":689}," tailwindcss\n",[651,124632,124633],{},"With Tailwind CSS installed, we can now generate the initial Tailwind CSS configuration file by running the following command:",[669,124635,124637],{"className":7993,"code":124636,"language":7995,"meta":674,"style":674},"npx tailwindcss init\n",[676,124638,124639],{"__ignoreMap":674},[679,124640,124641,124644,124647],{"class":681,"line":682},[679,124642,124643],{"class":880},"npx",[679,124645,124646],{"class":689}," tailwindcss",[679,124648,44250],{"class":689},[651,124650,124608,124651,124654,124655,124658,124659,124661],{},[676,124652,124653],{},"tailwind.config.js"," file in the frontend folder. You'll need to update this file to tell Tailwind CSS where to find our HTML templates. All of your templates will be in ",[676,124656,124657],{},"/src/main/resources/templates"," so you can update the ",[676,124660,47833],{}," value as shown below.",[669,124663,124665],{"className":25132,"code":124664,"language":25134,"meta":674,"style":674},"/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\"../resources/templates/**/*.{html,js}\"],\n theme: {\n extend: {},\n },\n plugins: [],\n}\n",[676,124666,124667,124680,124692,124702,124707,124712,124716,124721],{"__ignoreMap":674},[679,124668,124669,124672,124675,124678],{"class":681,"line":682},[679,124670,124671],{"class":1400},"/** ",[679,124673,124674],{"class":685},"@type",[679,124676,124677],{"class":880}," {import('tailwindcss').Config}",[679,124679,77179],{"class":1400},[679,124681,124682,124684,124686,124688,124690],{"class":681,"line":790},[679,124683,27515],{"class":931},[679,124685,664],{"class":693},[679,124687,43903],{"class":931},[679,124689,6883],{"class":685},[679,124691,884],{"class":693},[679,124693,124694,124697,124700],{"class":681,"line":892},[679,124695,124696],{"class":693}," content: [",[679,124698,124699],{"class":689},"\"../resources/templates/**/*.{html,js}\"",[679,124701,44016],{"class":693},[679,124703,124704],{"class":681,"line":901},[679,124705,124706],{"class":693}," theme: {\n",[679,124708,124709],{"class":681,"line":909},[679,124710,124711],{"class":693}," extend: {},\n",[679,124713,124714],{"class":681,"line":918},[679,124715,28483],{"class":693},[679,124717,124718],{"class":681,"line":935},[679,124719,124720],{"class":693}," plugins: [],\n",[679,124722,124723],{"class":681,"line":944},[679,124724,996],{"class":693},[5909,124726,124728],{"id":124727},"creating-the-css-file","Creating the CSS File",[651,124730,124731,124732,124734,124735,124738],{},"Now that we have Tailwind CSS set up, we can create a new CSS file in the ",[676,124733,89000],{}," folder called ",[676,124736,124737],{},"styles.css",". In this file, we'll add the following Tailwind CSS directives:",[669,124740,124742],{"className":66165,"code":124741,"language":66167,"meta":674,"style":674},"@tailwind base;\n@tailwind components;\n@tailwind utilities;\n",[676,124743,124744,124752,124759],{"__ignoreMap":674},[679,124745,124746,124749],{"class":681,"line":682},[679,124747,124748],{"class":685},"@tailwind",[679,124750,124751],{"class":693}," base;\n",[679,124753,124754,124756],{"class":681,"line":790},[679,124755,124748],{"class":685},[679,124757,124758],{"class":693}," components;\n",[679,124760,124761,124763],{"class":681,"line":892},[679,124762,124748],{"class":685},[679,124764,124765],{"class":693}," utilities;\n",[651,124767,124768],{},"These directives will import all the necessary Tailwind CSS styles into our CSS file.",[5909,124770,124772],{"id":124771},"building-the-css-for-production","Building the CSS for Production",[651,124774,124775],{},"To optimize our CSS for production, we'll need to set up a build process that will only include the Tailwind CSS classes that we're actually using in our HTML templates. We can do this by adding two new scripts to our \"package.json\" file:",[669,124777,124779],{"className":28439,"code":124778,"language":28441,"meta":674,"style":674},"\"scripts\": {\n \"build\": \"tailwindcss -i ./styles.css -o ../resources/static/main.css --minify\",\n \"watch\": \"tailwindcss -i ./styles.css -o ../resources/static/main.css --watch\"\n}\n",[676,124780,124781,124787,124799,124809],{"__ignoreMap":674},[679,124782,124783,124785],{"class":681,"line":682},[679,124784,54888],{"class":689},[679,124786,28468],{"class":693},[679,124788,124789,124792,124794,124797],{"class":681,"line":790},[679,124790,124791],{"class":931}," \"build\"",[679,124793,4282],{"class":693},[679,124795,124796],{"class":689},"\"tailwindcss -i ./styles.css -o ../resources/static/main.css --minify\"",[679,124798,12083],{"class":693},[679,124800,124801,124804,124806],{"class":681,"line":892},[679,124802,124803],{"class":931}," \"watch\"",[679,124805,4282],{"class":693},[679,124807,124808],{"class":689},"\"tailwindcss -i ./styles.css -o ../resources/static/main.css --watch\"\n",[679,124810,124811],{"class":681,"line":901},[679,124812,996],{"class":693},[651,124814,40060,124815,124817,124818,124820,124821,124824,124825,124828,124829,124832],{},[676,124816,23612],{}," script will take our ",[676,124819,124737],{}," file, process it through Tailwind CSS, and output the final CSS file to the ",[676,124822,124823],{},"src/main/resources/static"," directory. The ",[676,124826,124827],{},"--minify"," option will also minify the output CSS file. The ",[676,124830,124831],{},"watch"," script is similar, but it will continuously watch for changes in our HTML templates and rebuild the CSS file whenever something changes.",[651,124834,124835],{},"Now, we can run the \"watch\" script in our terminal to start the build process:",[669,124837,124839],{"className":7993,"code":124838,"language":7995,"meta":674,"style":674},"npm run watch\n",[676,124840,124841],{"__ignoreMap":674},[679,124842,124843,124845,124847],{"class":681,"line":682},[679,124844,24568],{"class":880},[679,124846,16486],{"class":689},[679,124848,124849],{"class":689}," watch\n",[651,124851,124852,124853,124856],{},"This will start the Tailwind CSS build process and keep it running in the background, automatically updating the ",[676,124854,124855],{},"main.css"," file whenever we make changes to our HTML templates.",[5909,124858,124860],{"id":124859},"creating-a-thymeleaf-template","Creating a Thymeleaf Template",[651,124862,124863,124864,124867,124868,124870],{},"With our Tailwind CSS setup complete, we can now create a new Thymeleaf template in the ",[676,124865,124866],{},"src/main/resources/templates"," directory called ",[676,124869,57223],{},". In this template, we'll use some Tailwind CSS classes to style our content:",[669,124872,124874],{"className":4496,"code":124873,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\" xmlns:th=\"http://www.thymeleaf.org\">\n\u003Chead>\n \u003Cmeta charset=\"UTF-8\">\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003Ctitle>Spring Boot Tailwind\u003C/title>\n \u003Clink rel=\"stylesheet\" th:href=\"@{/main.css}\">\n\u003C/head>\n\u003Cbody class=\"bg-slate-50\">\n \u003Cheader class=\"bg-white container mx-auto py-8\">\n \u003Ch1 class=\"text-4xl font-bold px-4\">Welcome to Spring Boot Tailwind\u003C/h1>\n \u003C/header>\n\u003C/body>\n\u003C/html>\n",[676,124875,124876,124886,124908,124916,124930,124950,124963,124987,124995,125010,125025,125045,125053,125061],{"__ignoreMap":674},[679,124877,124878,124880,124882,124884],{"class":681,"line":682},[679,124879,11904],{"class":693},[679,124881,11907],{"class":4508},[679,124883,11910],{"class":880},[679,124885,4519],{"class":693},[679,124887,124888,124890,124892,124894,124896,124898,124901,124903,124906],{"class":681,"line":790},[679,124889,4505],{"class":693},[679,124891,4498],{"class":4508},[679,124893,18806],{"class":880},[679,124895,686],{"class":693},[679,124897,57251],{"class":689},[679,124899,124900],{"class":880}," xmlns:th",[679,124902,686],{"class":693},[679,124904,124905],{"class":689},"\"http://www.thymeleaf.org\"",[679,124907,4519],{"class":693},[679,124909,124910,124912,124914],{"class":681,"line":892},[679,124911,4505],{"class":693},[679,124913,11741],{"class":4508},[679,124915,4519],{"class":693},[679,124917,124918,124920,124922,124924,124926,124928],{"class":681,"line":901},[679,124919,11738],{"class":693},[679,124921,11968],{"class":4508},[679,124923,11971],{"class":880},[679,124925,686],{"class":693},[679,124927,47958],{"class":689},[679,124929,4519],{"class":693},[679,124931,124932,124934,124936,124938,124940,124942,124944,124946,124948],{"class":681,"line":909},[679,124933,11738],{"class":693},[679,124935,11968],{"class":4508},[679,124937,5283],{"class":880},[679,124939,686],{"class":693},[679,124941,12015],{"class":689},[679,124943,11995],{"class":880},[679,124945,686],{"class":693},[679,124947,48042],{"class":689},[679,124949,4519],{"class":693},[679,124951,124952,124954,124956,124959,124961],{"class":681,"line":918},[679,124953,11738],{"class":693},[679,124955,11750],{"class":4508},[679,124957,124958],{"class":693},">Spring Boot Tailwind\u003C/",[679,124960,11750],{"class":4508},[679,124962,4519],{"class":693},[679,124964,124965,124967,124969,124972,124974,124977,124980,124982,124985],{"class":681,"line":935},[679,124966,11738],{"class":693},[679,124968,6409],{"class":4508},[679,124970,124971],{"class":880}," rel",[679,124973,686],{"class":693},[679,124975,124976],{"class":689},"\"stylesheet\"",[679,124978,124979],{"class":880}," th:href",[679,124981,686],{"class":693},[679,124983,124984],{"class":689},"\"@{/main.css}\"",[679,124986,4519],{"class":693},[679,124988,124989,124991,124993],{"class":681,"line":944},[679,124990,4586],{"class":693},[679,124992,11741],{"class":4508},[679,124994,4519],{"class":693},[679,124996,124997,124999,125001,125003,125005,125008],{"class":681,"line":959},[679,124998,4505],{"class":693},[679,125000,3006],{"class":4508},[679,125002,4512],{"class":880},[679,125004,686],{"class":693},[679,125006,125007],{"class":689},"\"bg-slate-50\"",[679,125009,4519],{"class":693},[679,125011,125012,125014,125016,125018,125020,125023],{"class":681,"line":964},[679,125013,11738],{"class":693},[679,125015,91684],{"class":4508},[679,125017,4512],{"class":880},[679,125019,686],{"class":693},[679,125021,125022],{"class":689},"\"bg-white container mx-auto py-8\"",[679,125024,4519],{"class":693},[679,125026,125027,125029,125031,125033,125035,125038,125041,125043],{"class":681,"line":977},[679,125028,4524],{"class":693},[679,125030,11859],{"class":4508},[679,125032,4512],{"class":880},[679,125034,686],{"class":693},[679,125036,125037],{"class":689},"\"text-4xl font-bold px-4\"",[679,125039,125040],{"class":693},">Welcome to Spring Boot Tailwind\u003C/",[679,125042,11859],{"class":4508},[679,125044,4519],{"class":693},[679,125046,125047,125049,125051],{"class":681,"line":982},[679,125048,11840],{"class":693},[679,125050,91684],{"class":4508},[679,125052,4519],{"class":693},[679,125054,125055,125057,125059],{"class":681,"line":988},[679,125056,4586],{"class":693},[679,125058,3006],{"class":4508},[679,125060,4519],{"class":693},[679,125062,125063,125065,125067],{"class":681,"line":993},[679,125064,4586],{"class":693},[679,125066,4498],{"class":4508},[679,125068,4519],{"class":693},[651,125070,125071],{},"In this template, we're using Tailwind CSS classes to set the background color of the body, create a white header with some padding, and style the main heading.",[5909,125073,125075],{"id":125074},"creating-a-spring-boot-controller","Creating a Spring Boot Controller",[651,125077,125078],{},"To render our Thymeleaf template, we'll need to create a Spring Boot controller. In the \"src/main/java\" directory, create a new package called \"com.example.springboottailwind\" (or whatever your package name is) and a new class called \"HomeController\":",[669,125080,125082],{"className":4107,"code":125081,"language":4109,"meta":674,"style":674},"@Controller\npublic class HomeController {\n\n @GetMapping(\"/\")\n public String home() {\n return \"index\";\n }\n\n}\n",[676,125083,125084,125090,125100,125104,125116,125126,125134,125138,125142],{"__ignoreMap":674},[679,125085,125086,125088],{"class":681,"line":682},[679,125087,4116],{"class":693},[679,125089,9942],{"class":685},[679,125091,125092,125094,125096,125098],{"class":681,"line":790},[679,125093,6073],{"class":685},[679,125095,4512],{"class":685},[679,125097,18716],{"class":880},[679,125099,884],{"class":693},[679,125101,125102],{"class":681,"line":892},[679,125103,889],{"emptyLinePlaceholder":797},[679,125105,125106,125108,125110,125112,125114],{"class":681,"line":901},[679,125107,6872],{"class":693},[679,125109,20852],{"class":685},[679,125111,745],{"class":693},[679,125113,10032],{"class":689},[679,125115,1339],{"class":693},[679,125117,125118,125120,125122,125124],{"class":681,"line":909},[679,125119,6089],{"class":685},[679,125121,9289],{"class":693},[679,125123,12642],{"class":880},[679,125125,2667],{"class":693},[679,125127,125128,125130,125132],{"class":681,"line":918},[679,125129,9444],{"class":685},[679,125131,18769],{"class":689},[679,125133,1186],{"class":693},[679,125135,125136],{"class":681,"line":935},[679,125137,985],{"class":693},[679,125139,125140],{"class":681,"line":944},[679,125141,889],{"emptyLinePlaceholder":797},[679,125143,125144],{"class":681,"line":959},[679,125145,996],{"class":693},[651,125147,125148],{},"This controller simply maps the root URL (\"/\") to the \"index\" Thymeleaf template that we created earlier.",[5909,125150,98048],{"id":7309},[651,125152,125153],{},"Now that we have everything set up, we can run our Spring Boot application. In the terminal, navigate to the root of your project and run the following command:",[669,125155,125156],{"className":7993,"code":36339,"language":7995,"meta":674,"style":674},[676,125157,125158],{"__ignoreMap":674},[679,125159,125160,125162],{"class":681,"line":682},[679,125161,32493],{"class":880},[679,125163,36348],{"class":689},[651,125165,125166,125167,125170],{},"This will start the Spring Boot application and you should be able to access it at ",[812,125168,11697],{"href":11697,"rel":125169},[816],". You should see the Tailwind CSS-styled header and background that we defined in our Thymeleaf template.",[4542,125172,125174],{"id":125173},"optimizing-for-production","Optimizing for Production",[651,125176,125177,125178,125181],{},"The setup we've done so far is great for development, but we still need to optimize our CSS for production. To do this, we'll use the ",[812,125179,81560],{"href":81558,"rel":125180},[816],", which will handle the installation of Node.js and npm, as well as running the Tailwind CSS build process as part of our Maven build.",[651,125183,125184,125185,125187],{},"First, we'll need to add a few properties to our ",[676,125186,81517],{}," file to specify the versions of Node.js and npm we want to use:",[669,125189,125191],{"className":9101,"code":125190,"language":9103,"meta":674,"style":674},"\u003Cproperties>\n \u003Cnode.version>v16.13.0\u003C/node.version>\n \u003Cnpm.version>8.1.0\u003C/npm.version>\n\u003C/properties>\n",[676,125192,125193,125201,125215,125229],{"__ignoreMap":674},[679,125194,125195,125197,125199],{"class":681,"line":682},[679,125196,4505],{"class":693},[679,125198,35538],{"class":4508},[679,125200,4519],{"class":693},[679,125202,125203,125205,125208,125211,125213],{"class":681,"line":790},[679,125204,11738],{"class":693},[679,125206,125207],{"class":4508},"node.version",[679,125209,125210],{"class":693},">v16.13.0\u003C/",[679,125212,125207],{"class":4508},[679,125214,4519],{"class":693},[679,125216,125217,125219,125222,125225,125227],{"class":681,"line":892},[679,125218,11738],{"class":693},[679,125220,125221],{"class":4508},"npm.version",[679,125223,125224],{"class":693},">8.1.0\u003C/",[679,125226,125221],{"class":4508},[679,125228,4519],{"class":693},[679,125230,125231,125233,125235],{"class":681,"line":901},[679,125232,4586],{"class":693},[679,125234,35538],{"class":4508},[679,125236,4519],{"class":693},[651,125238,125239,125240,94061],{},"Next, we'll add the frontend-maven-plugin configuration to our ",[676,125241,81517],{},[669,125243,125245],{"className":9101,"code":125244,"language":9103,"meta":674,"style":674},"\u003Cplugin>\n \u003CgroupId>com.github.eirslett\u003C/groupId>\n \u003CartifactId>frontend-maven-plugin\u003C/artifactId>\n \u003Cversion>1.15.0\u003C/version>\n \u003Cexecutions>\n\n \u003Cexecution>\n \u003Cid>install node and npm\u003C/id>\n \u003Cgoals>\n \u003Cgoal>install-node-and-npm\u003C/goal>\n \u003C/goals>\n \u003Cphase>generate-resources\u003C/phase>\n \u003Cconfiguration>\n \u003CnodeVersion>${node.version}\u003C/nodeVersion>\n \u003CnpmVersion>${npm.version}\u003C/npmVersion>\n \u003C/configuration>\n \u003C/execution>\n\n \u003Cexecution>\n \u003Cid>npm install\u003C/id>\n \u003Cgoals>\n \u003Cgoal>npm\u003C/goal>\n \u003C/goals>\n \u003Cphase>generate-resources\u003C/phase>\n \u003Cconfiguration>\n \u003Carguments>install\u003C/arguments>\n \u003C/configuration>\n \u003C/execution>\n\n \u003Cexecution>\n \u003Cid>npm build\u003C/id>\n \u003Cgoals>\n \u003Cgoal>npm\u003C/goal>\n \u003C/goals>\n \u003Cphase>generate-resources\u003C/phase>\n \u003Cconfiguration>\n \u003Carguments>run build\u003C/arguments>\n \u003C/configuration>\n \u003C/execution>\n\n \u003C/executions>\n \u003Cconfiguration>\n \u003CnodeVersion>${node.version}\u003C/nodeVersion>\n \u003CworkingDirectory>src/main/frontend\u003C/workingDirectory>\n \u003CinstallDirectory>target\u003C/installDirectory>\n \u003C/configuration>\n\u003C/plugin>\n",[676,125246,125247,125255,125268,125281,125294,125303,125307,125316,125329,125337,125352,125361,125375,125384,125398,125412,125420,125428,125432,125440,125453,125461,125474,125482,125494,125502,125516,125524,125532,125536,125544,125557,125565,125577,125585,125597,125605,125618,125626,125634,125638,125646,125654,125666,125680,125694,125702],{"__ignoreMap":674},[679,125248,125249,125251,125253],{"class":681,"line":682},[679,125250,4505],{"class":693},[679,125252,24405],{"class":4508},[679,125254,4519],{"class":693},[679,125256,125257,125259,125261,125264,125266],{"class":681,"line":790},[679,125258,4524],{"class":693},[679,125260,119847],{"class":4508},[679,125262,125263],{"class":693},">com.github.eirslett\u003C/",[679,125265,119847],{"class":4508},[679,125267,4519],{"class":693},[679,125269,125270,125272,125274,125277,125279],{"class":681,"line":892},[679,125271,4524],{"class":693},[679,125273,119861],{"class":4508},[679,125275,125276],{"class":693},">frontend-maven-plugin\u003C/",[679,125278,119861],{"class":4508},[679,125280,4519],{"class":693},[679,125282,125283,125285,125287,125290,125292],{"class":681,"line":901},[679,125284,4524],{"class":693},[679,125286,107166],{"class":4508},[679,125288,125289],{"class":693},">1.15.0\u003C/",[679,125291,107166],{"class":4508},[679,125293,4519],{"class":693},[679,125295,125296,125298,125301],{"class":681,"line":909},[679,125297,4524],{"class":693},[679,125299,125300],{"class":4508},"executions",[679,125302,4519],{"class":693},[679,125304,125305],{"class":681,"line":918},[679,125306,889],{"emptyLinePlaceholder":797},[679,125308,125309,125311,125314],{"class":681,"line":935},[679,125310,4904],{"class":693},[679,125312,125313],{"class":4508},"execution",[679,125315,4519],{"class":693},[679,125317,125318,125320,125322,125325,125327],{"class":681,"line":944},[679,125319,5392],{"class":693},[679,125321,11341],{"class":4508},[679,125323,125324],{"class":693},">install node and npm\u003C/",[679,125326,11341],{"class":4508},[679,125328,4519],{"class":693},[679,125330,125331,125333,125335],{"class":681,"line":959},[679,125332,5392],{"class":693},[679,125334,39139],{"class":4508},[679,125336,4519],{"class":693},[679,125338,125339,125342,125345,125348,125350],{"class":681,"line":964},[679,125340,125341],{"class":693}," \u003C",[679,125343,125344],{"class":4508},"goal",[679,125346,125347],{"class":693},">install-node-and-npm\u003C/",[679,125349,125344],{"class":4508},[679,125351,4519],{"class":693},[679,125353,125354,125357,125359],{"class":681,"line":977},[679,125355,125356],{"class":693}," \u003C/",[679,125358,39139],{"class":4508},[679,125360,4519],{"class":693},[679,125362,125363,125365,125368,125371,125373],{"class":681,"line":982},[679,125364,5392],{"class":693},[679,125366,125367],{"class":4508},"phase",[679,125369,125370],{"class":693},">generate-resources\u003C/",[679,125372,125367],{"class":4508},[679,125374,4519],{"class":693},[679,125376,125377,125379,125382],{"class":681,"line":988},[679,125378,5392],{"class":693},[679,125380,125381],{"class":4508},"configuration",[679,125383,4519],{"class":693},[679,125385,125386,125388,125391,125394,125396],{"class":681,"line":993},[679,125387,125341],{"class":693},[679,125389,125390],{"class":4508},"nodeVersion",[679,125392,125393],{"class":693},">${node.version}\u003C/",[679,125395,125390],{"class":4508},[679,125397,4519],{"class":693},[679,125399,125400,125402,125405,125408,125410],{"class":681,"line":2129},[679,125401,125341],{"class":693},[679,125403,125404],{"class":4508},"npmVersion",[679,125406,125407],{"class":693},">${npm.version}\u003C/",[679,125409,125404],{"class":4508},[679,125411,4519],{"class":693},[679,125413,125414,125416,125418],{"class":681,"line":2140},[679,125415,125356],{"class":693},[679,125417,125381],{"class":4508},[679,125419,4519],{"class":693},[679,125421,125422,125424,125426],{"class":681,"line":2145},[679,125423,5480],{"class":693},[679,125425,125313],{"class":4508},[679,125427,4519],{"class":693},[679,125429,125430],{"class":681,"line":2154},[679,125431,889],{"emptyLinePlaceholder":797},[679,125433,125434,125436,125438],{"class":681,"line":2159},[679,125435,4904],{"class":693},[679,125437,125313],{"class":4508},[679,125439,4519],{"class":693},[679,125441,125442,125444,125446,125449,125451],{"class":681,"line":2164},[679,125443,5392],{"class":693},[679,125445,11341],{"class":4508},[679,125447,125448],{"class":693},">npm install\u003C/",[679,125450,11341],{"class":4508},[679,125452,4519],{"class":693},[679,125454,125455,125457,125459],{"class":681,"line":3134},[679,125456,5392],{"class":693},[679,125458,39139],{"class":4508},[679,125460,4519],{"class":693},[679,125462,125463,125465,125467,125470,125472],{"class":681,"line":3139},[679,125464,125341],{"class":693},[679,125466,125344],{"class":4508},[679,125468,125469],{"class":693},">npm\u003C/",[679,125471,125344],{"class":4508},[679,125473,4519],{"class":693},[679,125475,125476,125478,125480],{"class":681,"line":3144},[679,125477,125356],{"class":693},[679,125479,39139],{"class":4508},[679,125481,4519],{"class":693},[679,125483,125484,125486,125488,125490,125492],{"class":681,"line":3149},[679,125485,5392],{"class":693},[679,125487,125367],{"class":4508},[679,125489,125370],{"class":693},[679,125491,125367],{"class":4508},[679,125493,4519],{"class":693},[679,125495,125496,125498,125500],{"class":681,"line":3169},[679,125497,5392],{"class":693},[679,125499,125381],{"class":4508},[679,125501,4519],{"class":693},[679,125503,125504,125506,125509,125512,125514],{"class":681,"line":3185},[679,125505,125341],{"class":693},[679,125507,125508],{"class":4508},"arguments",[679,125510,125511],{"class":693},">install\u003C/",[679,125513,125508],{"class":4508},[679,125515,4519],{"class":693},[679,125517,125518,125520,125522],{"class":681,"line":3194},[679,125519,125356],{"class":693},[679,125521,125381],{"class":4508},[679,125523,4519],{"class":693},[679,125525,125526,125528,125530],{"class":681,"line":3199},[679,125527,5480],{"class":693},[679,125529,125313],{"class":4508},[679,125531,4519],{"class":693},[679,125533,125534],{"class":681,"line":3212},[679,125535,889],{"emptyLinePlaceholder":797},[679,125537,125538,125540,125542],{"class":681,"line":3217},[679,125539,4904],{"class":693},[679,125541,125313],{"class":4508},[679,125543,4519],{"class":693},[679,125545,125546,125548,125550,125553,125555],{"class":681,"line":3222},[679,125547,5392],{"class":693},[679,125549,11341],{"class":4508},[679,125551,125552],{"class":693},">npm build\u003C/",[679,125554,11341],{"class":4508},[679,125556,4519],{"class":693},[679,125558,125559,125561,125563],{"class":681,"line":3227},[679,125560,5392],{"class":693},[679,125562,39139],{"class":4508},[679,125564,4519],{"class":693},[679,125566,125567,125569,125571,125573,125575],{"class":681,"line":3232},[679,125568,125341],{"class":693},[679,125570,125344],{"class":4508},[679,125572,125469],{"class":693},[679,125574,125344],{"class":4508},[679,125576,4519],{"class":693},[679,125578,125579,125581,125583],{"class":681,"line":3499},[679,125580,125356],{"class":693},[679,125582,39139],{"class":4508},[679,125584,4519],{"class":693},[679,125586,125587,125589,125591,125593,125595],{"class":681,"line":3509},[679,125588,5392],{"class":693},[679,125590,125367],{"class":4508},[679,125592,125370],{"class":693},[679,125594,125367],{"class":4508},[679,125596,4519],{"class":693},[679,125598,125599,125601,125603],{"class":681,"line":3516},[679,125600,5392],{"class":693},[679,125602,125381],{"class":4508},[679,125604,4519],{"class":693},[679,125606,125607,125609,125611,125614,125616],{"class":681,"line":3531},[679,125608,125341],{"class":693},[679,125610,125508],{"class":4508},[679,125612,125613],{"class":693},">run build\u003C/",[679,125615,125508],{"class":4508},[679,125617,4519],{"class":693},[679,125619,125620,125622,125624],{"class":681,"line":3536},[679,125621,125356],{"class":693},[679,125623,125381],{"class":4508},[679,125625,4519],{"class":693},[679,125627,125628,125630,125632],{"class":681,"line":3541},[679,125629,5480],{"class":693},[679,125631,125313],{"class":4508},[679,125633,4519],{"class":693},[679,125635,125636],{"class":681,"line":3546},[679,125637,889],{"emptyLinePlaceholder":797},[679,125639,125640,125642,125644],{"class":681,"line":3551},[679,125641,4577],{"class":693},[679,125643,125300],{"class":4508},[679,125645,4519],{"class":693},[679,125647,125648,125650,125652],{"class":681,"line":3557},[679,125649,4524],{"class":693},[679,125651,125381],{"class":4508},[679,125653,4519],{"class":693},[679,125655,125656,125658,125660,125662,125664],{"class":681,"line":3567},[679,125657,4904],{"class":693},[679,125659,125390],{"class":4508},[679,125661,125393],{"class":693},[679,125663,125390],{"class":4508},[679,125665,4519],{"class":693},[679,125667,125668,125670,125673,125676,125678],{"class":681,"line":3574},[679,125669,4904],{"class":693},[679,125671,125672],{"class":4508},"workingDirectory",[679,125674,125675],{"class":693},">src/main/frontend\u003C/",[679,125677,125672],{"class":4508},[679,125679,4519],{"class":693},[679,125681,125682,125684,125687,125690,125692],{"class":681,"line":3589},[679,125683,4904],{"class":693},[679,125685,125686],{"class":4508},"installDirectory",[679,125688,125689],{"class":693},">target\u003C/",[679,125691,125686],{"class":4508},[679,125693,4519],{"class":693},[679,125695,125696,125698,125700],{"class":681,"line":3594},[679,125697,4577],{"class":693},[679,125699,125381],{"class":4508},[679,125701,4519],{"class":693},[679,125703,125704,125706,125708],{"class":681,"line":3602},[679,125705,4586],{"class":693},[679,125707,24405],{"class":4508},[679,125709,4519],{"class":693},[651,125711,125712],{},"This configuration will ensure that Node.js and npm are installed, the necessary npm dependencies are installed, and the Tailwind CSS build process is run as part of the Maven build. Now, when you run the following command in the terminal:",[669,125714,125716],{"className":7993,"code":125715,"language":7995,"meta":674,"style":674},"./mvnw clean package\n",[676,125717,125718],{"__ignoreMap":674},[679,125719,125720,125722,125724],{"class":681,"line":682},[679,125721,32493],{"class":880},[679,125723,81925],{"class":689},[679,125725,32496],{"class":689},[651,125727,125728,125729,125731,125732,125734],{},"Maven will handle the entire build process, including the Tailwind CSS optimization. The final ",[676,125730,124855],{}," file will be generated in the ",[676,125733,124823],{}," directory and will only include the Tailwind CSS classes that are used in your application.",[4542,125736,9042],{"id":9041},[651,125738,125739],{},"In this blog post, we've explored how to integrate Tailwind CSS into a Spring Boot application, ensuring that we only include the styles we need in our production build. By setting up a build process using the frontend-maven-plugin, we can automate the installation of Node.js and npm, as well as the Tailwind CSS build process, making it easy to maintain and deploy our application.",[651,125741,125742,125743,76149,125747,125751,125752,125756],{},"If you're interested in learning more about Tailwind CSS or exploring other Spring Boot projects, be sure to check out the ",[812,125744,110300],{"href":125745,"rel":125746},"https://github.com/danvega/spring-boot-tailwind",[816],[812,125748,125750],{"href":124479,"rel":125749},[816],"Tailwind CSS website",". And don't forget to subscribe to my ",[812,125753,67441],{"href":125754,"rel":125755},"https://www.danvega.dev/newsletter",[816]," for more Spring Boot and web development content!",[786,125758,125759],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}",{"title":674,"searchDepth":790,"depth":790,"links":125761},[125762,125763,125764,125772,125773],{"id":124485,"depth":790,"text":124486},{"id":124495,"depth":790,"text":124496},{"id":124531,"depth":790,"text":124532,"children":125765},[125766,125767,125768,125769,125770,125771],{"id":124558,"depth":892,"text":124559},{"id":124727,"depth":892,"text":124728},{"id":124771,"depth":892,"text":124772},{"id":124859,"depth":892,"text":124860},{"id":125074,"depth":892,"text":125075},{"id":7309,"depth":892,"text":98048},{"id":125173,"depth":790,"text":125174},{"id":9041,"depth":790,"text":9042},"This is an example of how to use Tailwind CSS in your next Spring Boot application. If you just want to get up and running with Tailwind CSS you can use a CDN but this should only be used for prototyping because you will end up shipping a very large css file to production. In this tutorial you will learn how to introduce a build process to only use the styles you are using.",{"slug":125776,"date":125777,"published":797,"author":798,"tags":125778,"cover":125779,"video":125780,"keywords":125781},"spring-boot-tailwind","2024-07-18T17:00:00.000Z",[7077,124481],"./spring-boot-tailwind.jpeg","https://www.youtube.com/embed/6_6AIzxPXvQ","Spring Framework, Spring Boot, Tailwind CSS, Frontend",{"title":90,"description":125774},"blog/2024/07/18/spring-boot-tailwind","vEm_5Yh1-Nn3IOOR_qxYfM7jX1zz98RtCcshBrwywiY",{"id":125786,"title":87,"body":125787,"description":126427,"extension":793,"meta":126428,"navigation":797,"path":88,"seo":126436,"stem":126437,"__hash__":126438},"content/blog/2024/08/01/java-method-references.md",{"type":648,"value":125788,"toc":126415},[125789,125792,125796,125799,125803,125806,125826,125830,125833,125847,125850,125854,125858,125861,125950,125957,125961,125964,126122,126136,126140,126143,126245,126256,126260,126263,126393,126399,126401,126404,126407,126410,126412],[651,125790,125791],{},"Hello, Java enthusiasts! 👋 Today, we're diving into a powerful feature of Java that can make your code cleaner, more readable, and boost your productivity. Let's explore method references!",[4542,125793,125795],{"id":125794},"what-are-method-references","What Are Method References?",[651,125797,125798],{},"Method references, introduced in Java 8, are a concise shorthand for referring to methods or constructors. Think of them as a way to say, \"Hey Java, use this method here!\" without writing out the entire method call. They work hand-in-hand with lambda expressions to bring a touch of functional programming to Java.",[4542,125800,125802],{"id":125801},"why-should-you-care","Why Should You Care?",[651,125804,125805],{},"You might be wondering, \"Why should I bother learning about method references?\" Great question! Here are three good reasons to start using them:",[27665,125807,125808,125814,125820],{},[5332,125809,125810,125813],{},[2939,125811,125812],{},"Cleaner Code",": Method references can significantly reduce boilerplate, making your code more concise and easier to read.",[5332,125815,125816,125819],{},[2939,125817,125818],{},"Increased Productivity",": Less code to write means more time for solving real problems. Who doesn't want that?",[5332,125821,125822,125825],{},[2939,125823,125824],{},"Better Expressiveness",": They help you express your intent more clearly, especially in functional-style programming.",[4542,125827,125829],{"id":125828},"types-of-method-references","Types of Method References",[651,125831,125832],{},"Java offers four types of method references. Let's break them down:",[27665,125834,125835,125838,125841,125844],{},[5332,125836,125837],{},"Reference to a static method",[5332,125839,125840],{},"Reference to an instance method of a particular object",[5332,125842,125843],{},"Reference to an instance method of an arbitrary object of a particular type",[5332,125845,125846],{},"Reference to a constructor",[651,125848,125849],{},"Now, let's look at each type with some practical examples!",[4542,125851,125853],{"id":125852},"code-examples","Code Examples",[5909,125855,125857],{"id":125856},"_1-reference-to-a-static-method","1. Reference to a Static Method",[651,125859,125860],{},"This is probably the easiest type to understand. You're basically pointing to a static method in a class.",[669,125862,125864],{"className":4107,"code":125863,"language":4109,"meta":674,"style":674},"// Without method reference\nList\u003CInteger> numbers = Arrays.asList(1, 2, 3, 4, 5);\nnumbers.forEach(number -> System.out.println(number));\n\n// With method reference\nnumbers.forEach(System.out::println);\n",[676,125865,125866,125871,125909,125929,125933,125938],{"__ignoreMap":674},[679,125867,125868],{"class":681,"line":682},[679,125869,125870],{"class":1400},"// Without method reference\n",[679,125872,125873,125876,125878,125881,125883,125885,125887,125889,125891,125893,125895,125897,125899,125901,125903,125905,125907],{"class":681,"line":790},[679,125874,125875],{"class":693},"List\u003C",[679,125877,1083],{"class":685},[679,125879,125880],{"class":693},"> numbers ",[679,125882,686],{"class":685},[679,125884,38274],{"class":693},[679,125886,38277],{"class":880},[679,125888,745],{"class":693},[679,125890,1557],{"class":931},[679,125892,2797],{"class":693},[679,125894,17262],{"class":931},[679,125896,2797],{"class":693},[679,125898,66599],{"class":931},[679,125900,2797],{"class":693},[679,125902,49776],{"class":931},[679,125904,2797],{"class":693},[679,125906,66775],{"class":931},[679,125908,1208],{"class":693},[679,125910,125911,125914,125916,125919,125921,125924,125926],{"class":681,"line":892},[679,125912,125913],{"class":693},"numbers.",[679,125915,46928],{"class":880},[679,125917,125918],{"class":693},"(number ",[679,125920,16955],{"class":685},[679,125922,125923],{"class":693}," System.out.",[679,125925,1729],{"class":880},[679,125927,125928],{"class":693},"(number));\n",[679,125930,125931],{"class":681,"line":901},[679,125932,889],{"emptyLinePlaceholder":797},[679,125934,125935],{"class":681,"line":909},[679,125936,125937],{"class":1400},"// With method reference\n",[679,125939,125940,125942,125944,125946,125948],{"class":681,"line":918},[679,125941,125913],{"class":693},[679,125943,46928],{"class":880},[679,125945,97984],{"class":693},[679,125947,90007],{"class":685},[679,125949,97989],{"class":693},[651,125951,125952,125953,125956],{},"In this example, ",[676,125954,125955],{},"System.out::println"," is a method reference. It's saying \"use the println method of System.out for each element\". Nice and clean, right?",[5909,125958,125960],{"id":125959},"_2-reference-to-an-instance-method-of-a-particular-object","2. Reference to an Instance Method of a Particular Object",[651,125962,125963],{},"This type is used when you want to refer to a method of a specific object instance.",[669,125965,125967],{"className":4107,"code":125966,"language":4109,"meta":674,"style":674},"class Greeter {\n public void greet(String name) {\n System.out.println(\"Hello, \" + name + \"!\");\n }\n}\n\n// Without method reference\nGreeter greeter = new Greeter();\nList\u003CString> family = List.of(\"Dan\", \"Jen\", \"Isabella\",\"Juliana\");\nfamily.forEach(name -> greeter.greet(name));\n\n// With method reference\nfamily.forEach(greeter::greet);\n",[676,125968,125969,125978,125993,126014,126018,126022,126026,126030,126043,126079,126100,126104,126108],{"__ignoreMap":674},[679,125970,125971,125973,125976],{"class":681,"line":682},[679,125972,877],{"class":685},[679,125974,125975],{"class":880}," Greeter",[679,125977,884],{"class":693},[679,125979,125980,125982,125984,125987,125989,125991],{"class":681,"line":790},[679,125981,6089],{"class":685},[679,125983,6095],{"class":685},[679,125985,125986],{"class":880}," greet",[679,125988,11400],{"class":693},[679,125990,16334],{"class":2099},[679,125992,4390],{"class":693},[679,125994,125995,125997,125999,126001,126004,126006,126008,126010,126012],{"class":681,"line":892},[679,125996,9592],{"class":693},[679,125998,1729],{"class":880},[679,126000,745],{"class":693},[679,126002,126003],{"class":689},"\"Hello, \"",[679,126005,3059],{"class":685},[679,126007,100070],{"class":693},[679,126009,3065],{"class":685},[679,126011,100075],{"class":689},[679,126013,1208],{"class":693},[679,126015,126016],{"class":681,"line":901},[679,126017,985],{"class":693},[679,126019,126020],{"class":681,"line":909},[679,126021,996],{"class":693},[679,126023,126024],{"class":681,"line":918},[679,126025,889],{"emptyLinePlaceholder":797},[679,126027,126028],{"class":681,"line":935},[679,126029,125870],{"class":1400},[679,126031,126032,126035,126037,126039,126041],{"class":681,"line":944},[679,126033,126034],{"class":693},"Greeter greeter ",[679,126036,686],{"class":685},[679,126038,2054],{"class":685},[679,126040,125975],{"class":880},[679,126042,9317],{"class":693},[679,126044,126045,126047,126049,126052,126054,126056,126058,126060,126062,126064,126067,126069,126072,126074,126077],{"class":681,"line":959},[679,126046,125875],{"class":693},[679,126048,4758],{"class":685},[679,126050,126051],{"class":693},"> family ",[679,126053,686],{"class":685},[679,126055,16669],{"class":693},[679,126057,16672],{"class":880},[679,126059,745],{"class":693},[679,126061,1414],{"class":689},[679,126063,2797],{"class":693},[679,126065,126066],{"class":689},"\"Jen\"",[679,126068,2797],{"class":693},[679,126070,126071],{"class":689},"\"Isabella\"",[679,126073,1202],{"class":693},[679,126075,126076],{"class":689},"\"Juliana\"",[679,126078,1208],{"class":693},[679,126080,126081,126084,126086,126089,126091,126094,126097],{"class":681,"line":964},[679,126082,126083],{"class":693},"family.",[679,126085,46928],{"class":880},[679,126087,126088],{"class":693},"(name ",[679,126090,16955],{"class":685},[679,126092,126093],{"class":693}," greeter.",[679,126095,126096],{"class":880},"greet",[679,126098,126099],{"class":693},"(name));\n",[679,126101,126102],{"class":681,"line":977},[679,126103,889],{"emptyLinePlaceholder":797},[679,126105,126106],{"class":681,"line":982},[679,126107,125937],{"class":1400},[679,126109,126110,126112,126114,126117,126119],{"class":681,"line":988},[679,126111,126083],{"class":693},[679,126113,46928],{"class":880},[679,126115,126116],{"class":693},"(greeter",[679,126118,90007],{"class":685},[679,126120,126121],{"class":693},"greet);\n",[651,126123,126124,126125,126128,126129,126131,126132,126135],{},"Here, ",[676,126126,126127],{},"greeter::greet"," is referring to the ",[676,126130,126096],{}," method of our ",[676,126133,126134],{},"greeter"," object. It's a more concise way to say \"for each name, call the greet method of the greeter object\".",[5909,126137,126139],{"id":126138},"_3-reference-to-an-instance-method-of-an-arbitrary-object-of-a-particular-type","3. Reference to an Instance Method of an Arbitrary Object of a Particular Type",[651,126141,126142],{},"This one's a bit of a mouthful, but it's useful when you want to call a method that's defined in the object you're processing.",[669,126144,126146],{"className":4107,"code":126145,"language":4109,"meta":674,"style":674},"// Without method reference\nList\u003CString> team = Arrays.asList(\"Tasha\", \"Dan\", \"Josh\", \"DaShaun\", \"Cora\", \"Whitney\", \"Cote\");\nteam.sort((s1, s2) -> s1.compareToIgnoreCase(s2));\n\n// With method reference\nteam.sort(String::compareToIgnoreCase);\n",[676,126147,126148,126152,126202,126223,126227,126231],{"__ignoreMap":674},[679,126149,126150],{"class":681,"line":682},[679,126151,125870],{"class":1400},[679,126153,126154,126156,126158,126161,126163,126165,126167,126169,126172,126174,126176,126178,126180,126182,126185,126187,126190,126192,126195,126197,126200],{"class":681,"line":790},[679,126155,125875],{"class":693},[679,126157,4758],{"class":685},[679,126159,126160],{"class":693},"> team ",[679,126162,686],{"class":685},[679,126164,38274],{"class":693},[679,126166,38277],{"class":880},[679,126168,745],{"class":693},[679,126170,126171],{"class":689},"\"Tasha\"",[679,126173,2797],{"class":693},[679,126175,1414],{"class":689},[679,126177,2797],{"class":693},[679,126179,87855],{"class":689},[679,126181,2797],{"class":693},[679,126183,126184],{"class":689},"\"DaShaun\"",[679,126186,2797],{"class":693},[679,126188,126189],{"class":689},"\"Cora\"",[679,126191,2797],{"class":693},[679,126193,126194],{"class":689},"\"Whitney\"",[679,126196,2797],{"class":693},[679,126198,126199],{"class":689},"\"Cote\"",[679,126201,1208],{"class":693},[679,126203,126204,126207,126209,126212,126214,126217,126220],{"class":681,"line":892},[679,126205,126206],{"class":693},"team.",[679,126208,51928],{"class":880},[679,126210,126211],{"class":693},"((s1, s2) ",[679,126213,16955],{"class":685},[679,126215,126216],{"class":693}," s1.",[679,126218,126219],{"class":880},"compareToIgnoreCase",[679,126221,126222],{"class":693},"(s2));\n",[679,126224,126225],{"class":681,"line":901},[679,126226,889],{"emptyLinePlaceholder":797},[679,126228,126229],{"class":681,"line":909},[679,126230,125937],{"class":1400},[679,126232,126233,126235,126237,126240,126242],{"class":681,"line":918},[679,126234,126206],{"class":693},[679,126236,51928],{"class":880},[679,126238,126239],{"class":693},"(String",[679,126241,90007],{"class":685},[679,126243,126244],{"class":693},"compareToIgnoreCase);\n",[651,126246,126247,126248,126251,126252,126255],{},"In this case, ",[676,126249,126250],{},"String::compareTo"," is saying \"use the compareTo method of String objects\". Java figures out that it should call ",[676,126253,126254],{},"compareTo"," on the first string, passing the second string as an argument.",[5909,126257,126259],{"id":126258},"_4-reference-to-a-constructor","4. Reference to a Constructor",[651,126261,126262],{},"Last but not least, you can use method references to create new objects. This is particularly handy when you're mapping or collecting streams.",[669,126264,126266],{"className":4107,"code":126265,"language":4109,"meta":674,"style":674},"// Without method reference\nList\u003CString> names = Arrays.asList(\"Alice\", \"Bob\", \"Charlie\");\nList\u003CPerson> people1 = names.stream()\n .map(name -> new Person(name))\n .toList();\n\n// With method reference\nList\u003CPerson> people2 = names.stream()\n .map(Person::new)\n .toList();\n",[676,126267,126268,126272,126304,126322,126338,126346,126350,126354,126371,126385],{"__ignoreMap":674},[679,126269,126270],{"class":681,"line":682},[679,126271,125870],{"class":1400},[679,126273,126274,126276,126278,126281,126283,126285,126287,126289,126292,126294,126297,126299,126302],{"class":681,"line":790},[679,126275,125875],{"class":693},[679,126277,4758],{"class":685},[679,126279,126280],{"class":693},"> names ",[679,126282,686],{"class":685},[679,126284,38274],{"class":693},[679,126286,38277],{"class":880},[679,126288,745],{"class":693},[679,126290,126291],{"class":689},"\"Alice\"",[679,126293,2797],{"class":693},[679,126295,126296],{"class":689},"\"Bob\"",[679,126298,2797],{"class":693},[679,126300,126301],{"class":689},"\"Charlie\"",[679,126303,1208],{"class":693},[679,126305,126306,126308,126310,126313,126315,126318,126320],{"class":681,"line":892},[679,126307,125875],{"class":693},[679,126309,83905],{"class":685},[679,126311,126312],{"class":693},"> people1 ",[679,126314,686],{"class":685},[679,126316,126317],{"class":693}," names.",[679,126319,87323],{"class":880},[679,126321,17545],{"class":693},[679,126323,126324,126326,126328,126330,126332,126334,126336],{"class":681,"line":901},[679,126325,21331],{"class":693},[679,126327,51904],{"class":880},[679,126329,126088],{"class":693},[679,126331,16955],{"class":685},[679,126333,2054],{"class":685},[679,126335,83929],{"class":880},[679,126337,87781],{"class":693},[679,126339,126340,126342,126344],{"class":681,"line":909},[679,126341,21331],{"class":693},[679,126343,85083],{"class":880},[679,126345,9317],{"class":693},[679,126347,126348],{"class":681,"line":918},[679,126349,889],{"emptyLinePlaceholder":797},[679,126351,126352],{"class":681,"line":935},[679,126353,125937],{"class":1400},[679,126355,126356,126358,126360,126363,126365,126367,126369],{"class":681,"line":944},[679,126357,125875],{"class":693},[679,126359,83905],{"class":685},[679,126361,126362],{"class":693},"> people2 ",[679,126364,686],{"class":685},[679,126366,126317],{"class":693},[679,126368,87323],{"class":880},[679,126370,17545],{"class":693},[679,126372,126373,126375,126377,126380,126383],{"class":681,"line":959},[679,126374,21331],{"class":693},[679,126376,51904],{"class":880},[679,126378,126379],{"class":693},"(Person",[679,126381,126382],{"class":685},"::new",[679,126384,1339],{"class":693},[679,126386,126387,126389,126391],{"class":681,"line":964},[679,126388,21331],{"class":693},[679,126390,85083],{"class":880},[679,126392,9317],{"class":693},[651,126394,126124,126395,126398],{},[676,126396,126397],{},"Person::new"," is a reference to the Person constructor that takes a String argument. It's a concise way to say \"create a new Person object for each name\".",[4542,126400,78006],{"id":78005},[651,126402,126403],{},"Method references are a powerful tool in your Java toolbox. They can help you write more concise, readable code, especially when working with functional interfaces and streams. As you get more comfortable with them, you'll find yourself reaching for them more often in your day-to-day coding.",[651,126405,126406],{},"Remember, good code isn't just about solving problems—it's about solving them in a way that's clear and maintainable. Method references are one more step towards that goal.",[651,126408,126409],{},"So go ahead, give method references a try in your next Java project. Your future self (and your team members) will thank you for the clean, expressive code!",[651,126411,78024],{},[786,126413,126414],{},"html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":126416},[126417,126418,126419,126420,126426],{"id":125794,"depth":790,"text":125795},{"id":125801,"depth":790,"text":125802},{"id":125828,"depth":790,"text":125829},{"id":125852,"depth":790,"text":125853,"children":126421},[126422,126423,126424,126425],{"id":125856,"depth":892,"text":125857},{"id":125959,"depth":892,"text":125960},{"id":126138,"depth":892,"text":126139},{"id":126258,"depth":892,"text":126259},{"id":78005,"depth":790,"text":78006},"Discover the power of method references in Java! This beginner-friendly guide explains what method references are, why they're useful, and how to use them effectively. With clear explanations and practical code examples, you'll learn to write cleaner, more expressive code using this powerful Java feature.",{"slug":126429,"date":126430,"published":797,"author":798,"tags":126431,"cover":126433,"video":126434,"keywords":126435},"java-method-references","2024-08-01T09:00:00.000Z",[36422,126432],"Functional Programming","java_method_references_with_me.png","https://www.youtube.com/embed/DELCbBuCHHE","Java, Method References, Lambda Expressions, Functional Programming, Java 8 Features",{"title":87,"description":126427},"blog/2024/08/01/java-method-references","HNFxyh7aEVXWCia45e2Mh7nLmFPu93fSMnYwtaBUBG4",{"id":126440,"title":84,"body":126441,"description":126899,"extension":793,"meta":126900,"navigation":797,"path":85,"seo":126908,"stem":126909,"__hash__":126910},"content/blog/2024/08/05/ollama-web-ui.md",{"type":648,"value":126442,"toc":126879},[126443,126450,126456,126460,126463,126467,126476,126480,126483,126488,126491,126494,126498,126524,126528,126531,126535,126543,126548,126551,126555,126558,126563,126567,126570,126590,126593,126597,126600,126616,126619,126632,126635,126640,126644,126647,126665,126668,126672,126679,126683,126686,126707,126714,126719,126723,126726,126737,126740,126744,126748,126751,126776,126780,126783,126798,126813,126817,126820,126832,126855,126858,126860,126863,126868,126873,126876],[651,126444,126445,126446,126449],{},"Welcome back, everyone. Dan Vega here, and today we are going to dive into the fascinating world of ",[2939,126447,126448],{},"Ollama",". But you might be wondering, what exactly is Ollama, and why should you care about it? Well, let's break it down.",[651,126451,126452],{},[660,126453],{"alt":126454,"src":126455},"Ollama Website","/images/blog/2024/08/05/01.png",[4542,126457,126459],{"id":126458},"introduction-to-ollama","Introduction to Ollama",[651,126461,126462],{},"Ollama is a tool that allows us to get up and running with large language models (LLMs) on our local machines. It's part of the wave of new technology that includes models like the newly open-sourced Llama 3.1 from Meta, Mistral, and many more. The big question on your mind might be: why would anyone want to run an LLM locally?",[4542,126464,126466],{"id":126465},"why-run-large-language-models-locally","Why Run Large Language Models Locally?",[651,126468,126469,126470,23212,126473,664],{},"In my opinion here are two primary reasons for doing this: ",[2939,126471,126472],{},"cost",[2939,126474,126475],{},"security",[5909,126477,126479],{"id":126478},"cost-efficiency","Cost Efficiency",[651,126481,126482],{},"When you're using large language models from providers like OpenAI's GPT-4, Google Gemini, etc., there are costs involved. While prototyping or developing an MVP might not incur substantial costs, you will eventually need to put a credit card on file for continued use.",[651,126484,126485],{},[660,126486],{"alt":674,"src":126487},"/images/blog/2024/08/05/02.png",[5909,126489,126490],{"id":126475},"Security",[651,126492,126493],{},"For enterprises or organizations dealing with private or sensitive documents, running an LLM locally can be a game-changer. It ensures that your data doesn't have to leave your secure environment, making it perfect for tasks involving confidential information.",[5909,126495,126497],{"id":126496},"additional-reasons-to-run-an-llm-locally","Additional Reasons to run an LLM Locally",[5316,126499,126500,126506,126512,126518],{},[5332,126501,126502,126505],{},[2939,126503,126504],{},"Offline access:"," Local LLMs can operate without an internet connection, which is beneficial in areas with poor connectivity or in situations where internet access is restricted.",[5332,126507,126508,126511],{},[2939,126509,126510],{},"Lower latency:"," Local models can provide faster response times since there's no need to send requests to and receive responses from a remote server.",[5332,126513,126514,126517],{},[2939,126515,126516],{},"Customization and fine-tuning:"," Local deployment allows for easier customization and fine-tuning of the model to specific use cases or domains.",[5332,126519,126520,126523],{},[2939,126521,126522],{},"Regulatory compliance:"," For industries with strict data regulations, local LLMs can help ensure compliance by keeping data within controlled environments.",[4542,126525,126527],{"id":126526},"getting-started-with-ollama","Getting Started with Ollama",[651,126529,126530],{},"Here's how you can get started with Ollama and make your development experience smoother.",[5909,126532,126534],{"id":126533},"download-and-installation","Download and Installation",[651,126536,105635,126537,126542],{},[812,126538,126541],{"href":126539,"rel":126540},"https://ollamadomain.com",[816],"Ollama's website"," and download the necessary files. Ollama is compatible with macOS, Linux, and Windows. For this guide, I will be using macOS.",[651,126544,126545],{},[660,126546],{"alt":674,"src":126547},"/images/blog/2024/08/05/03.png",[651,126549,126550],{},"Once you've downloaded it, follow the installation steps. This will install a Command Line Interface (CLI) on your system.",[5909,126552,126554],{"id":126553},"selecting-a-model","Selecting a Model",[651,126556,126557],{},"After the CLI is up and running, you’ll need to choose a model. Ollama supports various models, each optimized for different tasks. For instance, you might find models optimized for conversational interactions, long-context tasks, and more.",[651,126559,126560],{},[660,126561],{"alt":674,"src":126562},"/images/blog/2024/08/05/04.png",[5909,126564,126566],{"id":126565},"example-with-llama-31","Example with Llama 3.1",[651,126568,126569],{},"For this tutorial, we'll use Llama 3.1, recently released by Meta. It comes in several sizes:",[5316,126571,126572,126578,126584],{},[5332,126573,126574,126577],{},[2939,126575,126576],{},"8B",": 4.7GB",[5332,126579,126580,126583],{},[2939,126581,126582],{},"70B",": 40GB",[5332,126585,126586,126589],{},[2939,126587,126588],{},"405B",": 231GB",[651,126591,126592],{},"Remember, larger models require more storage space and processing power. The 8B model is a great starting point as it doesn't demand hefty resources.",[5909,126594,126596],{"id":126595},"running-the-cli","Running the CLI",[651,126598,126599],{},"To run the model, you first need to download it. Here’s a sample command to get you started:",[669,126601,126603],{"className":122951,"code":126602,"language":122953,"meta":674,"style":674},"ollama download llama-3.1-8B\n",[676,126604,126605],{"__ignoreMap":674},[679,126606,126607,126610,126613],{"class":681,"line":682},[679,126608,126609],{"class":880},"ollama",[679,126611,126612],{"class":689}," download",[679,126614,126615],{"class":689}," llama-3.1-8B\n",[651,126617,126618],{},"After downloading, run the command to initialize the model:",[669,126620,126622],{"className":122951,"code":126621,"language":122953,"meta":674,"style":674},"ollama run llama-3.1-8B\n",[676,126623,126624],{"__ignoreMap":674},[679,126625,126626,126628,126630],{"class":681,"line":682},[679,126627,126609],{"class":880},[679,126629,16486],{"class":689},[679,126631,126615],{"class":689},[651,126633,126634],{},"Once you run the command, you’ll be prompted to interact with the LLM directly through the CLI, allowing you to send messages and receive responses.",[651,126636,126637],{},[660,126638],{"alt":674,"src":126639},"/images/blog/2024/08/05/05.png",[5259,126641,126643],{"id":126642},"call-the-llm","Call the LLM",[651,126645,126646],{},"A fun way to test large language models is through something non trivial, like Dad Jokes. For example, if you ask the model “Tell me a funny dad joke about computers” you might get a response similar to this:",[669,126648,126650],{"className":122951,"code":126649,"language":122953,"meta":674,"style":674},"> Why did the computer go to the doctor?> Because it had a virus.\n",[676,126651,126652],{"__ignoreMap":674},[679,126653,126654,126656,126659,126662],{"class":681,"line":682},[679,126655,5860],{"class":685},[679,126657,126658],{"class":693}," Why did the computer go to the doctor",[679,126660,126661],{"class":685},"?>",[679,126663,126664],{"class":693}," Because it had a virus.\n",[651,126666,126667],{},"These interactions showcase the LLM’s ability to process and respond to prompts quickly, all done locally.",[4542,126669,126671],{"id":126670},"enhancing-developer-experience-with-open-web-ui","Enhancing Developer Experience with Open Web UI",[651,126673,126674,126675,126678],{},"While the CLI is great for quick tests, a more robust developer experience can be achieved through a project called ",[2939,126676,126677],{},"Open Web UI",". This self-hosted web UI is designed to operate offline and supports various LLM runners, including Ollama.",[5909,126680,126682],{"id":126681},"setting-up-open-web-ui","Setting Up Open Web UI",[651,126684,126685],{},"To get started, ensure you have Docker Desktop installed. With Ollama and Docker set up, run the following command:",[669,126687,126689],{"className":122951,"code":126688,"language":122953,"meta":674,"style":674},"docker run -d -p 3000:3000 openwebui/ollama\n",[676,126690,126691],{"__ignoreMap":674},[679,126692,126693,126695,126697,126699,126701,126704],{"class":681,"line":682},[679,126694,112824],{"class":880},[679,126696,16486],{"class":689},[679,126698,112845],{"class":931},[679,126700,30144],{"class":931},[679,126702,126703],{"class":689}," 3000:3000",[679,126705,126706],{"class":689}," openwebui/ollama\n",[651,126708,126709,126710,126713],{},"Check Docker Desktop to confirm that Open Web UI is running. Now, access the UI via ",[676,126711,126712],{},"http://localhost:3000",". You may need to authorize access initially.",[651,126715,126716],{},[660,126717],{"alt":674,"src":126718},"/images/blog/2024/08/05/06.png",[5909,126720,126722],{"id":126721},"exploring-the-ui","Exploring the UI",[651,126724,126725],{},"Open Web UI offers a familiar interface if you've used applications like ChatGPT. It allows you to manage and interact with multiple models simultaneously, offering features like:",[5316,126727,126728,126731,126734],{},[5332,126729,126730],{},"Model selection",[5332,126732,126733],{},"Contextual conversation history",[5332,126735,126736],{},"Code formatting for better readability",[651,126738,126739],{},"Select the default model (e.g., Llama 3.1) to streamline interactions.",[5909,126741,126743],{"id":126742},"practical-examples","Practical Examples",[5259,126745,126747],{"id":126746},"conversational-example","Conversational Example",[651,126749,126750],{},"Start a chat with something light-hearted, like:",[669,126752,126754],{"className":122951,"code":126753,"language":122953,"meta":674,"style":674},"> Tell me a dad joke about dogs.> Why did the dog go to the vet? Because it was feeling a little \"ruff.\"\n",[676,126755,126756],{"__ignoreMap":674},[679,126757,126758,126760,126763,126765,126768,126770,126773],{"class":681,"line":682},[679,126759,5860],{"class":685},[679,126761,126762],{"class":693}," Tell me a dad joke about dogs.",[679,126764,5860],{"class":685},[679,126766,126767],{"class":693}," Why did the dog go to the vet",[679,126769,2381],{"class":685},[679,126771,126772],{"class":693}," Because it was feeling a little ",[679,126774,126775],{"class":689},"\"ruff.\"\n",[5259,126777,126779],{"id":126778},"coding-example","Coding Example",[651,126781,126782],{},"Now, let's see how it handles code-related tasks:",[669,126784,126786],{"className":122951,"code":126785,"language":122953,"meta":674,"style":674},"> What are the best ways to iterate over a list in Java?\n",[676,126787,126788],{"__ignoreMap":674},[679,126789,126790,126792,126795],{"class":681,"line":682},[679,126791,5860],{"class":685},[679,126793,126794],{"class":693}," What are the best ways to iterate over a list in Java",[679,126796,126797],{"class":685},"?\n",[651,126799,126800,126801,2797,126804,2797,126807,48406,126810,664],{},"The response might include several methods like ",[676,126802,126803],{},"for-each loop",[676,126805,126806],{},"traditional for loop",[676,126808,126809],{},"iterator",[676,126811,126812],{},"streams API",[5259,126814,126816],{"id":126815},"documentation-usage","Documentation Usage",[651,126818,126819],{},"Ollama allows you to upload your own documents for a more tailored response. For instance, if you're working with Spring Boot 3.2, provide relevant documentation:",[27665,126821,126822,126829],{},[5332,126823,126824,126825,126828],{},"Click on ",[676,126826,126827],{},"More"," and upload the file.",[5332,126830,126831],{},"Re-ask the question relevant to the uploaded docs.",[669,126833,126835],{"className":122951,"code":126834,"language":122953,"meta":674,"style":674},"> Give me a code example of how to use the `REST Client` in Spring Boot 3.2.\n",[676,126836,126837],{"__ignoreMap":674},[679,126838,126839,126841,126844,126846,126849,126852],{"class":681,"line":682},[679,126840,5860],{"class":685},[679,126842,126843],{"class":693}," Give me a code example of how to use the ",[679,126845,44388],{"class":689},[679,126847,126848],{"class":880},"REST",[679,126850,126851],{"class":689}," Client`",[679,126853,126854],{"class":693}," in Spring Boot 3.2.\n",[651,126856,126857],{},"With the document uploaded, the LLM can now offer accurate and current details, enhancing its utility for your specific needs.",[4542,126859,9042],{"id":9041},[651,126861,126862],{},"Running large language models locally using Ollama, enhanced with tools like Open Web UI, provides significant advantages in terms of cost and security. The capability to customize interactions with local data makes it a compelling choice for developers and enterprises alike.",[651,126864,126865],{},[660,126866],{"alt":674,"src":126867},"/images/blog/2024/08/05/07.png",[1004,126869,126870],{},[651,126871,126872],{},"\"For me, the ability to add my own documents is what really brings this home. It allows customization and privacy that online LLMs can't offer.\"",[651,126874,126875],{},"As always, happy coding!",[786,126877,126878],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":126880},[126881,126882,126887,126893,126898],{"id":126458,"depth":790,"text":126459},{"id":126465,"depth":790,"text":126466,"children":126883},[126884,126885,126886],{"id":126478,"depth":892,"text":126479},{"id":126475,"depth":892,"text":126490},{"id":126496,"depth":892,"text":126497},{"id":126526,"depth":790,"text":126527,"children":126888},[126889,126890,126891,126892],{"id":126533,"depth":892,"text":126534},{"id":126553,"depth":892,"text":126554},{"id":126565,"depth":892,"text":126566},{"id":126595,"depth":892,"text":126596},{"id":126670,"depth":790,"text":126671,"children":126894},[126895,126896,126897],{"id":126681,"depth":892,"text":126682},{"id":126721,"depth":892,"text":126722},{"id":126742,"depth":892,"text":126743},{"id":9041,"depth":790,"text":9042},"This guide introduces Ollama, a tool for running large language models (LLMs) locally, and its integration with Open Web UI. It highlights the cost and security benefits of local LLM deployment, providing setup instructions for Ollama and demonstrating how to use Open Web UI for enhanced model interaction.",{"slug":126901,"date":126902,"published":797,"author":798,"tags":126903,"cover":126905,"video":126906,"keywords":126907},"ollama-web-ui","2024-08-05T09:00:00.000Z",[126904],"AI","ollama_web_ui.jpeg","https://www.youtube.com/embed/BzFafshQkWw","Artificial Intelligence, AI, Ollama, Llama, Llama 3.1, Web UI",{"title":84,"description":126899},"blog/2024/08/05/ollama-web-ui","ZsUMlVR32JFbghy3TAo7AFh53lj5_eTSyQWdNvVZD0Q",{"id":126912,"title":81,"body":126913,"description":129579,"extension":793,"meta":129580,"navigation":797,"path":82,"seo":129586,"stem":129587,"__hash__":129588},"content/blog/2024/08/06/claude-sonnet-35.md",{"type":648,"value":126914,"toc":129560},[126915,126924,126928,126932,126935,126946,126950,126953,126964,126968,126971,126982,126986,127003,127007,127010,127014,127017,127023,127027,127030,127034,127037,127256,127259,127859,127863,127866,127869,128124,128127,128133,128137,128140,128143,128146,128149,129420,129423,129429,129433,129436,129440,129443,129447,129473,129477,129509,129513,129543,129546,129548,129551,129554,129557],[651,126916,126917,126918,126923],{},"Artificial Intelligence has taken another leap forward with the release of ",[812,126919,126922],{"href":126920,"rel":126921},"https://www.anthropic.com/news/claude-3-5-sonnet",[816],"Claude 3.5 Sonnet by Anthropic",". This new model promises to revolutionize the way we interact with AI, offering enhanced capabilities, improved speed, and a novel feature called Artifacts. In this blog post, we'll explore the highlights of Claude 3.5 Sonnet and how it can benefit both beginners and seasoned AI enthusiasts.",[4542,126925,126927],{"id":126926},"key-features-of-claude-35-sonnet","Key Features of Claude 3.5 Sonnet",[5909,126929,126931],{"id":126930},"_1-intelligence-and-speed","1. Intelligence and Speed",[651,126933,126934],{},"Claude 3.5 Sonnet sets new benchmarks in the AI industry:",[5316,126936,126937,126940,126943],{},[5332,126938,126939],{},"Outperforms competitor models and its predecessor, Claude 3 Opus, on various evaluations",[5332,126941,126942],{},"Operates at twice the speed of Claude 3 Opus",[5332,126944,126945],{},"Excels in graduate-level reasoning, undergraduate-level knowledge, and coding proficiency",[5909,126947,126949],{"id":126948},"_2-enhanced-capabilities","2. Enhanced Capabilities",[651,126951,126952],{},"The new model shows significant improvements in:",[5316,126954,126955,126958,126961],{},[5332,126956,126957],{},"Understanding nuance, humor, and complex instructions",[5332,126959,126960],{},"Writing high-quality content with a natural, relatable tone",[5332,126962,126963],{},"Advanced coding abilities, including bug fixing and adding functionality to existing codebases",[5909,126965,126967],{"id":126966},"_3-state-of-the-art-vision","3. State-of-the-Art Vision",[651,126969,126970],{},"Claude 3.5 Sonnet boasts impressive visual capabilities:",[5316,126972,126973,126976,126979],{},[5332,126974,126975],{},"Strongest vision model in the Claude family",[5332,126977,126978],{},"Excels at visual reasoning tasks like interpreting charts and graphs",[5332,126980,126981],{},"Accurately transcribes text from imperfect images",[5909,126983,126985],{"id":126984},"_4-availability-and-pricing","4. Availability and Pricing",[5316,126987,126988,126991,126994,126997,127000],{},[5332,126989,126990],{},"Available for free on Claude.ai and the Claude iOS app",[5332,126992,126993],{},"Higher rate limits for Claude Pro and Team plan subscribers",[5332,126995,126996],{},"Accessible via Anthropic API, Amazon Bedrock, and Google Cloud's Vertex AI",[5332,126998,126999],{},"Priced at $3 per million input tokens and $15 per million output tokens",[5332,127001,127002],{},"200K token context window",[4542,127004,127006],{"id":127005},"introducing-artifacts-a-new-way-to-interact-with-claude","Introducing Artifacts: A New Way to Interact with Claude",[651,127008,127009],{},"One of the most exciting features of Claude 3.5 Sonnet is the introduction of Artifacts. This new functionality creates a more dynamic and collaborative workspace for users on Claude.ai. Let's explore how Artifacts work and some practical examples of how you can use them.",[5909,127011,127013],{"id":127012},"what-are-artifacts","What are Artifacts?",[651,127015,127016],{},"Artifacts are dedicated windows that appear alongside your conversation with Claude, displaying generated content such as code snippets, text documents, or website designs. This feature allows users to see, edit, and build upon Claude's creations in real-time, seamlessly integrating AI-generated content into their projects and workflows. At the time of writing this article this is a preview feature and you will need to enable it:",[651,127018,127019],{},[660,127020],{"alt":127021,"src":127022},"Feature Preview","/images/blog/2024/08/06/artifacts_feature.png",[5909,127024,127026],{"id":127025},"practical-examples-of-using-artifacts","Practical Examples of Using Artifacts",[651,127028,127029],{},"Let's look at three examples of how you can use Artifacts with Claude 3.5 Sonnet:",[5259,127031,127033],{"id":127032},"_1-code-generation-simple-calculator","1. Code Generation: Simple Calculator",[651,127035,127036],{},"Ask Claude to create a basic calculator class in Java:",[669,127038,127040],{"className":4107,"code":127039,"language":4109,"meta":674,"style":674},"public class SimpleCalculator {\n public double add(double a, double b) {\n return a + b;\n }\n\n public double subtract(double a, double b) {\n return a - b;\n }\n\n public double multiply(double a, double b) {\n return a * b;\n }\n\n public double divide(double a, double b) {\n if (b == 0) {\n throw new ArithmeticException(\"Division by zero is not allowed\");\n }\n return a / b;\n }\n \n}\n",[676,127041,127042,127053,127077,127087,127091,127095,127118,127128,127132,127136,127159,127169,127173,127177,127200,127212,127229,127233,127243,127247,127252],{"__ignoreMap":674},[679,127043,127044,127046,127048,127051],{"class":681,"line":682},[679,127045,6073],{"class":685},[679,127047,4512],{"class":685},[679,127049,127050],{"class":880}," SimpleCalculator",[679,127052,884],{"class":693},[679,127054,127055,127057,127060,127062,127064,127066,127068,127070,127072,127075],{"class":681,"line":790},[679,127056,19606],{"class":685},[679,127058,127059],{"class":685}," double",[679,127061,8872],{"class":880},[679,127063,745],{"class":693},[679,127065,1110],{"class":685},[679,127067,21697],{"class":2099},[679,127069,2797],{"class":693},[679,127071,1110],{"class":685},[679,127073,127074],{"class":2099}," b",[679,127076,4390],{"class":693},[679,127078,127079,127081,127083,127085],{"class":681,"line":892},[679,127080,63468],{"class":685},[679,127082,51944],{"class":693},[679,127084,3065],{"class":685},[679,127086,14272],{"class":693},[679,127088,127089],{"class":681,"line":901},[679,127090,19637],{"class":693},[679,127092,127093],{"class":681,"line":909},[679,127094,889],{"emptyLinePlaceholder":797},[679,127096,127097,127099,127101,127104,127106,127108,127110,127112,127114,127116],{"class":681,"line":918},[679,127098,19606],{"class":685},[679,127100,127059],{"class":685},[679,127102,127103],{"class":880}," subtract",[679,127105,745],{"class":693},[679,127107,1110],{"class":685},[679,127109,21697],{"class":2099},[679,127111,2797],{"class":693},[679,127113,1110],{"class":685},[679,127115,127074],{"class":2099},[679,127117,4390],{"class":693},[679,127119,127120,127122,127124,127126],{"class":681,"line":935},[679,127121,63468],{"class":685},[679,127123,51944],{"class":693},[679,127125,6453],{"class":685},[679,127127,14272],{"class":693},[679,127129,127130],{"class":681,"line":944},[679,127131,19637],{"class":693},[679,127133,127134],{"class":681,"line":959},[679,127135,889],{"emptyLinePlaceholder":797},[679,127137,127138,127140,127142,127145,127147,127149,127151,127153,127155,127157],{"class":681,"line":964},[679,127139,19606],{"class":685},[679,127141,127059],{"class":685},[679,127143,127144],{"class":880}," multiply",[679,127146,745],{"class":693},[679,127148,1110],{"class":685},[679,127150,21697],{"class":2099},[679,127152,2797],{"class":693},[679,127154,1110],{"class":685},[679,127156,127074],{"class":2099},[679,127158,4390],{"class":693},[679,127160,127161,127163,127165,127167],{"class":681,"line":977},[679,127162,63468],{"class":685},[679,127164,51944],{"class":693},[679,127166,4150],{"class":685},[679,127168,14272],{"class":693},[679,127170,127171],{"class":681,"line":982},[679,127172,19637],{"class":693},[679,127174,127175],{"class":681,"line":988},[679,127176,889],{"emptyLinePlaceholder":797},[679,127178,127179,127181,127183,127186,127188,127190,127192,127194,127196,127198],{"class":681,"line":993},[679,127180,19606],{"class":685},[679,127182,127059],{"class":685},[679,127184,127185],{"class":880}," divide",[679,127187,745],{"class":693},[679,127189,1110],{"class":685},[679,127191,21697],{"class":2099},[679,127193,2797],{"class":693},[679,127195,1110],{"class":685},[679,127197,127074],{"class":2099},[679,127199,4390],{"class":693},[679,127201,127202,127204,127206,127208,127210],{"class":681,"line":2129},[679,127203,59232],{"class":685},[679,127205,14836],{"class":693},[679,127207,2304],{"class":685},[679,127209,14987],{"class":931},[679,127211,4390],{"class":693},[679,127213,127214,127217,127219,127222,127224,127227],{"class":681,"line":2140},[679,127215,127216],{"class":685}," throw",[679,127218,2054],{"class":685},[679,127220,127221],{"class":880}," ArithmeticException",[679,127223,745],{"class":693},[679,127225,127226],{"class":689},"\"Division by zero is not allowed\"",[679,127228,1208],{"class":693},[679,127230,127231],{"class":681,"line":2145},[679,127232,11804],{"class":693},[679,127234,127235,127237,127239,127241],{"class":681,"line":2154},[679,127236,63468],{"class":685},[679,127238,51944],{"class":693},[679,127240,4408],{"class":685},[679,127242,14272],{"class":693},[679,127244,127245],{"class":681,"line":2159},[679,127246,19637],{"class":693},[679,127248,127249],{"class":681,"line":2164},[679,127250,127251],{"class":693}," \n",[679,127253,127254],{"class":681,"line":3134},[679,127255,996],{"class":693},[651,127257,127258],{},"This code will appear in the Artifacts window, where you can easily edit or expand upon it. For instance, you might ask Claude to write unit tests for this class:",[669,127260,127262],{"className":4107,"code":127261,"language":4109,"meta":674,"style":674},"import org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class SimpleCalculatorTest {\n private SimpleCalculator calculator;\n\n @BeforeEach\n void setUp() {\n calculator = new SimpleCalculator();\n }\n\n @Test\n void testAdd() {\n assertEquals(8, calculator.add(5, 3), \"5 + 3 should equal 8\");\n assertEquals(0, calculator.add(-5, 5), \"-5 + 5 should equal 0\");\n assertEquals(-8, calculator.add(-5, -3), \"-5 + -3 should equal -8\");\n }\n\n @Test\n void testSubtract() {\n assertEquals(2, calculator.subtract(5, 3), \"5 - 3 should equal 2\");\n assertEquals(-10, calculator.subtract(-5, 5), \"-5 - 5 should equal -10\");\n assertEquals(-2, calculator.subtract(-5, -3), \"-5 - -3 should equal -2\");\n }\n\n @Test\n void testMultiply() {\n assertEquals(15, calculator.multiply(5, 3), \"5 * 3 should equal 15\");\n assertEquals(-25, calculator.multiply(-5, 5), \"-5 * 5 should equal -25\");\n assertEquals(15, calculator.multiply(-5, -3), \"-5 * -3 should equal 15\");\n }\n\n @Test\n void testDivide() {\n assertEquals(2, calculator.divide(6, 3), \"6 / 3 should equal 2\");\n assertEquals(-2, calculator.divide(-6, 3), \"-6 / 3 should equal -2\");\n assertEquals(2, calculator.divide(-6, -3), \"-6 / -3 should equal 2\");\n }\n\n @Test\n void testDivideByZero() {\n assertThrows(ArithmeticException.class, () -> calculator.divide(5, 0),\n \"Dividing by zero should throw ArithmeticException\");\n }\n}\n",[676,127263,127264,127270,127276,127288,127292,127303,127310,127314,127320,127328,127341,127345,127349,127355,127364,127392,127421,127454,127458,127462,127468,127477,127505,127536,127569,127573,127577,127583,127592,127621,127652,127683,127687,127691,127697,127706,127734,127765,127796,127800,127804,127810,127819,127844,127851,127855],{"__ignoreMap":674},[679,127265,127266,127268],{"class":681,"line":682},[679,127267,1999],{"class":685},[679,127269,94718],{"class":693},[679,127271,127272,127274],{"class":681,"line":790},[679,127273,1999],{"class":685},[679,127275,73054],{"class":693},[679,127277,127278,127280,127282,127284,127286],{"class":681,"line":892},[679,127279,1999],{"class":685},[679,127281,6092],{"class":685},[679,127283,93962],{"class":693},[679,127285,4150],{"class":931},[679,127287,1186],{"class":693},[679,127289,127290],{"class":681,"line":901},[679,127291,889],{"emptyLinePlaceholder":797},[679,127293,127294,127296,127298,127301],{"class":681,"line":909},[679,127295,6073],{"class":685},[679,127297,4512],{"class":685},[679,127299,127300],{"class":880}," SimpleCalculatorTest",[679,127302,884],{"class":693},[679,127304,127305,127307],{"class":681,"line":918},[679,127306,9232],{"class":685},[679,127308,127309],{"class":693}," SimpleCalculator calculator;\n",[679,127311,127312],{"class":681,"line":935},[679,127313,889],{"emptyLinePlaceholder":797},[679,127315,127316,127318],{"class":681,"line":944},[679,127317,6872],{"class":693},[679,127319,94862],{"class":685},[679,127321,127322,127324,127326],{"class":681,"line":959},[679,127323,3314],{"class":685},[679,127325,94869],{"class":880},[679,127327,2667],{"class":693},[679,127329,127330,127333,127335,127337,127339],{"class":681,"line":964},[679,127331,127332],{"class":693}," calculator ",[679,127334,686],{"class":685},[679,127336,2054],{"class":685},[679,127338,127050],{"class":880},[679,127340,9317],{"class":693},[679,127342,127343],{"class":681,"line":977},[679,127344,985],{"class":693},[679,127346,127347],{"class":681,"line":982},[679,127348,889],{"emptyLinePlaceholder":797},[679,127350,127351,127353],{"class":681,"line":988},[679,127352,6872],{"class":693},[679,127354,73087],{"class":685},[679,127356,127357,127359,127362],{"class":681,"line":993},[679,127358,3314],{"class":685},[679,127360,127361],{"class":880}," testAdd",[679,127363,2667],{"class":693},[679,127365,127366,127368,127370,127372,127375,127377,127379,127381,127383,127385,127387,127390],{"class":681,"line":2129},[679,127367,73133],{"class":880},[679,127369,745],{"class":693},[679,127371,73893],{"class":931},[679,127373,127374],{"class":693},", calculator.",[679,127376,12952],{"class":880},[679,127378,745],{"class":693},[679,127380,66775],{"class":931},[679,127382,2797],{"class":693},[679,127384,66599],{"class":931},[679,127386,16686],{"class":693},[679,127388,127389],{"class":689},"\"5 + 3 should equal 8\"",[679,127391,1208],{"class":693},[679,127393,127394,127396,127398,127400,127402,127404,127406,127408,127410,127412,127414,127416,127419],{"class":681,"line":2140},[679,127395,73133],{"class":880},[679,127397,745],{"class":693},[679,127399,1060],{"class":931},[679,127401,127374],{"class":693},[679,127403,12952],{"class":880},[679,127405,745],{"class":693},[679,127407,6453],{"class":685},[679,127409,66775],{"class":931},[679,127411,2797],{"class":693},[679,127413,66775],{"class":931},[679,127415,16686],{"class":693},[679,127417,127418],{"class":689},"\"-5 + 5 should equal 0\"",[679,127420,1208],{"class":693},[679,127422,127423,127425,127427,127429,127431,127433,127435,127437,127439,127441,127443,127445,127447,127449,127452],{"class":681,"line":2145},[679,127424,73133],{"class":880},[679,127426,745],{"class":693},[679,127428,6453],{"class":685},[679,127430,73893],{"class":931},[679,127432,127374],{"class":693},[679,127434,12952],{"class":880},[679,127436,745],{"class":693},[679,127438,6453],{"class":685},[679,127440,66775],{"class":931},[679,127442,2797],{"class":693},[679,127444,6453],{"class":685},[679,127446,66599],{"class":931},[679,127448,16686],{"class":693},[679,127450,127451],{"class":689},"\"-5 + -3 should equal -8\"",[679,127453,1208],{"class":693},[679,127455,127456],{"class":681,"line":2154},[679,127457,985],{"class":693},[679,127459,127460],{"class":681,"line":2159},[679,127461,889],{"emptyLinePlaceholder":797},[679,127463,127464,127466],{"class":681,"line":2164},[679,127465,6872],{"class":693},[679,127467,73087],{"class":685},[679,127469,127470,127472,127475],{"class":681,"line":3134},[679,127471,3314],{"class":685},[679,127473,127474],{"class":880}," testSubtract",[679,127476,2667],{"class":693},[679,127478,127479,127481,127483,127485,127487,127490,127492,127494,127496,127498,127500,127503],{"class":681,"line":3139},[679,127480,73133],{"class":880},[679,127482,745],{"class":693},[679,127484,17262],{"class":931},[679,127486,127374],{"class":693},[679,127488,127489],{"class":880},"subtract",[679,127491,745],{"class":693},[679,127493,66775],{"class":931},[679,127495,2797],{"class":693},[679,127497,66599],{"class":931},[679,127499,16686],{"class":693},[679,127501,127502],{"class":689},"\"5 - 3 should equal 2\"",[679,127504,1208],{"class":693},[679,127506,127507,127509,127511,127513,127515,127517,127519,127521,127523,127525,127527,127529,127531,127534],{"class":681,"line":3144},[679,127508,73133],{"class":880},[679,127510,745],{"class":693},[679,127512,6453],{"class":685},[679,127514,1205],{"class":931},[679,127516,127374],{"class":693},[679,127518,127489],{"class":880},[679,127520,745],{"class":693},[679,127522,6453],{"class":685},[679,127524,66775],{"class":931},[679,127526,2797],{"class":693},[679,127528,66775],{"class":931},[679,127530,16686],{"class":693},[679,127532,127533],{"class":689},"\"-5 - 5 should equal -10\"",[679,127535,1208],{"class":693},[679,127537,127538,127540,127542,127544,127546,127548,127550,127552,127554,127556,127558,127560,127562,127564,127567],{"class":681,"line":3149},[679,127539,73133],{"class":880},[679,127541,745],{"class":693},[679,127543,6453],{"class":685},[679,127545,17262],{"class":931},[679,127547,127374],{"class":693},[679,127549,127489],{"class":880},[679,127551,745],{"class":693},[679,127553,6453],{"class":685},[679,127555,66775],{"class":931},[679,127557,2797],{"class":693},[679,127559,6453],{"class":685},[679,127561,66599],{"class":931},[679,127563,16686],{"class":693},[679,127565,127566],{"class":689},"\"-5 - -3 should equal -2\"",[679,127568,1208],{"class":693},[679,127570,127571],{"class":681,"line":3169},[679,127572,985],{"class":693},[679,127574,127575],{"class":681,"line":3185},[679,127576,889],{"emptyLinePlaceholder":797},[679,127578,127579,127581],{"class":681,"line":3194},[679,127580,6872],{"class":693},[679,127582,73087],{"class":685},[679,127584,127585,127587,127590],{"class":681,"line":3199},[679,127586,3314],{"class":685},[679,127588,127589],{"class":880}," testMultiply",[679,127591,2667],{"class":693},[679,127593,127594,127596,127598,127601,127603,127606,127608,127610,127612,127614,127616,127619],{"class":681,"line":3212},[679,127595,73133],{"class":880},[679,127597,745],{"class":693},[679,127599,127600],{"class":931},"15",[679,127602,127374],{"class":693},[679,127604,127605],{"class":880},"multiply",[679,127607,745],{"class":693},[679,127609,66775],{"class":931},[679,127611,2797],{"class":693},[679,127613,66599],{"class":931},[679,127615,16686],{"class":693},[679,127617,127618],{"class":689},"\"5 * 3 should equal 15\"",[679,127620,1208],{"class":693},[679,127622,127623,127625,127627,127629,127631,127633,127635,127637,127639,127641,127643,127645,127647,127650],{"class":681,"line":3217},[679,127624,73133],{"class":880},[679,127626,745],{"class":693},[679,127628,6453],{"class":685},[679,127630,35955],{"class":931},[679,127632,127374],{"class":693},[679,127634,127605],{"class":880},[679,127636,745],{"class":693},[679,127638,6453],{"class":685},[679,127640,66775],{"class":931},[679,127642,2797],{"class":693},[679,127644,66775],{"class":931},[679,127646,16686],{"class":693},[679,127648,127649],{"class":689},"\"-5 * 5 should equal -25\"",[679,127651,1208],{"class":693},[679,127653,127654,127656,127658,127660,127662,127664,127666,127668,127670,127672,127674,127676,127678,127681],{"class":681,"line":3222},[679,127655,73133],{"class":880},[679,127657,745],{"class":693},[679,127659,127600],{"class":931},[679,127661,127374],{"class":693},[679,127663,127605],{"class":880},[679,127665,745],{"class":693},[679,127667,6453],{"class":685},[679,127669,66775],{"class":931},[679,127671,2797],{"class":693},[679,127673,6453],{"class":685},[679,127675,66599],{"class":931},[679,127677,16686],{"class":693},[679,127679,127680],{"class":689},"\"-5 * -3 should equal 15\"",[679,127682,1208],{"class":693},[679,127684,127685],{"class":681,"line":3227},[679,127686,985],{"class":693},[679,127688,127689],{"class":681,"line":3232},[679,127690,889],{"emptyLinePlaceholder":797},[679,127692,127693,127695],{"class":681,"line":3499},[679,127694,6872],{"class":693},[679,127696,73087],{"class":685},[679,127698,127699,127701,127704],{"class":681,"line":3509},[679,127700,3314],{"class":685},[679,127702,127703],{"class":880}," testDivide",[679,127705,2667],{"class":693},[679,127707,127708,127710,127712,127714,127716,127719,127721,127723,127725,127727,127729,127732],{"class":681,"line":3516},[679,127709,73133],{"class":880},[679,127711,745],{"class":693},[679,127713,17262],{"class":931},[679,127715,127374],{"class":693},[679,127717,127718],{"class":880},"divide",[679,127720,745],{"class":693},[679,127722,66698],{"class":931},[679,127724,2797],{"class":693},[679,127726,66599],{"class":931},[679,127728,16686],{"class":693},[679,127730,127731],{"class":689},"\"6 / 3 should equal 2\"",[679,127733,1208],{"class":693},[679,127735,127736,127738,127740,127742,127744,127746,127748,127750,127752,127754,127756,127758,127760,127763],{"class":681,"line":3531},[679,127737,73133],{"class":880},[679,127739,745],{"class":693},[679,127741,6453],{"class":685},[679,127743,17262],{"class":931},[679,127745,127374],{"class":693},[679,127747,127718],{"class":880},[679,127749,745],{"class":693},[679,127751,6453],{"class":685},[679,127753,66698],{"class":931},[679,127755,2797],{"class":693},[679,127757,66599],{"class":931},[679,127759,16686],{"class":693},[679,127761,127762],{"class":689},"\"-6 / 3 should equal -2\"",[679,127764,1208],{"class":693},[679,127766,127767,127769,127771,127773,127775,127777,127779,127781,127783,127785,127787,127789,127791,127794],{"class":681,"line":3536},[679,127768,73133],{"class":880},[679,127770,745],{"class":693},[679,127772,17262],{"class":931},[679,127774,127374],{"class":693},[679,127776,127718],{"class":880},[679,127778,745],{"class":693},[679,127780,6453],{"class":685},[679,127782,66698],{"class":931},[679,127784,2797],{"class":693},[679,127786,6453],{"class":685},[679,127788,66599],{"class":931},[679,127790,16686],{"class":693},[679,127792,127793],{"class":689},"\"-6 / -3 should equal 2\"",[679,127795,1208],{"class":693},[679,127797,127798],{"class":681,"line":3541},[679,127799,985],{"class":693},[679,127801,127802],{"class":681,"line":3546},[679,127803,889],{"emptyLinePlaceholder":797},[679,127805,127806,127808],{"class":681,"line":3551},[679,127807,6872],{"class":693},[679,127809,73087],{"class":685},[679,127811,127812,127814,127817],{"class":681,"line":3557},[679,127813,3314],{"class":685},[679,127815,127816],{"class":880}," testDivideByZero",[679,127818,2667],{"class":693},[679,127820,127821,127824,127827,127829,127832,127834,127836,127838,127840,127842],{"class":681,"line":3567},[679,127822,127823],{"class":880}," assertThrows",[679,127825,127826],{"class":693},"(ArithmeticException.class, () ",[679,127828,16955],{"class":685},[679,127830,127831],{"class":693}," calculator.",[679,127833,127718],{"class":880},[679,127835,745],{"class":693},[679,127837,66775],{"class":931},[679,127839,2797],{"class":693},[679,127841,1060],{"class":931},[679,127843,66689],{"class":693},[679,127845,127846,127849],{"class":681,"line":3574},[679,127847,127848],{"class":689}," \"Dividing by zero should throw ArithmeticException\"",[679,127850,1208],{"class":693},[679,127852,127853],{"class":681,"line":3589},[679,127854,985],{"class":693},[679,127856,127857],{"class":681,"line":3594},[679,127858,996],{"class":693},[5259,127860,127862],{"id":127861},"_2-document-creation-blog-post-outline","2. Document Creation: Blog Post Outline",[651,127864,127865],{},"Request Claude to draft an outline for a blog post about \"Generating Native Images in Java with GraalVM\":",[1004,127867,127868],{},"\nI need to draft a new outline for a blog post. This blog post will be about generating native images in Java using GraalVM. This outline should contain at least an intro, main section covering why you would want to use native images in Java and the technical how to. The tone of this article will be welcoming and geared towards beginners.\n",[669,127870,127872],{"className":59028,"code":127871,"language":43882,"meta":674,"style":674},"# Generating Native Images in Java with GraalVM: A Beginner's Guide\n\n## 1. Introduction\n- Brief explanation of Java and its traditional compilation process\n- Introduction to GraalVM and native images\n- Teaser of the benefits we'll explore\n\n## 2. Why Use Native Images in Java?\n- Faster startup time\n- Reduced memory usage\n- Smaller application size\n- Improved security (smaller attack surface)\n- Use cases where native images shine\n\n## 3. Understanding GraalVM\n- What is GraalVM?\n- How GraalVM differs from traditional JVMs\n- The concept of ahead-of-time (AOT) compilation\n\n## 4. Getting Started with Native Images\n- Setting up GraalVM\n- Installing the native-image tool\n- Preparing your Java application for native image generation\n\n## 5. Creating Your First Native Image\n- Step-by-step guide to generating a native image\n- Common command-line options\n- Handling dependencies and resources\n\n## 6. Overcoming Common Challenges\n- Dealing with reflection\n- Managing dynamic class loading\n- Handling resources and configuration files\n\n## 7. Best Practices and Tips\n- Optimizing your code for native images\n- Testing and debugging native images\n- Performance tuning techniques\n\n## 8. Real-world Examples\n- Simple console application example\n- Web application example (e.g., using a framework like Quarkus or Micronaut)\n\n## 9. Conclusion\n- Recap of the benefits of native images\n- Encouragement for readers to try it out\n- Future of native images in Java development\n\n## 10. Additional Resources\n- Official GraalVM documentation\n- Helpful community resources and forums\n- Related technologies and frameworks\n",[676,127873,127874,127879,127883,127888,127893,127898,127903,127907,127912,127917,127922,127927,127932,127937,127941,127946,127951,127956,127961,127965,127970,127975,127980,127985,127989,127994,127999,128004,128009,128013,128018,128023,128028,128033,128037,128042,128047,128052,128057,128061,128066,128071,128076,128080,128085,128090,128095,128100,128104,128109,128114,128119],{"__ignoreMap":674},[679,127875,127876],{"class":681,"line":682},[679,127877,127878],{},"# Generating Native Images in Java with GraalVM: A Beginner's Guide\n",[679,127880,127881],{"class":681,"line":790},[679,127882,889],{"emptyLinePlaceholder":797},[679,127884,127885],{"class":681,"line":892},[679,127886,127887],{},"## 1. Introduction\n",[679,127889,127890],{"class":681,"line":901},[679,127891,127892],{},"- Brief explanation of Java and its traditional compilation process\n",[679,127894,127895],{"class":681,"line":909},[679,127896,127897],{},"- Introduction to GraalVM and native images\n",[679,127899,127900],{"class":681,"line":918},[679,127901,127902],{},"- Teaser of the benefits we'll explore\n",[679,127904,127905],{"class":681,"line":935},[679,127906,889],{"emptyLinePlaceholder":797},[679,127908,127909],{"class":681,"line":944},[679,127910,127911],{},"## 2. Why Use Native Images in Java?\n",[679,127913,127914],{"class":681,"line":959},[679,127915,127916],{},"- Faster startup time\n",[679,127918,127919],{"class":681,"line":964},[679,127920,127921],{},"- Reduced memory usage\n",[679,127923,127924],{"class":681,"line":977},[679,127925,127926],{},"- Smaller application size\n",[679,127928,127929],{"class":681,"line":982},[679,127930,127931],{},"- Improved security (smaller attack surface)\n",[679,127933,127934],{"class":681,"line":988},[679,127935,127936],{},"- Use cases where native images shine\n",[679,127938,127939],{"class":681,"line":993},[679,127940,889],{"emptyLinePlaceholder":797},[679,127942,127943],{"class":681,"line":2129},[679,127944,127945],{},"## 3. Understanding GraalVM\n",[679,127947,127948],{"class":681,"line":2140},[679,127949,127950],{},"- What is GraalVM?\n",[679,127952,127953],{"class":681,"line":2145},[679,127954,127955],{},"- How GraalVM differs from traditional JVMs\n",[679,127957,127958],{"class":681,"line":2154},[679,127959,127960],{},"- The concept of ahead-of-time (AOT) compilation\n",[679,127962,127963],{"class":681,"line":2159},[679,127964,889],{"emptyLinePlaceholder":797},[679,127966,127967],{"class":681,"line":2164},[679,127968,127969],{},"## 4. Getting Started with Native Images\n",[679,127971,127972],{"class":681,"line":3134},[679,127973,127974],{},"- Setting up GraalVM\n",[679,127976,127977],{"class":681,"line":3139},[679,127978,127979],{},"- Installing the native-image tool\n",[679,127981,127982],{"class":681,"line":3144},[679,127983,127984],{},"- Preparing your Java application for native image generation\n",[679,127986,127987],{"class":681,"line":3149},[679,127988,889],{"emptyLinePlaceholder":797},[679,127990,127991],{"class":681,"line":3169},[679,127992,127993],{},"## 5. Creating Your First Native Image\n",[679,127995,127996],{"class":681,"line":3185},[679,127997,127998],{},"- Step-by-step guide to generating a native image\n",[679,128000,128001],{"class":681,"line":3194},[679,128002,128003],{},"- Common command-line options\n",[679,128005,128006],{"class":681,"line":3199},[679,128007,128008],{},"- Handling dependencies and resources\n",[679,128010,128011],{"class":681,"line":3212},[679,128012,889],{"emptyLinePlaceholder":797},[679,128014,128015],{"class":681,"line":3217},[679,128016,128017],{},"## 6. Overcoming Common Challenges\n",[679,128019,128020],{"class":681,"line":3222},[679,128021,128022],{},"- Dealing with reflection\n",[679,128024,128025],{"class":681,"line":3227},[679,128026,128027],{},"- Managing dynamic class loading\n",[679,128029,128030],{"class":681,"line":3232},[679,128031,128032],{},"- Handling resources and configuration files\n",[679,128034,128035],{"class":681,"line":3499},[679,128036,889],{"emptyLinePlaceholder":797},[679,128038,128039],{"class":681,"line":3509},[679,128040,128041],{},"## 7. Best Practices and Tips\n",[679,128043,128044],{"class":681,"line":3516},[679,128045,128046],{},"- Optimizing your code for native images\n",[679,128048,128049],{"class":681,"line":3531},[679,128050,128051],{},"- Testing and debugging native images\n",[679,128053,128054],{"class":681,"line":3536},[679,128055,128056],{},"- Performance tuning techniques\n",[679,128058,128059],{"class":681,"line":3541},[679,128060,889],{"emptyLinePlaceholder":797},[679,128062,128063],{"class":681,"line":3546},[679,128064,128065],{},"## 8. Real-world Examples\n",[679,128067,128068],{"class":681,"line":3551},[679,128069,128070],{},"- Simple console application example\n",[679,128072,128073],{"class":681,"line":3557},[679,128074,128075],{},"- Web application example (e.g., using a framework like Quarkus or Micronaut)\n",[679,128077,128078],{"class":681,"line":3567},[679,128079,889],{"emptyLinePlaceholder":797},[679,128081,128082],{"class":681,"line":3574},[679,128083,128084],{},"## 9. Conclusion\n",[679,128086,128087],{"class":681,"line":3589},[679,128088,128089],{},"- Recap of the benefits of native images\n",[679,128091,128092],{"class":681,"line":3594},[679,128093,128094],{},"- Encouragement for readers to try it out\n",[679,128096,128097],{"class":681,"line":3602},[679,128098,128099],{},"- Future of native images in Java development\n",[679,128101,128102],{"class":681,"line":3608},[679,128103,889],{"emptyLinePlaceholder":797},[679,128105,128106],{"class":681,"line":3619},[679,128107,128108],{},"## 10. Additional Resources\n",[679,128110,128111],{"class":681,"line":3624},[679,128112,128113],{},"- Official GraalVM documentation\n",[679,128115,128116],{"class":681,"line":3629},[679,128117,128118],{},"- Helpful community resources and forums\n",[679,128120,128121],{"class":681,"line":3639},[679,128122,128123],{},"- Related technologies and frameworks\n",[651,128125,128126],{},"This outline will appear in the Artifacts window, allowing you to refine and expand it collaboratively with Claude.",[651,128128,128129],{},[660,128130],{"alt":128131,"src":128132},"Draft Blog Post","/images/blog/2024/08/06/draft_blog_post.png",[5259,128134,128136],{"id":128135},"_3-website-design-personal-portfolio-page","3. Website Design: Personal Portfolio Page",[651,128138,128139],{},"Ask Claude to create a basic HTML structure for a personal portfolio page:",[1004,128141,128142],{},"\nI want to create a new personal portfolio website for myself. I really like the home page of one of my favorite content creators, Dan Vega. I have attached a screenshot of his home page and would like to recreate a basic structure of the page using html & css.\n",[651,128144,128145],{},"I took a screenshot of my home page and attached it. I was really happy with the response from the model",[1004,128147,128148],{},"\nI understand you'd like to create a personal portfolio website inspired by Dan Vega's home page layout. While I can't reproduce his design exactly, I can help you create a similar structure using HTML and CSS that captures the key elements.\n",[669,128150,128152],{"className":4496,"code":128151,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n \u003Cmeta charset=\"UTF-8\">\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003Ctitle>Your Name - Portfolio\u003C/title>\n \u003Cstyle>\n body {\n font-family: Arial, sans-serif;\n line-height: 1.6;\n margin: 0;\n padding: 0;\n color: #333;\n }\n .container {\n width: 80%;\n margin: auto;\n overflow: hidden;\n padding: 20px;\n }\n header {\n background: #f4f4f4;\n padding: 1rem;\n text-align: center;\n }\n .profile-image {\n width: 150px;\n border-radius: 50%;\n }\n .social-links {\n margin: 1rem 0;\n }\n .social-links a {\n margin: 0 10px;\n color: #333;\n text-decoration: none;\n }\n .image-gallery {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n margin: 2rem 0;\n }\n .image-gallery img {\n width: 18%;\n margin-bottom: 10px;\n }\n .blog-posts, .work-history {\n margin: 2rem 0;\n }\n .blog-post, .job {\n margin-bottom: 1rem;\n }\n footer {\n background: #f4f4f4;\n text-align: center;\n padding: 1rem;\n margin-top: 2rem;\n }\n \u003C/style>\n\u003C/head>\n\u003Cbody>\n\u003Cheader>\n \u003Cimg src=\"/api/placeholder/150/150\" alt=\"Your Profile Picture\" class=\"profile-image\">\n \u003Ch1>Your Name\u003C/h1>\n \u003Cp>Your Title, Specialties, and Passions\u003C/p>\n \u003Cdiv class=\"social-links\">\n \u003Ca href=\"#\">Twitter\u003C/a>\n \u003Ca href=\"#\">YouTube\u003C/a>\n \u003Ca href=\"#\">GitHub\u003C/a>\n \u003Ca href=\"#\">LinkedIn\u003C/a>\n \u003C/div>\n\u003C/header>\n\n\u003Cdiv class=\"container\">\n \u003Csection class=\"about\">\n \u003Ch2>About Me\u003C/h2>\n \u003Cp>A brief introduction about yourself, your expertise, and what you're passionate about.\u003C/p>\n \u003C/section>\n\n \u003Csection class=\"image-gallery\">\n \u003Cimg src=\"/api/placeholder/200/200\" alt=\"Gallery Image 1\">\n \u003Cimg src=\"/api/placeholder/200/200\" alt=\"Gallery Image 2\">\n \u003Cimg src=\"/api/placeholder/200/200\" alt=\"Gallery Image 3\">\n \u003Cimg src=\"/api/placeholder/200/200\" alt=\"Gallery Image 4\">\n \u003Cimg src=\"/api/placeholder/200/200\" alt=\"Gallery Image 5\">\n \u003C/section>\n\n \u003Csection class=\"blog-posts\">\n \u003Ch2>Recent Blog Posts\u003C/h2>\n \u003Cdiv class=\"blog-post\">\n \u003Ch3>Blog Post Title\u003C/h3>\n \u003Cp>Brief description of the blog post...\u003C/p>\n \u003Ca href=\"#\">Read more\u003C/a>\n \u003C/div>\n \u003C!-- Add more blog posts as needed -->\n \u003C/section>\n\n \u003Csection class=\"work-history\">\n \u003Ch2>Work History\u003C/h2>\n \u003Cdiv class=\"job\">\n \u003Ch3>Company Name\u003C/h3>\n \u003Cp>Job Title | Date Range\u003C/p>\n \u003Cp>Brief description of your role and responsibilities...\u003C/p>\n \u003C/div>\n \u003C!-- Add more jobs as needed -->\n \u003C/section>\n\u003C/div>\n\n\u003Cfooter>\n \u003Cp>© 2024 Your Name. All rights reserved.\u003C/p>\n\u003C/footer>\n\u003C/body>\n\u003C/html>\n",[676,128153,128154,128164,128178,128186,128200,128220,128233,128241,128248,128265,128277,128288,128299,128311,128315,128322,128336,128347,128358,128370,128374,128381,128393,128406,128417,128421,128428,128441,128455,128459,128466,128480,128484,128492,128506,128516,128527,128531,128538,128549,128561,128573,128587,128591,128600,128613,128626,128630,128642,128656,128660,128672,128684,128688,128695,128705,128715,128727,128740,128744,128752,128760,128768,128776,128805,128818,128831,128846,128865,128884,128903,128922,128930,128938,128942,128956,128970,128983,128996,129004,129008,129023,129045,129066,129087,129108,129129,129137,129141,129156,129169,129184,129197,129210,129229,129237,129242,129250,129254,129269,129282,129297,129310,129323,129336,129344,129349,129357,129365,129369,129378,129396,129404,129412],{"__ignoreMap":674},[679,128155,128156,128158,128160,128162],{"class":681,"line":682},[679,128157,11904],{"class":693},[679,128159,11907],{"class":4508},[679,128161,11910],{"class":880},[679,128163,4519],{"class":693},[679,128165,128166,128168,128170,128172,128174,128176],{"class":681,"line":790},[679,128167,4505],{"class":693},[679,128169,4498],{"class":4508},[679,128171,18806],{"class":880},[679,128173,686],{"class":693},[679,128175,57251],{"class":689},[679,128177,4519],{"class":693},[679,128179,128180,128182,128184],{"class":681,"line":892},[679,128181,4505],{"class":693},[679,128183,11741],{"class":4508},[679,128185,4519],{"class":693},[679,128187,128188,128190,128192,128194,128196,128198],{"class":681,"line":901},[679,128189,4524],{"class":693},[679,128191,11968],{"class":4508},[679,128193,11971],{"class":880},[679,128195,686],{"class":693},[679,128197,47958],{"class":689},[679,128199,4519],{"class":693},[679,128201,128202,128204,128206,128208,128210,128212,128214,128216,128218],{"class":681,"line":909},[679,128203,4524],{"class":693},[679,128205,11968],{"class":4508},[679,128207,5283],{"class":880},[679,128209,686],{"class":693},[679,128211,12015],{"class":689},[679,128213,11995],{"class":880},[679,128215,686],{"class":693},[679,128217,48042],{"class":689},[679,128219,4519],{"class":693},[679,128221,128222,128224,128226,128229,128231],{"class":681,"line":918},[679,128223,4524],{"class":693},[679,128225,11750],{"class":4508},[679,128227,128228],{"class":693},">Your Name - Portfolio\u003C/",[679,128230,11750],{"class":4508},[679,128232,4519],{"class":693},[679,128234,128235,128237,128239],{"class":681,"line":935},[679,128236,4524],{"class":693},[679,128238,786],{"class":4508},[679,128240,4519],{"class":693},[679,128242,128243,128246],{"class":681,"line":944},[679,128244,128245],{"class":4508}," body",[679,128247,884],{"class":693},[679,128249,128250,128253,128255,128258,128260,128263],{"class":681,"line":959},[679,128251,128252],{"class":931}," font-family",[679,128254,4282],{"class":693},[679,128256,128257],{"class":931},"Arial",[679,128259,2797],{"class":693},[679,128261,128262],{"class":931},"sans-serif",[679,128264,1186],{"class":693},[679,128266,128267,128270,128272,128275],{"class":681,"line":964},[679,128268,128269],{"class":931}," line-height",[679,128271,4282],{"class":693},[679,128273,128274],{"class":931},"1.6",[679,128276,1186],{"class":693},[679,128278,128279,128282,128284,128286],{"class":681,"line":977},[679,128280,128281],{"class":931}," margin",[679,128283,4282],{"class":693},[679,128285,1060],{"class":931},[679,128287,1186],{"class":693},[679,128289,128290,128293,128295,128297],{"class":681,"line":982},[679,128291,128292],{"class":931}," padding",[679,128294,4282],{"class":693},[679,128296,1060],{"class":931},[679,128298,1186],{"class":693},[679,128300,128301,128304,128306,128309],{"class":681,"line":988},[679,128302,128303],{"class":931}," color",[679,128305,4282],{"class":693},[679,128307,128308],{"class":931},"#333",[679,128310,1186],{"class":693},[679,128312,128313],{"class":681,"line":993},[679,128314,1290],{"class":693},[679,128316,128317,128320],{"class":681,"line":2129},[679,128318,128319],{"class":880}," .container",[679,128321,884],{"class":693},[679,128323,128324,128327,128329,128332,128334],{"class":681,"line":2140},[679,128325,128326],{"class":931}," width",[679,128328,4282],{"class":693},[679,128330,128331],{"class":931},"80",[679,128333,66892],{"class":685},[679,128335,1186],{"class":693},[679,128337,128338,128340,128342,128345],{"class":681,"line":2145},[679,128339,128281],{"class":931},[679,128341,4282],{"class":693},[679,128343,128344],{"class":931},"auto",[679,128346,1186],{"class":693},[679,128348,128349,128352,128354,128356],{"class":681,"line":2154},[679,128350,128351],{"class":931}," overflow",[679,128353,4282],{"class":693},[679,128355,66671],{"class":931},[679,128357,1186],{"class":693},[679,128359,128360,128362,128364,128366,128368],{"class":681,"line":2159},[679,128361,128292],{"class":931},[679,128363,4282],{"class":693},[679,128365,11794],{"class":931},[679,128367,11797],{"class":685},[679,128369,1186],{"class":693},[679,128371,128372],{"class":681,"line":2164},[679,128373,1290],{"class":693},[679,128375,128376,128379],{"class":681,"line":3134},[679,128377,128378],{"class":4508}," header",[679,128380,884],{"class":693},[679,128382,128383,128386,128388,128391],{"class":681,"line":3139},[679,128384,128385],{"class":931}," background",[679,128387,4282],{"class":693},[679,128389,128390],{"class":931},"#f4f4f4",[679,128392,1186],{"class":693},[679,128394,128395,128397,128399,128401,128404],{"class":681,"line":3144},[679,128396,128292],{"class":931},[679,128398,4282],{"class":693},[679,128400,1557],{"class":931},[679,128402,128403],{"class":685},"rem",[679,128405,1186],{"class":693},[679,128407,128408,128411,128413,128415],{"class":681,"line":3149},[679,128409,128410],{"class":931}," text-align",[679,128412,4282],{"class":693},[679,128414,22665],{"class":931},[679,128416,1186],{"class":693},[679,128418,128419],{"class":681,"line":3169},[679,128420,1290],{"class":693},[679,128422,128423,128426],{"class":681,"line":3185},[679,128424,128425],{"class":880}," .profile-image",[679,128427,884],{"class":693},[679,128429,128430,128432,128434,128437,128439],{"class":681,"line":3194},[679,128431,128326],{"class":931},[679,128433,4282],{"class":693},[679,128435,128436],{"class":931},"150",[679,128438,11797],{"class":685},[679,128440,1186],{"class":693},[679,128442,128443,128446,128448,128451,128453],{"class":681,"line":3199},[679,128444,128445],{"class":931}," border-radius",[679,128447,4282],{"class":693},[679,128449,128450],{"class":931},"50",[679,128452,66892],{"class":685},[679,128454,1186],{"class":693},[679,128456,128457],{"class":681,"line":3212},[679,128458,1290],{"class":693},[679,128460,128461,128464],{"class":681,"line":3217},[679,128462,128463],{"class":880}," .social-links",[679,128465,884],{"class":693},[679,128467,128468,128470,128472,128474,128476,128478],{"class":681,"line":3222},[679,128469,128281],{"class":931},[679,128471,4282],{"class":693},[679,128473,1557],{"class":931},[679,128475,128403],{"class":685},[679,128477,14987],{"class":931},[679,128479,1186],{"class":693},[679,128481,128482],{"class":681,"line":3227},[679,128483,1290],{"class":693},[679,128485,128486,128488,128490],{"class":681,"line":3232},[679,128487,128463],{"class":880},[679,128489,21697],{"class":4508},[679,128491,884],{"class":693},[679,128493,128494,128496,128498,128500,128502,128504],{"class":681,"line":3499},[679,128495,128281],{"class":931},[679,128497,4282],{"class":693},[679,128499,1060],{"class":931},[679,128501,48612],{"class":931},[679,128503,11797],{"class":685},[679,128505,1186],{"class":693},[679,128507,128508,128510,128512,128514],{"class":681,"line":3509},[679,128509,128303],{"class":931},[679,128511,4282],{"class":693},[679,128513,128308],{"class":931},[679,128515,1186],{"class":693},[679,128517,128518,128521,128523,128525],{"class":681,"line":3516},[679,128519,128520],{"class":931}," text-decoration",[679,128522,4282],{"class":693},[679,128524,49752],{"class":931},[679,128526,1186],{"class":693},[679,128528,128529],{"class":681,"line":3531},[679,128530,1290],{"class":693},[679,128532,128533,128536],{"class":681,"line":3536},[679,128534,128535],{"class":880}," .image-gallery",[679,128537,884],{"class":693},[679,128539,128540,128543,128545,128547],{"class":681,"line":3541},[679,128541,128542],{"class":931}," display",[679,128544,4282],{"class":693},[679,128546,66855],{"class":931},[679,128548,1186],{"class":693},[679,128550,128551,128554,128556,128559],{"class":681,"line":3546},[679,128552,128553],{"class":931}," flex-wrap",[679,128555,4282],{"class":693},[679,128557,128558],{"class":931},"wrap",[679,128560,1186],{"class":693},[679,128562,128563,128566,128568,128571],{"class":681,"line":3551},[679,128564,128565],{"class":931}," justify-content",[679,128567,4282],{"class":693},[679,128569,128570],{"class":931},"space-between",[679,128572,1186],{"class":693},[679,128574,128575,128577,128579,128581,128583,128585],{"class":681,"line":3557},[679,128576,128281],{"class":931},[679,128578,4282],{"class":693},[679,128580,17262],{"class":931},[679,128582,128403],{"class":685},[679,128584,14987],{"class":931},[679,128586,1186],{"class":693},[679,128588,128589],{"class":681,"line":3567},[679,128590,1290],{"class":693},[679,128592,128593,128595,128598],{"class":681,"line":3574},[679,128594,128535],{"class":880},[679,128596,128597],{"class":4508}," img",[679,128599,884],{"class":693},[679,128601,128602,128604,128606,128609,128611],{"class":681,"line":3589},[679,128603,128326],{"class":931},[679,128605,4282],{"class":693},[679,128607,128608],{"class":931},"18",[679,128610,66892],{"class":685},[679,128612,1186],{"class":693},[679,128614,128615,128618,128620,128622,128624],{"class":681,"line":3594},[679,128616,128617],{"class":931}," margin-bottom",[679,128619,4282],{"class":693},[679,128621,1205],{"class":931},[679,128623,11797],{"class":685},[679,128625,1186],{"class":693},[679,128627,128628],{"class":681,"line":3602},[679,128629,1290],{"class":693},[679,128631,128632,128635,128637,128640],{"class":681,"line":3608},[679,128633,128634],{"class":880}," .blog-posts",[679,128636,2797],{"class":693},[679,128638,128639],{"class":880},".work-history",[679,128641,884],{"class":693},[679,128643,128644,128646,128648,128650,128652,128654],{"class":681,"line":3619},[679,128645,128281],{"class":931},[679,128647,4282],{"class":693},[679,128649,17262],{"class":931},[679,128651,128403],{"class":685},[679,128653,14987],{"class":931},[679,128655,1186],{"class":693},[679,128657,128658],{"class":681,"line":3624},[679,128659,1290],{"class":693},[679,128661,128662,128665,128667,128670],{"class":681,"line":3629},[679,128663,128664],{"class":880}," .blog-post",[679,128666,2797],{"class":693},[679,128668,128669],{"class":880},".job",[679,128671,884],{"class":693},[679,128673,128674,128676,128678,128680,128682],{"class":681,"line":3639},[679,128675,128617],{"class":931},[679,128677,4282],{"class":693},[679,128679,1557],{"class":931},[679,128681,128403],{"class":685},[679,128683,1186],{"class":693},[679,128685,128686],{"class":681,"line":3644},[679,128687,1290],{"class":693},[679,128689,128690,128693],{"class":681,"line":3649},[679,128691,128692],{"class":4508}," footer",[679,128694,884],{"class":693},[679,128696,128697,128699,128701,128703],{"class":681,"line":3659},[679,128698,128385],{"class":931},[679,128700,4282],{"class":693},[679,128702,128390],{"class":931},[679,128704,1186],{"class":693},[679,128706,128707,128709,128711,128713],{"class":681,"line":3664},[679,128708,128410],{"class":931},[679,128710,4282],{"class":693},[679,128712,22665],{"class":931},[679,128714,1186],{"class":693},[679,128716,128717,128719,128721,128723,128725],{"class":681,"line":3669},[679,128718,128292],{"class":931},[679,128720,4282],{"class":693},[679,128722,1557],{"class":931},[679,128724,128403],{"class":685},[679,128726,1186],{"class":693},[679,128728,128729,128732,128734,128736,128738],{"class":681,"line":3679},[679,128730,128731],{"class":931}," margin-top",[679,128733,4282],{"class":693},[679,128735,17262],{"class":931},[679,128737,128403],{"class":685},[679,128739,1186],{"class":693},[679,128741,128742],{"class":681,"line":3684},[679,128743,1290],{"class":693},[679,128745,128746,128748,128750],{"class":681,"line":3689},[679,128747,4577],{"class":693},[679,128749,786],{"class":4508},[679,128751,4519],{"class":693},[679,128753,128754,128756,128758],{"class":681,"line":3699},[679,128755,4586],{"class":693},[679,128757,11741],{"class":4508},[679,128759,4519],{"class":693},[679,128761,128762,128764,128766],{"class":681,"line":3704},[679,128763,4505],{"class":693},[679,128765,3006],{"class":4508},[679,128767,4519],{"class":693},[679,128769,128770,128772,128774],{"class":681,"line":3709},[679,128771,4505],{"class":693},[679,128773,91684],{"class":4508},[679,128775,4519],{"class":693},[679,128777,128778,128780,128782,128784,128786,128789,128791,128793,128796,128798,128800,128803],{"class":681,"line":3719},[679,128779,4524],{"class":693},[679,128781,660],{"class":4508},[679,128783,5361],{"class":880},[679,128785,686],{"class":693},[679,128787,128788],{"class":689},"\"/api/placeholder/150/150\"",[679,128790,5380],{"class":880},[679,128792,686],{"class":693},[679,128794,128795],{"class":689},"\"Your Profile Picture\"",[679,128797,4512],{"class":880},[679,128799,686],{"class":693},[679,128801,128802],{"class":689},"\"profile-image\"",[679,128804,4519],{"class":693},[679,128806,128807,128809,128811,128814,128816],{"class":681,"line":3724},[679,128808,4524],{"class":693},[679,128810,11859],{"class":4508},[679,128812,128813],{"class":693},">Your Name\u003C/",[679,128815,11859],{"class":4508},[679,128817,4519],{"class":693},[679,128819,128820,128822,128824,128827,128829],{"class":681,"line":3729},[679,128821,4524],{"class":693},[679,128823,651],{"class":4508},[679,128825,128826],{"class":693},">Your Title, Specialties, and Passions\u003C/",[679,128828,651],{"class":4508},[679,128830,4519],{"class":693},[679,128832,128833,128835,128837,128839,128841,128844],{"class":681,"line":3739},[679,128834,4524],{"class":693},[679,128836,4509],{"class":4508},[679,128838,4512],{"class":880},[679,128840,686],{"class":693},[679,128842,128843],{"class":689},"\"social-links\"",[679,128845,4519],{"class":693},[679,128847,128848,128850,128852,128854,128856,128858,128861,128863],{"class":681,"line":3744},[679,128849,4904],{"class":693},[679,128851,812],{"class":4508},[679,128853,4550],{"class":880},[679,128855,686],{"class":693},[679,128857,5420],{"class":689},[679,128859,128860],{"class":693},">Twitter\u003C/",[679,128862,812],{"class":4508},[679,128864,4519],{"class":693},[679,128866,128867,128869,128871,128873,128875,128877,128880,128882],{"class":681,"line":3749},[679,128868,4904],{"class":693},[679,128870,812],{"class":4508},[679,128872,4550],{"class":880},[679,128874,686],{"class":693},[679,128876,5420],{"class":689},[679,128878,128879],{"class":693},">YouTube\u003C/",[679,128881,812],{"class":4508},[679,128883,4519],{"class":693},[679,128885,128886,128888,128890,128892,128894,128896,128899,128901],{"class":681,"line":3759},[679,128887,4904],{"class":693},[679,128889,812],{"class":4508},[679,128891,4550],{"class":880},[679,128893,686],{"class":693},[679,128895,5420],{"class":689},[679,128897,128898],{"class":693},">GitHub\u003C/",[679,128900,812],{"class":4508},[679,128902,4519],{"class":693},[679,128904,128905,128907,128909,128911,128913,128915,128918,128920],{"class":681,"line":3764},[679,128906,4904],{"class":693},[679,128908,812],{"class":4508},[679,128910,4550],{"class":880},[679,128912,686],{"class":693},[679,128914,5420],{"class":689},[679,128916,128917],{"class":693},">LinkedIn\u003C/",[679,128919,812],{"class":4508},[679,128921,4519],{"class":693},[679,128923,128924,128926,128928],{"class":681,"line":3769},[679,128925,4577],{"class":693},[679,128927,4509],{"class":4508},[679,128929,4519],{"class":693},[679,128931,128932,128934,128936],{"class":681,"line":3779},[679,128933,4586],{"class":693},[679,128935,91684],{"class":4508},[679,128937,4519],{"class":693},[679,128939,128940],{"class":681,"line":3784},[679,128941,889],{"emptyLinePlaceholder":797},[679,128943,128944,128946,128948,128950,128952,128954],{"class":681,"line":3789},[679,128945,4505],{"class":693},[679,128947,4509],{"class":4508},[679,128949,4512],{"class":880},[679,128951,686],{"class":693},[679,128953,66090],{"class":689},[679,128955,4519],{"class":693},[679,128957,128958,128960,128962,128964,128966,128968],{"class":681,"line":3800},[679,128959,4524],{"class":693},[679,128961,66068],{"class":4508},[679,128963,4512],{"class":880},[679,128965,686],{"class":693},[679,128967,52424],{"class":689},[679,128969,4519],{"class":693},[679,128971,128972,128974,128976,128979,128981],{"class":681,"line":3805},[679,128973,4904],{"class":693},[679,128975,4542],{"class":4508},[679,128977,128978],{"class":693},">About Me\u003C/",[679,128980,4542],{"class":4508},[679,128982,4519],{"class":693},[679,128984,128985,128987,128989,128992,128994],{"class":681,"line":3810},[679,128986,4904],{"class":693},[679,128988,651],{"class":4508},[679,128990,128991],{"class":693},">A brief introduction about yourself, your expertise, and what you're passionate about.\u003C/",[679,128993,651],{"class":4508},[679,128995,4519],{"class":693},[679,128997,128998,129000,129002],{"class":681,"line":3820},[679,128999,4577],{"class":693},[679,129001,66068],{"class":4508},[679,129003,4519],{"class":693},[679,129005,129006],{"class":681,"line":3825},[679,129007,889],{"emptyLinePlaceholder":797},[679,129009,129010,129012,129014,129016,129018,129021],{"class":681,"line":3830},[679,129011,4524],{"class":693},[679,129013,66068],{"class":4508},[679,129015,4512],{"class":880},[679,129017,686],{"class":693},[679,129019,129020],{"class":689},"\"image-gallery\"",[679,129022,4519],{"class":693},[679,129024,129025,129027,129029,129031,129033,129036,129038,129040,129043],{"class":681,"line":3840},[679,129026,4904],{"class":693},[679,129028,660],{"class":4508},[679,129030,5361],{"class":880},[679,129032,686],{"class":693},[679,129034,129035],{"class":689},"\"/api/placeholder/200/200\"",[679,129037,5380],{"class":880},[679,129039,686],{"class":693},[679,129041,129042],{"class":689},"\"Gallery Image 1\"",[679,129044,4519],{"class":693},[679,129046,129047,129049,129051,129053,129055,129057,129059,129061,129064],{"class":681,"line":3845},[679,129048,4904],{"class":693},[679,129050,660],{"class":4508},[679,129052,5361],{"class":880},[679,129054,686],{"class":693},[679,129056,129035],{"class":689},[679,129058,5380],{"class":880},[679,129060,686],{"class":693},[679,129062,129063],{"class":689},"\"Gallery Image 2\"",[679,129065,4519],{"class":693},[679,129067,129068,129070,129072,129074,129076,129078,129080,129082,129085],{"class":681,"line":3850},[679,129069,4904],{"class":693},[679,129071,660],{"class":4508},[679,129073,5361],{"class":880},[679,129075,686],{"class":693},[679,129077,129035],{"class":689},[679,129079,5380],{"class":880},[679,129081,686],{"class":693},[679,129083,129084],{"class":689},"\"Gallery Image 3\"",[679,129086,4519],{"class":693},[679,129088,129089,129091,129093,129095,129097,129099,129101,129103,129106],{"class":681,"line":3855},[679,129090,4904],{"class":693},[679,129092,660],{"class":4508},[679,129094,5361],{"class":880},[679,129096,686],{"class":693},[679,129098,129035],{"class":689},[679,129100,5380],{"class":880},[679,129102,686],{"class":693},[679,129104,129105],{"class":689},"\"Gallery Image 4\"",[679,129107,4519],{"class":693},[679,129109,129110,129112,129114,129116,129118,129120,129122,129124,129127],{"class":681,"line":3860},[679,129111,4904],{"class":693},[679,129113,660],{"class":4508},[679,129115,5361],{"class":880},[679,129117,686],{"class":693},[679,129119,129035],{"class":689},[679,129121,5380],{"class":880},[679,129123,686],{"class":693},[679,129125,129126],{"class":689},"\"Gallery Image 5\"",[679,129128,4519],{"class":693},[679,129130,129131,129133,129135],{"class":681,"line":3870},[679,129132,4577],{"class":693},[679,129134,66068],{"class":4508},[679,129136,4519],{"class":693},[679,129138,129139],{"class":681,"line":3877},[679,129140,889],{"emptyLinePlaceholder":797},[679,129142,129143,129145,129147,129149,129151,129154],{"class":681,"line":3890},[679,129144,4524],{"class":693},[679,129146,66068],{"class":4508},[679,129148,4512],{"class":880},[679,129150,686],{"class":693},[679,129152,129153],{"class":689},"\"blog-posts\"",[679,129155,4519],{"class":693},[679,129157,129158,129160,129162,129165,129167],{"class":681,"line":3896},[679,129159,4904],{"class":693},[679,129161,4542],{"class":4508},[679,129163,129164],{"class":693},">Recent Blog Posts\u003C/",[679,129166,4542],{"class":4508},[679,129168,4519],{"class":693},[679,129170,129171,129173,129175,129177,129179,129182],{"class":681,"line":3902},[679,129172,4904],{"class":693},[679,129174,4509],{"class":4508},[679,129176,4512],{"class":880},[679,129178,686],{"class":693},[679,129180,129181],{"class":689},"\"blog-post\"",[679,129183,4519],{"class":693},[679,129185,129186,129188,129190,129193,129195],{"class":681,"line":3908},[679,129187,5392],{"class":693},[679,129189,5909],{"class":4508},[679,129191,129192],{"class":693},">Blog Post Title\u003C/",[679,129194,5909],{"class":4508},[679,129196,4519],{"class":693},[679,129198,129199,129201,129203,129206,129208],{"class":681,"line":3920},[679,129200,5392],{"class":693},[679,129202,651],{"class":4508},[679,129204,129205],{"class":693},">Brief description of the blog post...\u003C/",[679,129207,651],{"class":4508},[679,129209,4519],{"class":693},[679,129211,129212,129214,129216,129218,129220,129222,129225,129227],{"class":681,"line":3925},[679,129213,5392],{"class":693},[679,129215,812],{"class":4508},[679,129217,4550],{"class":880},[679,129219,686],{"class":693},[679,129221,5420],{"class":689},[679,129223,129224],{"class":693},">Read more\u003C/",[679,129226,812],{"class":4508},[679,129228,4519],{"class":693},[679,129230,129231,129233,129235],{"class":681,"line":3932},[679,129232,5480],{"class":693},[679,129234,4509],{"class":4508},[679,129236,4519],{"class":693},[679,129238,129239],{"class":681,"line":3941},[679,129240,129241],{"class":1400}," \u003C!-- Add more blog posts as needed -->\n",[679,129243,129244,129246,129248],{"class":681,"line":3946},[679,129245,4577],{"class":693},[679,129247,66068],{"class":4508},[679,129249,4519],{"class":693},[679,129251,129252],{"class":681,"line":3951},[679,129253,889],{"emptyLinePlaceholder":797},[679,129255,129256,129258,129260,129262,129264,129267],{"class":681,"line":3956},[679,129257,4524],{"class":693},[679,129259,66068],{"class":4508},[679,129261,4512],{"class":880},[679,129263,686],{"class":693},[679,129265,129266],{"class":689},"\"work-history\"",[679,129268,4519],{"class":693},[679,129270,129271,129273,129275,129278,129280],{"class":681,"line":3961},[679,129272,4904],{"class":693},[679,129274,4542],{"class":4508},[679,129276,129277],{"class":693},">Work History\u003C/",[679,129279,4542],{"class":4508},[679,129281,4519],{"class":693},[679,129283,129284,129286,129288,129290,129292,129295],{"class":681,"line":3966},[679,129285,4904],{"class":693},[679,129287,4509],{"class":4508},[679,129289,4512],{"class":880},[679,129291,686],{"class":693},[679,129293,129294],{"class":689},"\"job\"",[679,129296,4519],{"class":693},[679,129298,129299,129301,129303,129306,129308],{"class":681,"line":3971},[679,129300,5392],{"class":693},[679,129302,5909],{"class":4508},[679,129304,129305],{"class":693},">Company Name\u003C/",[679,129307,5909],{"class":4508},[679,129309,4519],{"class":693},[679,129311,129312,129314,129316,129319,129321],{"class":681,"line":3976},[679,129313,5392],{"class":693},[679,129315,651],{"class":4508},[679,129317,129318],{"class":693},">Job Title | Date Range\u003C/",[679,129320,651],{"class":4508},[679,129322,4519],{"class":693},[679,129324,129325,129327,129329,129332,129334],{"class":681,"line":3981},[679,129326,5392],{"class":693},[679,129328,651],{"class":4508},[679,129330,129331],{"class":693},">Brief description of your role and responsibilities...\u003C/",[679,129333,651],{"class":4508},[679,129335,4519],{"class":693},[679,129337,129338,129340,129342],{"class":681,"line":3987},[679,129339,5480],{"class":693},[679,129341,4509],{"class":4508},[679,129343,4519],{"class":693},[679,129345,129346],{"class":681,"line":3992},[679,129347,129348],{"class":1400}," \u003C!-- Add more jobs as needed -->\n",[679,129350,129351,129353,129355],{"class":681,"line":3997},[679,129352,4577],{"class":693},[679,129354,66068],{"class":4508},[679,129356,4519],{"class":693},[679,129358,129359,129361,129363],{"class":681,"line":4002},[679,129360,4586],{"class":693},[679,129362,4509],{"class":4508},[679,129364,4519],{"class":693},[679,129366,129367],{"class":681,"line":4007},[679,129368,889],{"emptyLinePlaceholder":797},[679,129370,129371,129373,129376],{"class":681,"line":4012},[679,129372,4505],{"class":693},[679,129374,129375],{"class":4508},"footer",[679,129377,4519],{"class":693},[679,129379,129380,129382,129384,129386,129389,129392,129394],{"class":681,"line":4017},[679,129381,4524],{"class":693},[679,129383,651],{"class":4508},[679,129385,5860],{"class":693},[679,129387,129388],{"class":931},"©",[679,129390,129391],{"class":693}," 2024 Your Name. All rights reserved.\u003C/",[679,129393,651],{"class":4508},[679,129395,4519],{"class":693},[679,129397,129398,129400,129402],{"class":681,"line":4022},[679,129399,4586],{"class":693},[679,129401,129375],{"class":4508},[679,129403,4519],{"class":693},[679,129405,129406,129408,129410],{"class":681,"line":4028},[679,129407,4586],{"class":693},[679,129409,3006],{"class":4508},[679,129411,4519],{"class":693},[679,129413,129414,129416,129418],{"class":681,"line":4033},[679,129415,4586],{"class":693},[679,129417,4498],{"class":4508},[679,129419,4519],{"class":693},[651,129421,129422],{},"This HTML code will appear in the Artifacts window, where you can modify it or ask Claude for further styling suggestions. What I really love about this is you get both the preview and code tabs with this result.",[651,129424,129425],{},[660,129426],{"alt":129427,"src":129428},"Create Portfolio","/images/blog/2024/08/06/create_portfolio.png",[4542,129430,129432],{"id":129431},"projects-organize-your-work-with-claude","Projects: Organize Your Work with Claude",[651,129434,129435],{},"Another exciting feature introduced alongside Claude 3.5 Sonnet is Projects. This new functionality allows users to organize their work and conversations with Claude in a more structured and efficient manner. Let's explore how Projects work and how they can enhance your productivity.",[5909,129437,129439],{"id":129438},"what-are-projects","What are Projects?",[651,129441,129442],{},"Projects are a way to group related conversations, artifacts, and tasks within the Claude interface. This feature helps users manage complex workflows, long-term initiatives, or multiple related topics in a more organized fashion.",[5909,129444,129446],{"id":129445},"key-benefits-of-projects","Key Benefits of Projects",[27665,129448,129449,129455,129461,129467],{},[5332,129450,129451,129454],{},[2939,129452,129453],{},"Organization",": Keep all related conversations and artifacts in one place, making it easier to track progress and find information.",[5332,129456,129457,129460],{},[2939,129458,129459],{},"Continuity",": Maintain context across multiple sessions, allowing you to pick up where you left off without losing important details.",[5332,129462,129463,129466],{},[2939,129464,129465],{},"Collaboration",": Share projects with team members, enabling seamless collaboration on group tasks or research initiatives.",[5332,129468,129469,129472],{},[2939,129470,129471],{},"Version Control",": Track changes and iterations of your work over time, making it easy to review progress or revert to previous versions if needed.",[5909,129474,129476],{"id":129475},"how-to-use-projects","How to Use Projects",[27665,129478,129479,129485,129491,129497,129503],{},[5332,129480,129481,129484],{},[2939,129482,129483],{},"Creating a Project",": Start by giving your project a name and, optionally, a brief description. This helps you quickly identify the purpose of each project.",[5332,129486,129487,129490],{},[2939,129488,129489],{},"Adding Conversations",": As you chat with Claude, you can assign conversations to specific projects. This allows you to keep all related discussions in one place.",[5332,129492,129493,129496],{},[2939,129494,129495],{},"Organizing Artifacts",": Any artifacts created during your conversations can be associated with the relevant project, making it easy to find and manage your generated content.",[5332,129498,129499,129502],{},[2939,129500,129501],{},"Tagging and Filtering",": Use tags to further organize your project content, and take advantage of filtering options to quickly find what you need.",[5332,129504,129505,129508],{},[2939,129506,129507],{},"Sharing and Collaboration",": Invite team members to your project, allowing them to view, contribute, and collaborate on the project's content.",[5909,129510,129512],{"id":129511},"example-use-cases-for-projects","Example Use Cases for Projects",[27665,129514,129515,129521,129526,129531,129537],{},[5332,129516,129517,129520],{},[2939,129518,129519],{},"Research Initiatives",": Organize literature reviews, data analysis, and brainstorming sessions for academic or professional research projects.",[5332,129522,129523,129525],{},[2939,129524,22017],{},": Keep track of feature discussions, code snippets, and documentation for different aspects of your software project.",[5332,129527,129528,129530],{},[2939,129529,43654],{},": Manage outlines, drafts, and revisions for a series of blog posts or a book manuscript.",[5332,129532,129533,129536],{},[2939,129534,129535],{},"Business Planning",": Organize market research, financial projections, and strategy discussions for a new business venture.",[5332,129538,129539,129542],{},[2939,129540,129541],{},"Learning and Skill Development",": Create projects for different subjects or skills you're learning, keeping all relevant conversations and resources in one place.",[651,129544,129545],{},"By leveraging the Projects feature, users can take full advantage of Claude 3.5 Sonnet's capabilities while maintaining a clear and organized workflow. This feature, combined with the power of Artifacts, creates a robust environment for productivity and creativity.",[4542,129547,9042],{"id":9041},[651,129549,129550],{},"Claude 3.5 Sonnet represents a significant advancement in AI technology, offering improved intelligence, speed, and capabilities across various domains. The introduction of Artifacts creates a more interactive and collaborative experience, allowing users to seamlessly integrate AI-generated content into their workflows.",[651,129552,129553],{},"Whether you're a beginner looking to explore the world of AI or an experienced user seeking to enhance your productivity, Claude 3.5 Sonnet offers a user-friendly and powerful platform to assist you in your projects. From coding and content creation to visual analysis and beyond, this new model opens up exciting possibilities for AI-assisted work and learning.",[651,129555,129556],{},"We encourage you to try Claude 3.5 Sonnet and experiment with the Artifacts feature. Don't be afraid to ask questions, request clarifications, or explore new ideas. As AI continues to evolve, tools like Claude 3.5 Sonnet are here to augment human creativity and productivity, offering a glimpse into the future of human-AI collaboration.",[786,129558,129559],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":129561},[129562,129568,129572,129578],{"id":126926,"depth":790,"text":126927,"children":129563},[129564,129565,129566,129567],{"id":126930,"depth":892,"text":126931},{"id":126948,"depth":892,"text":126949},{"id":126966,"depth":892,"text":126967},{"id":126984,"depth":892,"text":126985},{"id":127005,"depth":790,"text":127006,"children":129569},[129570,129571],{"id":127012,"depth":892,"text":127013},{"id":127025,"depth":892,"text":127026},{"id":129431,"depth":790,"text":129432,"children":129573},[129574,129575,129576,129577],{"id":129438,"depth":892,"text":129439},{"id":129445,"depth":892,"text":129446},{"id":129475,"depth":892,"text":129476},{"id":129511,"depth":892,"text":129512},{"id":9041,"depth":790,"text":9042},"Claude 3.5 Sonnet, Anthropic's latest AI model, sets new benchmarks in intelligence, speed, and capabilities across various domains. This blog post explores the key features of Claude 3.5 Sonnet, including its enhanced performance and the groundbreaking Artifacts feature, which enables real-time collaboration between users and AI.",{"slug":129581,"date":129582,"published":797,"author":798,"tags":129583,"cover":129584,"keywords":129585},"claude-sonnet-35","2024-08-06T17:00:00.000Z",[97466],"./claude-sonnet-35.jpeg","AI, Artificial Intelligence (AI), Spring AI, Java, Anthropic, Claude Sonnet 3.5",{"title":81,"description":129579},"blog/2024/08/06/claude-sonnet-35","XkaG1jc1UIelIpURdKP68GcfWjYphxs_blL2VZ9VGns",{"id":129590,"title":78,"body":129591,"description":130636,"extension":793,"meta":130637,"navigation":797,"path":79,"seo":130642,"stem":130643,"__hash__":130644},"content/blog/2024/08/07/claude-sonnet-spring-ai.md",{"type":648,"value":129592,"toc":130623},[129593,129605,129609,129612,129644,129648,129651,129655,129664,129668,129671,129735,129739,129749,129777,129781,129786,130084,130088,130094,130553,130557,130560,130591,130593,130596,130598,130601,130604,130607,130615,130618,130620],[651,129594,93543,129595,129599,129600,129604],{},[812,129596,112246],{"href":129597,"rel":129598},"https://www.danvega.dev/blog/claude-sonnet-35",[816],", I introduced you to Claude 3.5 Sonnet, Anthropic's latest AI model that's revolutionizing the way we interact with artificial intelligence. Today, we're taking it a step further by exploring how to integrate Claude 3.5 Sonnet with Spring applications using ",[812,129601,123549],{"href":129602,"rel":129603},"https://spring.io/projects/spring-ai/",[816],". This powerful combination opens up a world of possibilities for Java developers looking to incorporate state-of-the-art AI capabilities into their projects.",[4542,129606,129608],{"id":129607},"why-choose-claude-35-sonnet-and-spring-ai","Why Choose Claude 3.5 Sonnet and Spring AI?",[651,129610,129611],{},"Before we dive into the code, let's discuss why you might want to use Claude 3.5 Sonnet with Spring AI:",[27665,129613,129614,129620,129626,129632,129638],{},[5332,129615,129616,129619],{},[2939,129617,129618],{},"Seamless Integration",": Spring AI provides a clean, intuitive API for working with various AI models, including Claude 3.5 Sonnet, making it easy to incorporate AI capabilities into your Spring applications.",[5332,129621,129622,129625],{},[2939,129623,129624],{},"Powerful AI Capabilities",": Claude 3.5 Sonnet offers advanced natural language processing, making it ideal for tasks like code generation, text analysis, and content creation.",[5332,129627,129628,129631],{},[2939,129629,129630],{},"Familiar Environment",": For Java developers already comfortable with the Spring ecosystem, using Spring AI feels natural and reduces the learning curve.",[5332,129633,129634,129637],{},[2939,129635,129636],{},"Scalability",": Spring's robust architecture combined with Claude 3.5 Sonnet's efficiency allows your AI-powered applications to scale effectively.",[5332,129639,129640,129643],{},[2939,129641,129642],{},"Flexibility",": Spring AI's abstraction layer makes it easier to switch between different AI models or providers if needed in the future.",[4542,129645,129647],{"id":129646},"setting-up-your-spring-ai-project","Setting Up Your Spring AI Project",[651,129649,129650],{},"Let's walk through the process of setting up a Spring Boot project that uses Spring AI to interact with Claude 3.5 Sonnet. We'll create a simple application that generates Java code based on user prompts.",[5909,129652,129654],{"id":129653},"step-1-create-a-spring-boot-project","Step 1: Create a Spring Boot Project",[651,129656,129657,129658,129661,129662,72927],{},"First, set up a new Spring Boot project with the necessary dependencies. You can use ",[812,129659,7117],{"href":51358,"rel":129660},[816]," or your favorite IDE to create the project. To follow along you will\nonly need the ",[676,129663,92944],{},[5909,129665,129667],{"id":129666},"step-2-add-spring-ai-dependencies","Step 2: Add Spring AI Dependencies",[651,129669,129670],{},"There isn't a starter on the Spring Initializr for Anthropic's Claude so you will need to add it manually. If you're reading this in the future and there is you can add it from the Spring Initializr and skip this step.",[669,129672,129674],{"className":9101,"code":129673,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.ai\u003C/groupId>\n \u003CartifactId>spring-ai-anthropic-spring-boot-starter\u003C/artifactId>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,129675,129676,129685,129693,129706,129719,129727],{"__ignoreMap":674},[679,129677,129678,129680,129683],{"class":681,"line":682},[679,129679,4505],{"class":693},[679,129681,129682],{"class":4508},"dependencies",[679,129684,4519],{"class":693},[679,129686,129687,129689,129691],{"class":681,"line":790},[679,129688,4524],{"class":693},[679,129690,119838],{"class":4508},[679,129692,4519],{"class":693},[679,129694,129695,129697,129699,129702,129704],{"class":681,"line":892},[679,129696,4904],{"class":693},[679,129698,119847],{"class":4508},[679,129700,129701],{"class":693},">org.springframework.ai\u003C/",[679,129703,119847],{"class":4508},[679,129705,4519],{"class":693},[679,129707,129708,129710,129712,129715,129717],{"class":681,"line":901},[679,129709,4904],{"class":693},[679,129711,119861],{"class":4508},[679,129713,129714],{"class":693},">spring-ai-anthropic-spring-boot-starter\u003C/",[679,129716,119861],{"class":4508},[679,129718,4519],{"class":693},[679,129720,129721,129723,129725],{"class":681,"line":909},[679,129722,4577],{"class":693},[679,129724,119838],{"class":4508},[679,129726,4519],{"class":693},[679,129728,129729,129731,129733],{"class":681,"line":918},[679,129730,4586],{"class":693},[679,129732,129682],{"class":4508},[679,129734,4519],{"class":693},[5909,129736,129738],{"id":129737},"step-3-configure-application-properties","Step 3: Configure Application Properties",[651,129740,129741,129742,129744,129745,129748],{},"Set up your ",[676,129743,16242],{}," file with the necessary configuration for Claude 3.5 Sonnet. If you want to hard code the key you can but just remember not to commit your API keys. In the example below I have externalized this key to an environment variable and I am referencing it using the ",[676,129746,129747],{},"${}"," syntax.",[669,129750,129752],{"className":76589,"code":129751,"language":35538,"meta":674,"style":674},"spring.application.name=hello-claude\nspring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}\nspring.ai.anthropic.chat.options.model=claude-3-5-sonnet-20240620\n",[676,129753,129754,129761,129769],{"__ignoreMap":674},[679,129755,129756,129758],{"class":681,"line":682},[679,129757,122485],{"class":685},[679,129759,129760],{"class":693},"=hello-claude\n",[679,129762,129763,129766],{"class":681,"line":790},[679,129764,129765],{"class":685},"spring.ai.anthropic.api-key",[679,129767,129768],{"class":693},"=${ANTHROPIC_API_KEY}\n",[679,129770,129771,129774],{"class":681,"line":892},[679,129772,129773],{"class":685},"spring.ai.anthropic.chat.options.model",[679,129775,129776],{"class":693},"=claude-3-5-sonnet-20240620\n",[5909,129778,129780],{"id":129779},"step-4-create-the-chat-controller","Step 4: Create the Chat Controller",[651,129782,107011,129783,129785],{},[676,129784,122514],{}," that will handle our AI interactions:",[669,129787,129789],{"className":4107,"code":129788,"language":4109,"meta":674,"style":674},"package dev.danvega.claude;\n\nimport org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class ChatController {\n\n private final ChatClient chatClient;\n private final GeneratedCodeService codeService;\n\n public ChatController(ChatClient.Builder builder, GeneratedCodeService codeService) {\n this.chatClient = builder\n .defaultSystem(\"\"\"\n You are helpful AI assistant for writing code. Each class or method you are\n asked to generate should have a supporting test class to cover that method or\n methods. Please include each test in the result.\n Please generate concise and readable code geared towards beginners.\n \"\"\")\n .build();\n this.codeService = codeService;\n }\n\n @GetMapping(\"/\")\n public Code chat() {\n Code code = chatClient.prompt()\n .user(\"\"\"\n Generate a Java class that contains math operations.\n Please contain more than just the basic 4 arithmetic operations.\n \"\"\")\n .call()\n .entity(Code.class);\n codeService.writeToFile(new String[]{code.code(),code.test()});\n return code;\n }\n}\n",[676,129790,129791,129798,129802,129809,129815,129821,129825,129831,129841,129845,129853,129862,129866,129884,129894,129905,129910,129915,129920,129925,129932,129940,129952,129956,129960,129972,129984,129997,130007,130012,130017,130024,130032,130041,130069,130076,130080],{"__ignoreMap":674},[679,129792,129793,129795],{"class":681,"line":682},[679,129794,2543],{"class":685},[679,129796,129797],{"class":693}," dev.danvega.claude;\n",[679,129799,129800],{"class":681,"line":790},[679,129801,889],{"emptyLinePlaceholder":797},[679,129803,129804,129806],{"class":681,"line":892},[679,129805,1999],{"class":685},[679,129807,129808],{"class":693}," org.springframework.ai.chat.client.ChatClient;\n",[679,129810,129811,129813],{"class":681,"line":901},[679,129812,1999],{"class":685},[679,129814,73185],{"class":693},[679,129816,129817,129819],{"class":681,"line":909},[679,129818,1999],{"class":685},[679,129820,12552],{"class":693},[679,129822,129823],{"class":681,"line":918},[679,129824,889],{"emptyLinePlaceholder":797},[679,129826,129827,129829],{"class":681,"line":935},[679,129828,4116],{"class":693},[679,129830,9212],{"class":685},[679,129832,129833,129835,129837,129839],{"class":681,"line":944},[679,129834,6073],{"class":685},[679,129836,4512],{"class":685},[679,129838,121635],{"class":880},[679,129840,884],{"class":693},[679,129842,129843],{"class":681,"line":959},[679,129844,889],{"emptyLinePlaceholder":797},[679,129846,129847,129849,129851],{"class":681,"line":964},[679,129848,9232],{"class":685},[679,129850,12768],{"class":685},[679,129852,121650],{"class":693},[679,129854,129855,129857,129859],{"class":681,"line":977},[679,129856,9232],{"class":685},[679,129858,12768],{"class":685},[679,129860,129861],{"class":693}," GeneratedCodeService codeService;\n",[679,129863,129864],{"class":681,"line":982},[679,129865,889],{"emptyLinePlaceholder":797},[679,129867,129868,129870,129872,129874,129876,129879,129882],{"class":681,"line":988},[679,129869,6089],{"class":685},[679,129871,121635],{"class":880},[679,129873,121663],{"class":693},[679,129875,90934],{"class":2099},[679,129877,129878],{"class":693},", GeneratedCodeService ",[679,129880,129881],{"class":2099},"codeService",[679,129883,4390],{"class":693},[679,129885,129886,129888,129890,129892],{"class":681,"line":993},[679,129887,7862],{"class":931},[679,129889,121674],{"class":693},[679,129891,686],{"class":685},[679,129893,119021],{"class":693},[679,129895,129896,129898,129900,129902],{"class":681,"line":2129},[679,129897,73482],{"class":693},[679,129899,121685],{"class":880},[679,129901,745],{"class":693},[679,129903,129904],{"class":689},"\"\"\"\n",[679,129906,129907],{"class":681,"line":2140},[679,129908,129909],{"class":689}," You are helpful AI assistant for writing code. Each class or method you are\n",[679,129911,129912],{"class":681,"line":2145},[679,129913,129914],{"class":689}," asked to generate should have a supporting test class to cover that method or\n",[679,129916,129917],{"class":681,"line":2154},[679,129918,129919],{"class":689}," methods. Please include each test in the result.\n",[679,129921,129922],{"class":681,"line":2159},[679,129923,129924],{"class":689}," Please generate concise and readable code geared towards beginners.\n",[679,129926,129927,129930],{"class":681,"line":2164},[679,129928,129929],{"class":689}," \"\"\"",[679,129931,1339],{"class":693},[679,129933,129934,129936,129938],{"class":681,"line":3134},[679,129935,73482],{"class":693},[679,129937,23612],{"class":880},[679,129939,9317],{"class":693},[679,129941,129942,129944,129947,129949],{"class":681,"line":3139},[679,129943,7862],{"class":931},[679,129945,129946],{"class":693},".codeService ",[679,129948,686],{"class":685},[679,129950,129951],{"class":693}," codeService;\n",[679,129953,129954],{"class":681,"line":3144},[679,129955,985],{"class":693},[679,129957,129958],{"class":681,"line":3149},[679,129959,889],{"emptyLinePlaceholder":797},[679,129961,129962,129964,129966,129968,129970],{"class":681,"line":3169},[679,129963,6872],{"class":693},[679,129965,20852],{"class":685},[679,129967,745],{"class":693},[679,129969,10032],{"class":689},[679,129971,1339],{"class":693},[679,129973,129974,129976,129979,129982],{"class":681,"line":3185},[679,129975,6089],{"class":685},[679,129977,129978],{"class":693}," Code ",[679,129980,129981],{"class":880},"chat",[679,129983,2667],{"class":693},[679,129985,129986,129989,129991,129993,129995],{"class":681,"line":3194},[679,129987,129988],{"class":693}," Code code ",[679,129990,686],{"class":685},[679,129992,121763],{"class":693},[679,129994,55494],{"class":880},[679,129996,17545],{"class":693},[679,129998,129999,130001,130003,130005],{"class":681,"line":3199},[679,130000,73482],{"class":693},[679,130002,9575],{"class":880},[679,130004,745],{"class":693},[679,130006,129904],{"class":689},[679,130008,130009],{"class":681,"line":3212},[679,130010,130011],{"class":689}," Generate a Java class that contains math operations.\n",[679,130013,130014],{"class":681,"line":3217},[679,130015,130016],{"class":689}," Please contain more than just the basic 4 arithmetic operations.\n",[679,130018,130019,130022],{"class":681,"line":3222},[679,130020,130021],{"class":689}," \"\"\"",[679,130023,1339],{"class":693},[679,130025,130026,130028,130030],{"class":681,"line":3227},[679,130027,73482],{"class":693},[679,130029,121783],{"class":880},[679,130031,17545],{"class":693},[679,130033,130034,130036,130038],{"class":681,"line":3232},[679,130035,73482],{"class":693},[679,130037,122930],{"class":880},[679,130039,130040],{"class":693},"(Code.class);\n",[679,130042,130043,130046,130049,130051,130053,130056,130059,130061,130064,130066],{"class":681,"line":3499},[679,130044,130045],{"class":693}," codeService.",[679,130047,130048],{"class":880},"writeToFile",[679,130050,745],{"class":693},[679,130052,8930],{"class":685},[679,130054,130055],{"class":685}," String",[679,130057,130058],{"class":693},"[]{code.",[679,130060,676],{"class":880},[679,130062,130063],{"class":693},"(),code.",[679,130065,44529],{"class":880},[679,130067,130068],{"class":693},"()});\n",[679,130070,130071,130073],{"class":681,"line":3509},[679,130072,9444],{"class":685},[679,130074,130075],{"class":693}," code;\n",[679,130077,130078],{"class":681,"line":3516},[679,130079,985],{"class":693},[679,130081,130082],{"class":681,"line":3531},[679,130083,996],{"class":693},[5909,130085,130087],{"id":130086},"step-5-create-the-generated-code-service","Step 5: Create the Generated Code Service",[651,130089,130090,130091,2391],{},"To handle the generated code, we'll create a ",[676,130092,130093],{},"GeneratedCodeService",[669,130095,130097],{"className":4107,"code":130096,"language":4109,"meta":674,"style":674},"package dev.danvega.claude;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n@Service\npublic class GeneratedCodeService {\n\n @Value(\"${generated.files.path:src/main/resources/generated}\")\n private String generatedFilesPath;\n\n public void writeToFile(String sourceCode) {\n String className = extractClassName(sourceCode);\n if (className == null) {\n throw new IllegalArgumentException(\"Source Code Does Not Contain a Class Name\");\n }\n\n Path filePath = Paths.get(generatedFilesPath, className + \".java\");\n\n try {\n Files.createDirectories(filePath.getParent());\n Files.writeString(filePath, sourceCode);\n System.out.printf(\"Class '%s' has been written to %s%n\", className, filePath);\n } catch (IOException e) {\n System.err.printf(\"Error writing to file: %s%n\", e.getMessage());\n }\n }\n\n public void writeToFile(String[] sourceCodes) {\n Arrays.stream(sourceCodes).forEach(this::writeToFile);\n }\n\n private String extractClassName(String sourceCode) {\n Pattern pattern = Pattern.compile(\"class\\\\s+(\\\\w+)\");\n Matcher matcher = pattern.matcher(sourceCode);\n if (matcher.find()) {\n return matcher.group(1);\n }\n return null;\n }\n}\n",[676,130098,130099,130105,130109,130115,130121,130125,130131,130138,130145,130152,130159,130166,130170,130176,130187,130191,130204,130211,130215,130231,130244,130257,130274,130278,130282,130304,130308,130314,130330,130340,130355,130367,130386,130390,130394,130398,130417,130438,130442,130446,130461,130490,130505,130517,130533,130537,130545,130549],{"__ignoreMap":674},[679,130100,130101,130103],{"class":681,"line":682},[679,130102,2543],{"class":685},[679,130104,129797],{"class":693},[679,130106,130107],{"class":681,"line":790},[679,130108,889],{"emptyLinePlaceholder":797},[679,130110,130111,130113],{"class":681,"line":892},[679,130112,1999],{"class":685},[679,130114,31740],{"class":693},[679,130116,130117,130119],{"class":681,"line":901},[679,130118,1999],{"class":685},[679,130120,12472],{"class":693},[679,130122,130123],{"class":681,"line":909},[679,130124,889],{"emptyLinePlaceholder":797},[679,130126,130127,130129],{"class":681,"line":918},[679,130128,1999],{"class":685},[679,130130,124143],{"class":693},[679,130132,130133,130135],{"class":681,"line":935},[679,130134,1999],{"class":685},[679,130136,130137],{"class":693}," java.nio.file.Files;\n",[679,130139,130140,130142],{"class":681,"line":944},[679,130141,1999],{"class":685},[679,130143,130144],{"class":693}," java.nio.file.Path;\n",[679,130146,130147,130149],{"class":681,"line":959},[679,130148,1999],{"class":685},[679,130150,130151],{"class":693}," java.nio.file.Paths;\n",[679,130153,130154,130156],{"class":681,"line":964},[679,130155,1999],{"class":685},[679,130157,130158],{"class":693}," java.util.regex.Matcher;\n",[679,130160,130161,130163],{"class":681,"line":977},[679,130162,1999],{"class":685},[679,130164,130165],{"class":693}," java.util.regex.Pattern;\n",[679,130167,130168],{"class":681,"line":982},[679,130169,889],{"emptyLinePlaceholder":797},[679,130171,130172,130174],{"class":681,"line":988},[679,130173,4116],{"class":693},[679,130175,9486],{"class":685},[679,130177,130178,130180,130182,130185],{"class":681,"line":993},[679,130179,6073],{"class":685},[679,130181,4512],{"class":685},[679,130183,130184],{"class":880}," GeneratedCodeService",[679,130186,884],{"class":693},[679,130188,130189],{"class":681,"line":2129},[679,130190,889],{"emptyLinePlaceholder":797},[679,130192,130193,130195,130197,130199,130202],{"class":681,"line":2140},[679,130194,6872],{"class":693},[679,130196,31784],{"class":685},[679,130198,745],{"class":693},[679,130200,130201],{"class":689},"\"${generated.files.path:src/main/resources/generated}\"",[679,130203,1339],{"class":693},[679,130205,130206,130208],{"class":681,"line":2145},[679,130207,9232],{"class":685},[679,130209,130210],{"class":693}," String generatedFilesPath;\n",[679,130212,130213],{"class":681,"line":2154},[679,130214,889],{"emptyLinePlaceholder":797},[679,130216,130217,130219,130221,130224,130226,130229],{"class":681,"line":2159},[679,130218,6089],{"class":685},[679,130220,6095],{"class":685},[679,130222,130223],{"class":880}," writeToFile",[679,130225,11400],{"class":693},[679,130227,130228],{"class":2099},"sourceCode",[679,130230,4390],{"class":693},[679,130232,130233,130236,130238,130241],{"class":681,"line":2164},[679,130234,130235],{"class":693}," String className ",[679,130237,686],{"class":685},[679,130239,130240],{"class":880}," extractClassName",[679,130242,130243],{"class":693},"(sourceCode);\n",[679,130245,130246,130248,130251,130253,130255],{"class":681,"line":3134},[679,130247,1249],{"class":685},[679,130249,130250],{"class":693}," (className ",[679,130252,2304],{"class":685},[679,130254,2307],{"class":931},[679,130256,4390],{"class":693},[679,130258,130259,130262,130264,130267,130269,130272],{"class":681,"line":3139},[679,130260,130261],{"class":685}," throw",[679,130263,2054],{"class":685},[679,130265,130266],{"class":880}," IllegalArgumentException",[679,130268,745],{"class":693},[679,130270,130271],{"class":689},"\"Source Code Does Not Contain a Class Name\"",[679,130273,1208],{"class":693},[679,130275,130276],{"class":681,"line":3144},[679,130277,1290],{"class":693},[679,130279,130280],{"class":681,"line":3149},[679,130281,889],{"emptyLinePlaceholder":797},[679,130283,130284,130287,130289,130292,130294,130297,130299,130302],{"class":681,"line":3169},[679,130285,130286],{"class":693}," Path filePath ",[679,130288,686],{"class":685},[679,130290,130291],{"class":693}," Paths.",[679,130293,7626],{"class":880},[679,130295,130296],{"class":693},"(generatedFilesPath, className ",[679,130298,3065],{"class":685},[679,130300,130301],{"class":689}," \".java\"",[679,130303,1208],{"class":693},[679,130305,130306],{"class":681,"line":3185},[679,130307,889],{"emptyLinePlaceholder":797},[679,130309,130310,130312],{"class":681,"line":3194},[679,130311,9373],{"class":685},[679,130313,884],{"class":693},[679,130315,130316,130319,130322,130325,130328],{"class":681,"line":3199},[679,130317,130318],{"class":693}," Files.",[679,130320,130321],{"class":880},"createDirectories",[679,130323,130324],{"class":693},"(filePath.",[679,130326,130327],{"class":880},"getParent",[679,130329,9431],{"class":693},[679,130331,130332,130334,130337],{"class":681,"line":3212},[679,130333,130318],{"class":693},[679,130335,130336],{"class":880},"writeString",[679,130338,130339],{"class":693},"(filePath, sourceCode);\n",[679,130341,130342,130344,130347,130349,130352],{"class":681,"line":3217},[679,130343,15660],{"class":693},[679,130345,130346],{"class":880},"printf",[679,130348,745],{"class":693},[679,130350,130351],{"class":689},"\"Class '%s' has been written to %s%n\"",[679,130353,130354],{"class":693},", className, filePath);\n",[679,130356,130357,130359,130361,130363,130365],{"class":681,"line":3222},[679,130358,15675],{"class":693},[679,130360,9394],{"class":685},[679,130362,34470],{"class":693},[679,130364,9400],{"class":2099},[679,130366,4390],{"class":693},[679,130368,130369,130372,130374,130376,130379,130382,130384],{"class":681,"line":3227},[679,130370,130371],{"class":693}," System.err.",[679,130373,130346],{"class":880},[679,130375,745],{"class":693},[679,130377,130378],{"class":689},"\"Error writing to file: %s%n\"",[679,130380,130381],{"class":693},", e.",[679,130383,9428],{"class":880},[679,130385,9431],{"class":693},[679,130387,130388],{"class":681,"line":3232},[679,130389,1290],{"class":693},[679,130391,130392],{"class":681,"line":3499},[679,130393,985],{"class":693},[679,130395,130396],{"class":681,"line":3509},[679,130397,889],{"emptyLinePlaceholder":797},[679,130399,130400,130402,130404,130406,130408,130410,130412,130415],{"class":681,"line":3516},[679,130401,6089],{"class":685},[679,130403,6095],{"class":685},[679,130405,130223],{"class":880},[679,130407,745],{"class":693},[679,130409,4758],{"class":685},[679,130411,16901],{"class":693},[679,130413,130414],{"class":2099},"sourceCodes",[679,130416,4390],{"class":693},[679,130418,130419,130422,130424,130427,130429,130431,130433,130435],{"class":681,"line":3531},[679,130420,130421],{"class":693}," Arrays.",[679,130423,87323],{"class":880},[679,130425,130426],{"class":693},"(sourceCodes).",[679,130428,46928],{"class":880},[679,130430,745],{"class":693},[679,130432,4732],{"class":931},[679,130434,90007],{"class":685},[679,130436,130437],{"class":693},"writeToFile);\n",[679,130439,130440],{"class":681,"line":3536},[679,130441,985],{"class":693},[679,130443,130444],{"class":681,"line":3541},[679,130445,889],{"emptyLinePlaceholder":797},[679,130447,130448,130450,130452,130455,130457,130459],{"class":681,"line":3546},[679,130449,9232],{"class":685},[679,130451,9289],{"class":693},[679,130453,130454],{"class":880},"extractClassName",[679,130456,11400],{"class":693},[679,130458,130228],{"class":2099},[679,130460,4390],{"class":693},[679,130462,130463,130466,130468,130471,130473,130475,130478,130480,130483,130485,130488],{"class":681,"line":3551},[679,130464,130465],{"class":693}," Pattern pattern ",[679,130467,686],{"class":685},[679,130469,130470],{"class":693}," Pattern.",[679,130472,7134],{"class":880},[679,130474,745],{"class":693},[679,130476,130477],{"class":689},"\"class",[679,130479,4412],{"class":931},[679,130481,130482],{"class":689},"s+(",[679,130484,4412],{"class":931},[679,130486,130487],{"class":689},"w+)\"",[679,130489,1208],{"class":693},[679,130491,130492,130495,130497,130500,130503],{"class":681,"line":3557},[679,130493,130494],{"class":693}," Matcher matcher ",[679,130496,686],{"class":685},[679,130498,130499],{"class":693}," pattern.",[679,130501,130502],{"class":880},"matcher",[679,130504,130243],{"class":693},[679,130506,130507,130509,130512,130515],{"class":681,"line":3567},[679,130508,1249],{"class":685},[679,130510,130511],{"class":693}," (matcher.",[679,130513,130514],{"class":880},"find",[679,130516,111224],{"class":693},[679,130518,130519,130521,130524,130527,130529,130531],{"class":681,"line":3574},[679,130520,20443],{"class":685},[679,130522,130523],{"class":693}," matcher.",[679,130525,130526],{"class":880},"group",[679,130528,745],{"class":693},[679,130530,1557],{"class":931},[679,130532,1208],{"class":693},[679,130534,130535],{"class":681,"line":3589},[679,130536,1290],{"class":693},[679,130538,130539,130541,130543],{"class":681,"line":3594},[679,130540,9444],{"class":685},[679,130542,2307],{"class":931},[679,130544,1186],{"class":693},[679,130546,130547],{"class":681,"line":3602},[679,130548,985],{"class":693},[679,130550,130551],{"class":681,"line":3608},[679,130552,996],{"class":693},[5909,130554,130556],{"id":130555},"step-6-create-the-code-record","Step 6: Create the Code Record",[651,130558,130559],{},"To structure our generated code and tests, we'll use a simple record:",[669,130561,130563],{"className":4107,"code":130562,"language":4109,"meta":674,"style":674},"package dev.danvega.claude;\n\npublic record Code(String code, String test) {\n}\n",[676,130564,130565,130571,130575,130587],{"__ignoreMap":674},[679,130566,130567,130569],{"class":681,"line":682},[679,130568,2543],{"class":685},[679,130570,129797],{"class":693},[679,130572,130573],{"class":681,"line":790},[679,130574,889],{"emptyLinePlaceholder":797},[679,130576,130577,130579,130581,130584],{"class":681,"line":892},[679,130578,6073],{"class":685},[679,130580,86928],{"class":685},[679,130582,130583],{"class":880}," Code",[679,130585,130586],{"class":693},"(String code, String test) {\n",[679,130588,130589],{"class":681,"line":901},[679,130590,996],{"class":693},[4542,130592,98048],{"id":7309},[651,130594,130595],{},"With everything set up, you can now run your Spring Boot application. When you access the root endpoint (\"/\"), it will generate a Java class containing math operations along with a corresponding test class.",[4542,130597,9042],{"id":9041},[651,130599,130600],{},"Integrating Claude 3.5 Sonnet with Spring AI opens up a world of possibilities for Java developers. From code generation to natural language processing tasks, this powerful combination allows you to leverage cutting-edge AI capabilities within the familiar Spring ecosystem.",[651,130602,130603],{},"As you explore further, consider expanding this example to handle more complex code generation tasks, implement conversational interfaces, or even use Claude 3.5 Sonnet for code review and optimization.",[651,130605,130606],{},"Remember, while AI can significantly boost your productivity, it's essential to review and understand the generated code. Use it as a starting point or inspiration, and always apply your expertise and best practices when incorporating AI-generated code into your projects.",[651,130608,130609,130610,130614],{},"For those who want to dive deeper into the code and explore the full implementation, I've made the complete source code available on my GitHub repository. You can find it at ",[812,130611,130612],{"href":130612,"rel":130613},"https://github.com/danvega/hello-claude",[816],". Feel free to clone the repository, experiment with the code, and adapt it to your own projects. Don't hesitate to open issues or submit pull requests if you have any questions or improvements!",[651,130616,130617],{},"We're excited to see what you'll build with Claude 3.5 Sonnet and Spring AI.",[651,130619,78024],{},[786,130621,130622],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":130624},[130625,130626,130634,130635],{"id":129607,"depth":790,"text":129608},{"id":129646,"depth":790,"text":129647,"children":130627},[130628,130629,130630,130631,130632,130633],{"id":129653,"depth":892,"text":129654},{"id":129666,"depth":892,"text":129667},{"id":129737,"depth":892,"text":129738},{"id":129779,"depth":892,"text":129780},{"id":130086,"depth":892,"text":130087},{"id":130555,"depth":892,"text":130556},{"id":7309,"depth":790,"text":98048},{"id":9041,"depth":790,"text":9042},"Unlock the power of artificial intelligence in your Java applications with Claude 3.5 Sonnet and Spring AI. This beginner-friendly guide walks you through integrating cutting-edge AI capabilities into your Spring projects, opening up a world of possibilities for smart, responsive applications.",{"slug":130638,"date":130639,"published":797,"author":798,"tags":130640,"cover":130641,"github":130612,"keywords":129585},"claude-sonnet-spring-ai","2024-08-07T17:00:00.000Z",[97466,123549,36422],"./claude_sonnet_spring_ai_cover.png",{"title":78,"description":130636},"blog/2024/08/07/claude-sonnet-spring-ai","XgyughNmHSiPWMjhKgswo324jCUuIJZubwi4KLsM-W8",{"id":130646,"title":75,"body":130647,"description":131990,"extension":793,"meta":131991,"navigation":797,"path":76,"seo":131997,"stem":131998,"__hash__":131999},"content/blog/2024/09/12/jdk-23-first-look.md",{"type":648,"value":130648,"toc":131973},[130649,130658,130662,130665,130668,130865,130868,130872,130875,130878,131012,131015,131019,131023,131026,131030,131033,131037,131040,131208,131212,131215,131219,131222,131226,131229,131442,131446,131449,131452,131753,131757,131760,131763,131819,131822,131850,131854,131857,131860,131953,131955,131958,131961,131964,131967,131970],[651,130650,130651,130652,130657],{},"As Java continues its rapid evolution with its six-month release cycle, we're on the cusp of another exciting update. ",[812,130653,130656],{"href":130654,"rel":130655},"https://openjdk.org/projects/jdk/23/",[816],"JDK 23",", scheduled for release on September 17, 2024, brings a host of new features and enhancements that promise to improve developer productivity, code readability, and application performance. In this post, we'll explore the key highlights of Java 23, diving into new preview features, enhancements to existing APIs, and performance improvements that make this release noteworthy.",[4542,130659,130661],{"id":130660},"primitive-types-in-patterns-instanceof-and-switch-preview","Primitive Types in Patterns, instanceof, and switch (Preview)",[651,130663,130664],{},"JEP 455 enhances pattern matching in Java by allowing primitive type patterns in all contexts, including instanceof and switch statements. This feature extends the capabilities of pattern matching to work with all primitive types, not just reference types. It aims to enable more uniform data exploration, align type patterns with instanceof, and allow switch to process values of any primitive type. This change makes the Java language more expressive and consistent, eliminating the need for manual type checking and casting when working with primitive types.",[651,130666,130667],{},"Here's a code example demonstrating some of the new capabilities introduced by JEP 455:",[669,130669,130671],{"className":4107,"code":130670,"language":4109,"meta":674,"style":674},"void processValue(Object value) {\n switch (value) {\n case Integer i when i > 100 -> System.out.println(\"Large integer: \" + i);\n case int i -> System.out.println(\"Small integer: \" + i);\n case Double d when d > 0 -> System.out.println(\"Positive double: \" + d);\n case double d -> System.out.println(\"Non-positive double: \" + d);\n case Long l -> System.out.println(\"Long value: \" + l);\n case Boolean b -> System.out.println(\"Boolean value: \" + b);\n default -> System.out.println(\"Unsupported type\");\n }\n}\n",[676,130672,130673,130683,130691,130720,130743,130770,130794,130817,130839,130857,130861],{"__ignoreMap":674},[679,130674,130675,130677,130680],{"class":681,"line":682},[679,130676,94936],{"class":685},[679,130678,130679],{"class":880}," processValue",[679,130681,130682],{"class":693},"(Object value) {\n",[679,130684,130685,130688],{"class":681,"line":790},[679,130686,130687],{"class":685}," switch",[679,130689,130690],{"class":693}," (value) {\n",[679,130692,130693,130696,130699,130701,130703,130706,130708,130710,130712,130715,130717],{"class":681,"line":892},[679,130694,130695],{"class":685}," case",[679,130697,130698],{"class":693}," Integer i when i ",[679,130700,5860],{"class":685},[679,130702,86193],{"class":931},[679,130704,130705],{"class":685}," ->",[679,130707,125923],{"class":693},[679,130709,1729],{"class":880},[679,130711,745],{"class":693},[679,130713,130714],{"class":689},"\"Large integer: \"",[679,130716,3059],{"class":685},[679,130718,130719],{"class":693}," i);\n",[679,130721,130722,130724,130726,130728,130730,130732,130734,130736,130739,130741],{"class":681,"line":901},[679,130723,130695],{"class":685},[679,130725,14925],{"class":685},[679,130727,36971],{"class":693},[679,130729,16955],{"class":685},[679,130731,125923],{"class":693},[679,130733,1729],{"class":880},[679,130735,745],{"class":693},[679,130737,130738],{"class":689},"\"Small integer: \"",[679,130740,3059],{"class":685},[679,130742,130719],{"class":693},[679,130744,130745,130747,130750,130752,130754,130756,130758,130760,130762,130765,130767],{"class":681,"line":909},[679,130746,130695],{"class":685},[679,130748,130749],{"class":693}," Double d when d ",[679,130751,5860],{"class":685},[679,130753,14987],{"class":931},[679,130755,130705],{"class":685},[679,130757,125923],{"class":693},[679,130759,1729],{"class":880},[679,130761,745],{"class":693},[679,130763,130764],{"class":689},"\"Positive double: \"",[679,130766,3059],{"class":685},[679,130768,130769],{"class":693}," d);\n",[679,130771,130772,130774,130776,130779,130781,130783,130785,130787,130790,130792],{"class":681,"line":918},[679,130773,130695],{"class":685},[679,130775,127059],{"class":685},[679,130777,130778],{"class":693}," d ",[679,130780,16955],{"class":685},[679,130782,125923],{"class":693},[679,130784,1729],{"class":880},[679,130786,745],{"class":693},[679,130788,130789],{"class":689},"\"Non-positive double: \"",[679,130791,3059],{"class":685},[679,130793,130769],{"class":693},[679,130795,130796,130798,130801,130803,130805,130807,130809,130812,130814],{"class":681,"line":935},[679,130797,130695],{"class":685},[679,130799,130800],{"class":693}," Long l ",[679,130802,16955],{"class":685},[679,130804,125923],{"class":693},[679,130806,1729],{"class":880},[679,130808,745],{"class":693},[679,130810,130811],{"class":689},"\"Long value: \"",[679,130813,3059],{"class":685},[679,130815,130816],{"class":693}," l);\n",[679,130818,130819,130821,130824,130826,130828,130830,130832,130835,130837],{"class":681,"line":944},[679,130820,130695],{"class":685},[679,130822,130823],{"class":693}," Boolean b ",[679,130825,16955],{"class":685},[679,130827,125923],{"class":693},[679,130829,1729],{"class":880},[679,130831,745],{"class":693},[679,130833,130834],{"class":689},"\"Boolean value: \"",[679,130836,3059],{"class":685},[679,130838,51949],{"class":693},[679,130840,130841,130844,130846,130848,130850,130852,130855],{"class":681,"line":959},[679,130842,130843],{"class":685}," default",[679,130845,130705],{"class":685},[679,130847,125923],{"class":693},[679,130849,1729],{"class":880},[679,130851,745],{"class":693},[679,130853,130854],{"class":689},"\"Unsupported type\"",[679,130856,1208],{"class":693},[679,130858,130859],{"class":681,"line":964},[679,130860,985],{"class":693},[679,130862,130863],{"class":681,"line":977},[679,130864,996],{"class":693},[651,130866,130867],{},"In this example, we can see how primitive type patterns are used in a switch statement to handle various types, including boxed and unboxed primitives. The code demonstrates type checking, value extraction, and conditional matching (using when clauses) for different primitive types, showcasing the enhanced expressiveness and flexibility provided by JEP 455.",[4542,130869,130871],{"id":130870},"markdown-documentation-comments","Markdown Documentation Comments",[651,130873,130874],{},"Java 23 introduces support for Markdown in documentation comments, a feature that many developers have been eagerly awaiting. This enhancement allows for more expressive and readable documentation directly in the code.",[651,130876,130877],{},"Here's an example of how Markdown syntax can be used in Java documentation:",[669,130879,130881],{"className":4107,"code":130880,"language":4109,"meta":674,"style":674},"/**\n * # Calculator Class\n *\n * This class provides basic arithmetic operations.\n *\n * ## Methods\n *\n * - `add(int a, int b)`: Returns the sum of two integers\n * - `subtract(int a, int b)`: Returns the difference between two integers\n *\n * ## Example Usage\n *\n * java\n * Calculator calc = new Calculator();\n * int sum = calc.add(5, 3); // Returns 8\n * \n*\n* @author Java Developer\n* @version 1.0\n */\n public class Calculator {\n // Class implementation...\n }\n",[676,130882,130883,130888,130893,130898,130903,130907,130912,130916,130921,130926,130930,130935,130939,130944,130949,130957,130962,130966,130977,130987,130992,131003,131008],{"__ignoreMap":674},[679,130884,130885],{"class":681,"line":682},[679,130886,130887],{"class":1400},"/**\n",[679,130889,130890],{"class":681,"line":790},[679,130891,130892],{"class":1400}," * # Calculator Class\n",[679,130894,130895],{"class":681,"line":892},[679,130896,130897],{"class":1400}," *\n",[679,130899,130900],{"class":681,"line":901},[679,130901,130902],{"class":1400}," * This class provides basic arithmetic operations.\n",[679,130904,130905],{"class":681,"line":909},[679,130906,130897],{"class":1400},[679,130908,130909],{"class":681,"line":918},[679,130910,130911],{"class":1400}," * ## Methods\n",[679,130913,130914],{"class":681,"line":935},[679,130915,130897],{"class":1400},[679,130917,130918],{"class":681,"line":944},[679,130919,130920],{"class":1400}," * - `add(int a, int b)`: Returns the sum of two integers\n",[679,130922,130923],{"class":681,"line":959},[679,130924,130925],{"class":1400}," * - `subtract(int a, int b)`: Returns the difference between two integers\n",[679,130927,130928],{"class":681,"line":964},[679,130929,130897],{"class":1400},[679,130931,130932],{"class":681,"line":977},[679,130933,130934],{"class":1400}," * ## Example Usage\n",[679,130936,130937],{"class":681,"line":982},[679,130938,130897],{"class":1400},[679,130940,130941],{"class":681,"line":988},[679,130942,130943],{"class":1400}," * java\n",[679,130945,130946],{"class":681,"line":993},[679,130947,130948],{"class":1400}," * Calculator calc = new Calculator();\n",[679,130950,130951,130954],{"class":681,"line":2129},[679,130952,130953],{"class":1400}," * int sum = calc.add(5, 3);",[679,130955,130956],{"class":1400}," // Returns 8\n",[679,130958,130959],{"class":681,"line":2140},[679,130960,130961],{"class":1400}," * \n",[679,130963,130964],{"class":681,"line":2145},[679,130965,4155],{"class":1400},[679,130967,130968,130971,130974],{"class":681,"line":2154},[679,130969,130970],{"class":1400},"* ",[679,130972,130973],{"class":685},"@author",[679,130975,130976],{"class":1400}," Java Developer\n",[679,130978,130979,130981,130984],{"class":681,"line":2159},[679,130980,130970],{"class":1400},[679,130982,130983],{"class":685},"@version",[679,130985,130986],{"class":1400}," 1.0\n",[679,130988,130989],{"class":681,"line":2164},[679,130990,130991],{"class":1400}," */\n",[679,130993,130994,130996,130998,131001],{"class":681,"line":3134},[679,130995,77606],{"class":685},[679,130997,4512],{"class":685},[679,130999,131000],{"class":880}," Calculator",[679,131002,884],{"class":693},[679,131004,131005],{"class":681,"line":3139},[679,131006,131007],{"class":1400}," // Class implementation...\n",[679,131009,131010],{"class":681,"line":3144},[679,131011,21405],{"class":693},[651,131013,131014],{},"This feature will make documentation more visually appealing and easier to write and read, potentially encouraging developers to create more comprehensive documentation.",[4542,131016,131018],{"id":131017},"enhanced-features","Enhanced Features",[5909,131020,131022],{"id":131021},"class-file-api-second-preview","Class-File API (Second Preview)",[651,131024,131025],{},"The Class-File API, now in its second preview, provides a standard way for parsing, generating, and transforming Java class files. This API is designed to evolve alongside the class-file format and replace the JDK's internal copy of the ASM library.",[5909,131027,131029],{"id":131028},"vector-api-eighth-incubator","Vector API (Eighth Incubator)",[651,131031,131032],{},"The Vector API, in its eighth incubation, allows developers to write vector algorithms that reliably compile to optimal vector instructions on supported CPU architectures. This can potentially improve performance for operations that can be vectorized, such as certain numerical computations or data processing tasks.",[5909,131034,131036],{"id":131035},"stream-gatherers-second-preview","Stream Gatherers (Second Preview)",[651,131038,131039],{},"This enhancement to the Stream API supports custom intermediate operations, making stream pipelines more flexible and expressive. If you want to use a gatherer for a more complex operation that isn't easily achievable with existing Stream API methods, you could create a custom gatherer. For example, let's say you wanted to group the even squares into pairs:",[669,131041,131043],{"className":4107,"code":131042,"language":4109,"meta":674,"style":674},"List\u003CInteger> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);\n\nList\u003CList\u003CInteger>> pairedEvenSquares = numbers.stream()\n .filter(n -> n % 2 == 0)\n .map(n -> n * n)\n .gather(Gatherers.windowFixed(2))\n .toList();\n\n System.out.println(pairedEvenSquares); // Output: [[4, 16], [36, 64], [100]]\n",[676,131044,131045,131101,131105,131124,131148,131165,131184,131192,131196],{"__ignoreMap":674},[679,131046,131047,131049,131051,131053,131055,131057,131059,131061,131063,131065,131067,131069,131071,131073,131075,131077,131079,131081,131083,131085,131087,131089,131091,131093,131095,131097,131099],{"class":681,"line":682},[679,131048,125875],{"class":693},[679,131050,1083],{"class":685},[679,131052,125880],{"class":693},[679,131054,686],{"class":685},[679,131056,16669],{"class":693},[679,131058,16672],{"class":880},[679,131060,745],{"class":693},[679,131062,1557],{"class":931},[679,131064,2797],{"class":693},[679,131066,17262],{"class":931},[679,131068,2797],{"class":693},[679,131070,66599],{"class":931},[679,131072,2797],{"class":693},[679,131074,49776],{"class":931},[679,131076,2797],{"class":693},[679,131078,66775],{"class":931},[679,131080,2797],{"class":693},[679,131082,66698],{"class":931},[679,131084,2797],{"class":693},[679,131086,66709],{"class":931},[679,131088,2797],{"class":693},[679,131090,73893],{"class":931},[679,131092,2797],{"class":693},[679,131094,66844],{"class":931},[679,131096,2797],{"class":693},[679,131098,1205],{"class":931},[679,131100,1208],{"class":693},[679,131102,131103],{"class":681,"line":790},[679,131104,889],{"emptyLinePlaceholder":797},[679,131106,131107,131110,131112,131115,131117,131120,131122],{"class":681,"line":892},[679,131108,131109],{"class":693},"List\u003CList\u003C",[679,131111,1083],{"class":685},[679,131113,131114],{"class":693},">> pairedEvenSquares ",[679,131116,686],{"class":685},[679,131118,131119],{"class":693}," numbers.",[679,131121,87323],{"class":880},[679,131123,17545],{"class":693},[679,131125,131126,131128,131130,131133,131135,131138,131140,131142,131144,131146],{"class":681,"line":901},[679,131127,21331],{"class":693},[679,131129,65029],{"class":880},[679,131131,131132],{"class":693},"(n ",[679,131134,16955],{"class":685},[679,131136,131137],{"class":693}," n ",[679,131139,66892],{"class":685},[679,131141,21871],{"class":931},[679,131143,14515],{"class":685},[679,131145,14987],{"class":931},[679,131147,1339],{"class":693},[679,131149,131150,131152,131154,131156,131158,131160,131162],{"class":681,"line":909},[679,131151,21331],{"class":693},[679,131153,51904],{"class":880},[679,131155,131132],{"class":693},[679,131157,16955],{"class":685},[679,131159,131137],{"class":693},[679,131161,4150],{"class":685},[679,131163,131164],{"class":693}," n)\n",[679,131166,131167,131169,131172,131175,131178,131180,131182],{"class":681,"line":918},[679,131168,21331],{"class":693},[679,131170,131171],{"class":880},"gather",[679,131173,131174],{"class":693},"(Gatherers.",[679,131176,131177],{"class":880},"windowFixed",[679,131179,745],{"class":693},[679,131181,17262],{"class":931},[679,131183,40143],{"class":693},[679,131185,131186,131188,131190],{"class":681,"line":935},[679,131187,21331],{"class":693},[679,131189,85083],{"class":880},[679,131191,9317],{"class":693},[679,131193,131194],{"class":681,"line":944},[679,131195,889],{"emptyLinePlaceholder":797},[679,131197,131198,131200,131202,131205],{"class":681,"line":959},[679,131199,15746],{"class":693},[679,131201,1729],{"class":880},[679,131203,131204],{"class":693},"(pairedEvenSquares); ",[679,131206,131207],{"class":1400},"// Output: [[4, 16], [36, 64], [100]]\n",[5909,131209,131211],{"id":131210},"zgc-generational-mode-by-default","ZGC: Generational Mode by Default",[651,131213,131214],{},"The Z Garbage Collector's generational mode, which maintains separate collections for young and old objects, becomes the default in JDK 23. This change aims to reduce CPU overhead and improve memory release efficiency, potentially leading to better application performance, especially for applications with many short-lived objects.",[4542,131216,131218],{"id":131217},"continued-preview-features","Continued Preview Features",[651,131220,131221],{},"Java 23 also continues to refine some existing preview features:",[5909,131223,131225],{"id":131224},"structured-concurrency-third-preview","Structured Concurrency (Third Preview)",[651,131227,131228],{},"JEP 480 introduces structured concurrency to Java, aiming to simplify concurrent programming by treating related tasks running in different threads as a single unit of work. This approach enhances error handling, cancellation, reliability, and observability. The key component is the StructuredTaskScope class, which allows developers to fork subtasks, join them as a unit, and handle their results or exceptions collectively.\nHere's an example demonstrating structured concurrency using StructuredTaskScope in the context of a weather application:",[669,131230,131232],{"className":4107,"code":131231,"language":4109,"meta":674,"style":674},"WeatherReport getWeatherReport(String location) throws ExecutionException, InterruptedException {\n try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {\n Supplier\u003CTemperature> temperature = scope.fork(() -> getTemperature(location));\n Supplier\u003CHumidity> humidity = scope.fork(() -> getHumidity(location));\n Supplier\u003CWindSpeed> windSpeed = scope.fork(() -> getWindSpeed(location));\n\n scope.join() // Join all subtasks\n .throwIfFailed(); // Propagate errors if any subtask fails\n\n // All subtasks have succeeded, so compose their results\n return new WeatherReport(\n location,\n temperature.get(),\n humidity.get(),\n windSpeed.get()\n );\n }\n}\n",[676,131233,131234,131245,131269,131298,131323,131348,131352,131365,131378,131382,131387,131398,131403,131412,131421,131430,131434,131438],{"__ignoreMap":674},[679,131235,131236,131239,131242],{"class":681,"line":682},[679,131237,131238],{"class":693},"WeatherReport ",[679,131240,131241],{"class":880},"getWeatherReport",[679,131243,131244],{"class":693},"(String location) throws ExecutionException, InterruptedException {\n",[679,131246,131247,131250,131252,131254,131257,131259,131261,131264,131267],{"class":681,"line":790},[679,131248,131249],{"class":685}," try",[679,131251,4193],{"class":693},[679,131253,73698],{"class":685},[679,131255,131256],{"class":693}," scope ",[679,131258,686],{"class":685},[679,131260,2054],{"class":685},[679,131262,131263],{"class":693}," StructuredTaskScope.",[679,131265,131266],{"class":880},"ShutdownOnFailure",[679,131268,111224],{"class":693},[679,131270,131271,131274,131277,131280,131282,131285,131288,131290,131292,131295],{"class":681,"line":892},[679,131272,131273],{"class":693}," Supplier\u003C",[679,131275,131276],{"class":685},"Temperature",[679,131278,131279],{"class":693},"> temperature ",[679,131281,686],{"class":685},[679,131283,131284],{"class":693}," scope.",[679,131286,131287],{"class":880},"fork",[679,131289,55186],{"class":693},[679,131291,16955],{"class":685},[679,131293,131294],{"class":880}," getTemperature",[679,131296,131297],{"class":693},"(location));\n",[679,131299,131300,131302,131305,131308,131310,131312,131314,131316,131318,131321],{"class":681,"line":901},[679,131301,131273],{"class":693},[679,131303,131304],{"class":685},"Humidity",[679,131306,131307],{"class":693},"> humidity ",[679,131309,686],{"class":685},[679,131311,131284],{"class":693},[679,131313,131287],{"class":880},[679,131315,55186],{"class":693},[679,131317,16955],{"class":685},[679,131319,131320],{"class":880}," getHumidity",[679,131322,131297],{"class":693},[679,131324,131325,131327,131330,131333,131335,131337,131339,131341,131343,131346],{"class":681,"line":909},[679,131326,131273],{"class":693},[679,131328,131329],{"class":685},"WindSpeed",[679,131331,131332],{"class":693},"> windSpeed ",[679,131334,686],{"class":685},[679,131336,131284],{"class":693},[679,131338,131287],{"class":880},[679,131340,55186],{"class":693},[679,131342,16955],{"class":685},[679,131344,131345],{"class":880}," getWindSpeed",[679,131347,131297],{"class":693},[679,131349,131350],{"class":681,"line":918},[679,131351,889],{"emptyLinePlaceholder":797},[679,131353,131354,131357,131359,131362],{"class":681,"line":935},[679,131355,131356],{"class":693}," scope.",[679,131358,74402],{"class":880},[679,131360,131361],{"class":693},"() ",[679,131363,131364],{"class":1400},"// Join all subtasks\n",[679,131366,131367,131369,131372,131375],{"class":681,"line":944},[679,131368,73482],{"class":693},[679,131370,131371],{"class":880},"throwIfFailed",[679,131373,131374],{"class":693},"(); ",[679,131376,131377],{"class":1400},"// Propagate errors if any subtask fails\n",[679,131379,131380],{"class":681,"line":959},[679,131381,889],{"emptyLinePlaceholder":797},[679,131383,131384],{"class":681,"line":964},[679,131385,131386],{"class":1400}," // All subtasks have succeeded, so compose their results\n",[679,131388,131389,131391,131393,131396],{"class":681,"line":977},[679,131390,9444],{"class":685},[679,131392,2054],{"class":685},[679,131394,131395],{"class":880}," WeatherReport",[679,131397,21337],{"class":693},[679,131399,131400],{"class":681,"line":982},[679,131401,131402],{"class":693}," location,\n",[679,131404,131405,131408,131410],{"class":681,"line":988},[679,131406,131407],{"class":693}," temperature.",[679,131409,7626],{"class":880},[679,131411,56208],{"class":693},[679,131413,131414,131417,131419],{"class":681,"line":993},[679,131415,131416],{"class":693}," humidity.",[679,131418,7626],{"class":880},[679,131420,56208],{"class":693},[679,131422,131423,131426,131428],{"class":681,"line":2129},[679,131424,131425],{"class":693}," windSpeed.",[679,131427,7626],{"class":880},[679,131429,17545],{"class":693},[679,131431,131432],{"class":681,"line":2140},[679,131433,21400],{"class":693},[679,131435,131436],{"class":681,"line":2145},[679,131437,985],{"class":693},[679,131439,131440],{"class":681,"line":2154},[679,131441,996],{"class":693},[5909,131443,131445],{"id":131444},"scoped-values-third-preview","Scoped Values (Third Preview)",[651,131447,131448],{},"JEP 481 introduces scoped values, a powerful new feature in Java that enables methods to share immutable data with their callees within a thread and with child threads. This feature addresses some of the limitations of thread-local variables, offering improved performance and easier reasoning about data flow. Scoped values are particularly beneficial when used in conjunction with virtual threads and structured concurrency, making them an excellent fit for modern, high-concurrency Java applications.",[651,131450,131451],{},"Let's look at a simple example to illustrate how scoped values work:",[669,131453,131455],{"className":4107,"code":131454,"language":4109,"meta":674,"style":674},"public class UserContext {\n private static final ScopedValue\u003CString> USER_ID = ScopedValue.newInstance();\n\n public static void processRequest(String userId, Runnable task) {\n ScopedValue.where(USER_ID, userId).run(() -> {\n // The userId is now available to all code executed within this lambda\n task.run();\n });\n }\n\n public static String getCurrentUserId() {\n return USER_ID.get();\n }\n}\n\npublic class UserService {\n public void performUserOperation() {\n String userId = UserContext.getCurrentUserId();\n System.out.println(\"Performing operation for user: \" + userId);\n // Perform user-specific operations...\n }\n}\n\npublic class Main {\n public static void main(String[] args) {\n UserContext.processRequest(\"user123\", () -> {\n UserService service = new UserService();\n service.performUserOperation();\n });\n }\n}\n",[676,131456,131457,131468,131493,131497,131519,131538,131543,131552,131557,131561,131565,131578,131589,131593,131597,131601,131611,131622,131636,131652,131657,131661,131665,131669,131679,131699,131718,131731,131741,131745,131749],{"__ignoreMap":674},[679,131458,131459,131461,131463,131466],{"class":681,"line":682},[679,131460,6073],{"class":685},[679,131462,4512],{"class":685},[679,131464,131465],{"class":880}," UserContext",[679,131467,884],{"class":693},[679,131469,131470,131472,131474,131476,131479,131481,131484,131486,131489,131491],{"class":681,"line":790},[679,131471,9232],{"class":685},[679,131473,6092],{"class":685},[679,131475,12768],{"class":685},[679,131477,131478],{"class":693}," ScopedValue\u003C",[679,131480,4758],{"class":685},[679,131482,131483],{"class":693},"> USER_ID ",[679,131485,686],{"class":685},[679,131487,131488],{"class":693}," ScopedValue.",[679,131490,99499],{"class":880},[679,131492,9317],{"class":693},[679,131494,131495],{"class":681,"line":892},[679,131496,889],{"emptyLinePlaceholder":797},[679,131498,131499,131501,131503,131505,131508,131510,131512,131515,131517],{"class":681,"line":901},[679,131500,6089],{"class":685},[679,131502,6092],{"class":685},[679,131504,6095],{"class":685},[679,131506,131507],{"class":880}," processRequest",[679,131509,11400],{"class":693},[679,131511,118768],{"class":2099},[679,131513,131514],{"class":693},", Runnable ",[679,131516,21434],{"class":2099},[679,131518,4390],{"class":693},[679,131520,131521,131524,131527,131530,131532,131534,131536],{"class":681,"line":909},[679,131522,131523],{"class":693}," ScopedValue.",[679,131525,131526],{"class":880},"where",[679,131528,131529],{"class":693},"(USER_ID, userId).",[679,131531,6118],{"class":880},[679,131533,55186],{"class":693},[679,131535,16955],{"class":685},[679,131537,884],{"class":693},[679,131539,131540],{"class":681,"line":918},[679,131541,131542],{"class":1400}," // The userId is now available to all code executed within this lambda\n",[679,131544,131545,131548,131550],{"class":681,"line":935},[679,131546,131547],{"class":693}," task.",[679,131549,6118],{"class":880},[679,131551,9317],{"class":693},[679,131553,131554],{"class":681,"line":944},[679,131555,131556],{"class":693}," });\n",[679,131558,131559],{"class":681,"line":959},[679,131560,985],{"class":693},[679,131562,131563],{"class":681,"line":964},[679,131564,889],{"emptyLinePlaceholder":797},[679,131566,131567,131569,131571,131573,131576],{"class":681,"line":977},[679,131568,6089],{"class":685},[679,131570,6092],{"class":685},[679,131572,9289],{"class":693},[679,131574,131575],{"class":880},"getCurrentUserId",[679,131577,2667],{"class":693},[679,131579,131580,131582,131585,131587],{"class":681,"line":982},[679,131581,9444],{"class":685},[679,131583,131584],{"class":693}," USER_ID.",[679,131586,7626],{"class":880},[679,131588,9317],{"class":693},[679,131590,131591],{"class":681,"line":988},[679,131592,985],{"class":693},[679,131594,131595],{"class":681,"line":993},[679,131596,996],{"class":693},[679,131598,131599],{"class":681,"line":2129},[679,131600,889],{"emptyLinePlaceholder":797},[679,131602,131603,131605,131607,131609],{"class":681,"line":2140},[679,131604,6073],{"class":685},[679,131606,4512],{"class":685},[679,131608,34065],{"class":880},[679,131610,884],{"class":693},[679,131612,131613,131615,131617,131620],{"class":681,"line":2145},[679,131614,6089],{"class":685},[679,131616,6095],{"class":685},[679,131618,131619],{"class":880}," performUserOperation",[679,131621,2667],{"class":693},[679,131623,131624,131627,131629,131632,131634],{"class":681,"line":2154},[679,131625,131626],{"class":693}," String userId ",[679,131628,686],{"class":685},[679,131630,131631],{"class":693}," UserContext.",[679,131633,131575],{"class":880},[679,131635,9317],{"class":693},[679,131637,131638,131640,131642,131644,131647,131649],{"class":681,"line":2159},[679,131639,9592],{"class":693},[679,131641,1729],{"class":880},[679,131643,745],{"class":693},[679,131645,131646],{"class":689},"\"Performing operation for user: \"",[679,131648,3059],{"class":685},[679,131650,131651],{"class":693}," userId);\n",[679,131653,131654],{"class":681,"line":2164},[679,131655,131656],{"class":1400}," // Perform user-specific operations...\n",[679,131658,131659],{"class":681,"line":3134},[679,131660,985],{"class":693},[679,131662,131663],{"class":681,"line":3139},[679,131664,996],{"class":693},[679,131666,131667],{"class":681,"line":3144},[679,131668,889],{"emptyLinePlaceholder":797},[679,131670,131671,131673,131675,131677],{"class":681,"line":3149},[679,131672,6073],{"class":685},[679,131674,4512],{"class":685},[679,131676,15473],{"class":880},[679,131678,884],{"class":693},[679,131680,131681,131683,131685,131687,131689,131691,131693,131695,131697],{"class":681,"line":3169},[679,131682,6089],{"class":685},[679,131684,6092],{"class":685},[679,131686,6095],{"class":685},[679,131688,6098],{"class":880},[679,131690,745],{"class":693},[679,131692,4758],{"class":685},[679,131694,16901],{"class":693},[679,131696,6108],{"class":2099},[679,131698,4390],{"class":693},[679,131700,131701,131704,131707,131709,131712,131714,131716],{"class":681,"line":3185},[679,131702,131703],{"class":693}," UserContext.",[679,131705,131706],{"class":880},"processRequest",[679,131708,745],{"class":693},[679,131710,131711],{"class":689},"\"user123\"",[679,131713,64887],{"class":693},[679,131715,16955],{"class":685},[679,131717,884],{"class":693},[679,131719,131720,131723,131725,131727,131729],{"class":681,"line":3194},[679,131721,131722],{"class":693}," UserService service ",[679,131724,686],{"class":685},[679,131726,2054],{"class":685},[679,131728,34065],{"class":880},[679,131730,9317],{"class":693},[679,131732,131733,131736,131739],{"class":681,"line":3199},[679,131734,131735],{"class":693}," service.",[679,131737,131738],{"class":880},"performUserOperation",[679,131740,9317],{"class":693},[679,131742,131743],{"class":681,"line":3212},[679,131744,131556],{"class":693},[679,131746,131747],{"class":681,"line":3217},[679,131748,985],{"class":693},[679,131750,131751],{"class":681,"line":3222},[679,131752,996],{"class":693},[5909,131754,131756],{"id":131755},"implicitly-declared-classes-and-instance-main-methods-third-preview","Implicitly Declared Classes and Instance Main Methods (Third Preview)",[651,131758,131759],{},"JEP 477 represents a significant step forward in making Java more accessible to beginners and more convenient for experienced developers writing small programs. By introducing implicitly declared classes, instance main methods, and simplified I/O operations, Java is becoming more user-friendly without sacrificing its power and flexibility.",[651,131761,131762],{},"Before JEP 477",[669,131764,131766],{"className":4107,"code":131765,"language":4109,"meta":674,"style":674},"public class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n}\n",[676,131767,131768,131779,131799,131811,131815],{"__ignoreMap":674},[679,131769,131770,131772,131774,131777],{"class":681,"line":682},[679,131771,6073],{"class":685},[679,131773,4512],{"class":685},[679,131775,131776],{"class":880}," HelloWorld",[679,131778,884],{"class":693},[679,131780,131781,131783,131785,131787,131789,131791,131793,131795,131797],{"class":681,"line":790},[679,131782,6089],{"class":685},[679,131784,6092],{"class":685},[679,131786,6095],{"class":685},[679,131788,6098],{"class":880},[679,131790,745],{"class":693},[679,131792,4758],{"class":685},[679,131794,16901],{"class":693},[679,131796,6108],{"class":2099},[679,131798,4390],{"class":693},[679,131800,131801,131803,131805,131807,131809],{"class":681,"line":892},[679,131802,9592],{"class":693},[679,131804,1729],{"class":880},[679,131806,745],{"class":693},[679,131808,71855],{"class":689},[679,131810,1208],{"class":693},[679,131812,131813],{"class":681,"line":901},[679,131814,985],{"class":693},[679,131816,131817],{"class":681,"line":909},[679,131818,996],{"class":693},[651,131820,131821],{},"After JEP 477",[669,131823,131825],{"className":4107,"code":131824,"language":4109,"meta":674,"style":674},"void main() {\n println(\"Hello, World!\");\n}\n",[676,131826,131827,131835,131846],{"__ignoreMap":674},[679,131828,131829,131831,131833],{"class":681,"line":682},[679,131830,94936],{"class":685},[679,131832,6098],{"class":880},[679,131834,2667],{"class":693},[679,131836,131837,131840,131842,131844],{"class":681,"line":790},[679,131838,131839],{"class":880}," println",[679,131841,745],{"class":693},[679,131843,71855],{"class":689},[679,131845,1208],{"class":693},[679,131847,131848],{"class":681,"line":892},[679,131849,996],{"class":693},[5909,131851,131853],{"id":131852},"flexible-constructor-bodies-second-preview","Flexible Constructor Bodies (Second Preview)",[651,131855,131856],{},"JEP 482: Flexible Constructor Bodies (Second Preview) is a feature aimed at giving Java developers more freedom in constructing objects. It allows statements to appear before the explicit constructor invocation (i.e., super() or this()) in a constructor body. This change enables developers to validate or prepare arguments, initialize fields, and perform other setup tasks before calling the superclass constructor. The key benefit is improved reliability when methods are overridden, as it allows subclass constructors to ensure that superclass constructors never see uninitialized fields.",[651,131858,131859],{},"Here's a simple code sample demonstrating this new flexibility",[669,131861,131863],{"className":4107,"code":131862,"language":4109,"meta":674,"style":674},"public class PositiveBigInteger extends BigInteger {\n public PositiveBigInteger(long value) {\n if (value \u003C= 0) {\n throw new IllegalArgumentException(\"Value must be positive\");\n }\n super(Long.toString(value));\n // Additional initialization can be done here\n }\n}\n",[676,131864,131865,131881,131895,131908,131923,131927,131940,131945,131949],{"__ignoreMap":674},[679,131866,131867,131869,131871,131874,131876,131879],{"class":681,"line":682},[679,131868,6073],{"class":685},[679,131870,4512],{"class":685},[679,131872,131873],{"class":880}," PositiveBigInteger",[679,131875,2767],{"class":685},[679,131877,131878],{"class":880}," BigInteger",[679,131880,884],{"class":693},[679,131882,131883,131885,131887,131889,131891,131893],{"class":681,"line":790},[679,131884,6089],{"class":685},[679,131886,131873],{"class":880},[679,131888,745],{"class":693},[679,131890,1088],{"class":685},[679,131892,65142],{"class":2099},[679,131894,4390],{"class":693},[679,131896,131897,131899,131902,131904,131906],{"class":681,"line":892},[679,131898,1249],{"class":685},[679,131900,131901],{"class":693}," (value ",[679,131903,1563],{"class":685},[679,131905,14987],{"class":931},[679,131907,4390],{"class":693},[679,131909,131910,131912,131914,131916,131918,131921],{"class":681,"line":901},[679,131911,130261],{"class":685},[679,131913,2054],{"class":685},[679,131915,130266],{"class":880},[679,131917,745],{"class":693},[679,131919,131920],{"class":689},"\"Value must be positive\"",[679,131922,1208],{"class":693},[679,131924,131925],{"class":681,"line":909},[679,131926,1290],{"class":693},[679,131928,131929,131932,131935,131937],{"class":681,"line":918},[679,131930,131931],{"class":931}," super",[679,131933,131934],{"class":693},"(Long.",[679,131936,14391],{"class":880},[679,131938,131939],{"class":693},"(value));\n",[679,131941,131942],{"class":681,"line":935},[679,131943,131944],{"class":1400}," // Additional initialization can be done here\n",[679,131946,131947],{"class":681,"line":944},[679,131948,985],{"class":693},[679,131950,131951],{"class":681,"line":959},[679,131952,996],{"class":693},[4542,131954,9042],{"id":9041},[651,131956,131957],{},"Java 23 brings a mix of exciting new preview features, refinements to existing ones, and performance improvements that keep Java at the forefront of programming languages. While preview features are not yet ready for production use, they offer a glimpse into Java's future and provide opportunities for developers to experiment and provide feedback.",[651,131959,131960],{},"As Java continues to evolve, it's crucial for developers to stay informed about these changes. Whether you're building enterprise applications, working on open-source projects, or just learning the language, understanding these new features can help you write more expressive, efficient, and maintainable code.",[651,131962,131963],{},"What feature of Java 23 are you most excited about? How do you see these changes impacting your development workflow? We'd love to hear your thoughts and experiences as you explore this latest release of Java!",[651,131965,131966],{},"Remember, the journey of Java's evolution is ongoing, and your feedback as a developer plays a crucial role in shaping its future. So don't hesitate to dive in, experiment with these new features, and share your insights with the Java community.",[651,131968,131969],{},"Happy coding with Java 23!",[786,131971,131972],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":131974},[131975,131976,131977,131983,131989],{"id":130660,"depth":790,"text":130661},{"id":130870,"depth":790,"text":130871},{"id":131017,"depth":790,"text":131018,"children":131978},[131979,131980,131981,131982],{"id":131021,"depth":892,"text":131022},{"id":131028,"depth":892,"text":131029},{"id":131035,"depth":892,"text":131036},{"id":131210,"depth":892,"text":131211},{"id":131217,"depth":790,"text":131218,"children":131984},[131985,131986,131987,131988],{"id":131224,"depth":892,"text":131225},{"id":131444,"depth":892,"text":131445},{"id":131755,"depth":892,"text":131756},{"id":131852,"depth":892,"text":131853},{"id":9041,"depth":790,"text":9042},"Explore the exciting new features and improvements coming in Java 23, including primitive types in patterns, Markdown documentation comments, and enhancements to garbage collection and concurrency.",{"slug":131992,"date":131993,"published":797,"author":798,"tags":131994,"cover":131995,"keywords":131996},"jdk-23-first-look","2024-09-12T17:00:00.000Z",[4109],"./jdk-23.png","Java 23, JDK 23, primitive types in patterns, Markdown documentation, ZGC, Stream Gatherers, Vector API, Structured Concurrency, Scoped Values",{"title":75,"description":131990},"blog/2024/09/12/jdk-23-first-look","NXP2OiY8MoFQOZ3R5PsroaFGYYyndKS7i4Cre1hqxYI",{"id":132001,"title":72,"body":132002,"description":132749,"extension":793,"meta":132750,"navigation":797,"path":73,"seo":132756,"stem":132757,"__hash__":132758},"content/blog/2024/10/01/hello-jte.md",{"type":648,"value":132003,"toc":132738},[132004,132007,132011,132014,132017,132060,132064,132067,132086,132089,132173,132179,132277,132281,132284,132288,132294,132410,132414,132417,132472,132599,132603,132606,132692,132696,132699,132722,132724,132727,132730,132733,132735],[651,132005,132006],{},"As Java developers, we're always on the lookout for tools that can enhance our productivity and improve our application's performance. Today, we're excited to introduce you to JTE (Java Template Engine), the latest addition to the Spring Initializer family. In this post, we'll explore what JTE is, how to get started with it in Spring Boot, and why you might want to consider it for your next project.",[4542,132008,132010],{"id":132009},"what-is-jte","What is JTE?",[651,132012,132013],{},"JTE is a modern, lightweight Java template engine that's designed to be both powerful and easy to use. It's been available for use with Spring Boot for a while, but its recent addition to the Spring Initializer makes it more accessible than ever.",[651,132015,132016],{},"Some key features and benefits of JTE include:",[27665,132018,132019,132024,132030,132036,132042,132048,132054],{},[5332,132020,132021,132023],{},[2939,132022,74024],{},": JTE compiles templates to Java bytecode, resulting in efficient runtime execution.",[5332,132025,132026,132029],{},[2939,132027,132028],{},"Compile-time checking",": Catch errors at compile time rather than runtime.",[5332,132031,132032,132035],{},[2939,132033,132034],{},"Simple syntax",": Easy for Java developers to pick up quickly.",[5332,132037,132038,132041],{},[2939,132039,132040],{},"Lightweight",": Minimal overhead and dependencies.",[5332,132043,132044,132047],{},[2939,132045,132046],{},"Hot reloading",": See changes immediately without restarting your application.",[5332,132049,132050,132053],{},[2939,132051,132052],{},"Pre-compilation",": Improve startup times by pre-compiling templates.",[5332,132055,132056,132059],{},[2939,132057,132058],{},"Explicit over implicit",": Declare variables at the top of templates for better documentation.",[4542,132061,132063],{"id":132062},"getting-started-with-jte-in-spring-boot","Getting Started with JTE in Spring Boot",[651,132065,132066],{},"Let's walk through setting up a basic Spring Boot application with JTE:",[27665,132068,132069,132074,132077,132080,132083],{},[5332,132070,77474,132071,664],{},[812,132072,77478],{"href":51358,"rel":132073},[816],[5332,132075,132076],{},"Choose your project settings (Java, latest Spring Boot version).",[5332,132078,132079],{},"Add the \"Web\" dependency.",[5332,132081,132082],{},"In the dependencies search, type \"template\" and select \"JTE\".",[5332,132084,132085],{},"Generate and download the project.",[651,132087,132088],{},"Once you've opened the project in your IDE, create a simple controller:",[669,132090,132092],{"className":4107,"code":132091,"language":4109,"meta":674,"style":674},"@Controller\npublic class HomeController {\n\n @GetMapping(\"/\")\n public String home(Model model) {\n model.addAttribute(\"name\", \"Dan\");\n return \"index\";\n }\n}\n",[676,132093,132094,132100,132110,132114,132126,132140,132157,132165,132169],{"__ignoreMap":674},[679,132095,132096,132098],{"class":681,"line":682},[679,132097,4116],{"class":693},[679,132099,9942],{"class":685},[679,132101,132102,132104,132106,132108],{"class":681,"line":790},[679,132103,6073],{"class":685},[679,132105,4512],{"class":685},[679,132107,18716],{"class":880},[679,132109,884],{"class":693},[679,132111,132112],{"class":681,"line":892},[679,132113,889],{"emptyLinePlaceholder":797},[679,132115,132116,132118,132120,132122,132124],{"class":681,"line":901},[679,132117,6872],{"class":693},[679,132119,20852],{"class":685},[679,132121,745],{"class":693},[679,132123,10032],{"class":689},[679,132125,1339],{"class":693},[679,132127,132128,132130,132132,132134,132136,132138],{"class":681,"line":909},[679,132129,6089],{"class":685},[679,132131,9289],{"class":693},[679,132133,12642],{"class":880},[679,132135,10045],{"class":693},[679,132137,10048],{"class":2099},[679,132139,4390],{"class":693},[679,132141,132142,132144,132146,132148,132151,132153,132155],{"class":681,"line":918},[679,132143,10061],{"class":693},[679,132145,10064],{"class":880},[679,132147,745],{"class":693},[679,132149,132150],{"class":689},"\"name\"",[679,132152,2797],{"class":693},[679,132154,1414],{"class":689},[679,132156,1208],{"class":693},[679,132158,132159,132161,132163],{"class":681,"line":935},[679,132160,9444],{"class":685},[679,132162,18769],{"class":689},[679,132164,1186],{"class":693},[679,132166,132167],{"class":681,"line":944},[679,132168,985],{"class":693},[679,132170,132171],{"class":681,"line":959},[679,132172,996],{"class":693},[651,132174,132175,132176,2391],{},"Now, create a template file ",[676,132177,132178],{},"src/main/jte/index.jte",[669,132180,132182],{"className":4496,"code":132181,"language":4498,"meta":674,"style":674},"@param String name\n\n\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n \u003Ctitle>Hello JTE\u003C/title>\n\u003C/head>\n\u003Cbody>\n \u003Ch1>Hello, ${name}!\u003C/h1>\n\u003C/body>\n\u003C/html>\n",[676,132183,132184,132189,132193,132203,132211,132219,132232,132240,132248,132261,132269],{"__ignoreMap":674},[679,132185,132186],{"class":681,"line":682},[679,132187,132188],{"class":693},"@param String name\n",[679,132190,132191],{"class":681,"line":790},[679,132192,889],{"emptyLinePlaceholder":797},[679,132194,132195,132197,132199,132201],{"class":681,"line":892},[679,132196,11904],{"class":693},[679,132198,11907],{"class":4508},[679,132200,11910],{"class":880},[679,132202,4519],{"class":693},[679,132204,132205,132207,132209],{"class":681,"line":901},[679,132206,4505],{"class":693},[679,132208,4498],{"class":4508},[679,132210,4519],{"class":693},[679,132212,132213,132215,132217],{"class":681,"line":909},[679,132214,4505],{"class":693},[679,132216,11741],{"class":4508},[679,132218,4519],{"class":693},[679,132220,132221,132223,132225,132228,132230],{"class":681,"line":918},[679,132222,4524],{"class":693},[679,132224,11750],{"class":4508},[679,132226,132227],{"class":693},">Hello JTE\u003C/",[679,132229,11750],{"class":4508},[679,132231,4519],{"class":693},[679,132233,132234,132236,132238],{"class":681,"line":935},[679,132235,4586],{"class":693},[679,132237,11741],{"class":4508},[679,132239,4519],{"class":693},[679,132241,132242,132244,132246],{"class":681,"line":944},[679,132243,4505],{"class":693},[679,132245,3006],{"class":4508},[679,132247,4519],{"class":693},[679,132249,132250,132252,132254,132257,132259],{"class":681,"line":959},[679,132251,4524],{"class":693},[679,132253,11859],{"class":4508},[679,132255,132256],{"class":693},">Hello, ${name}!\u003C/",[679,132258,11859],{"class":4508},[679,132260,4519],{"class":693},[679,132262,132263,132265,132267],{"class":681,"line":964},[679,132264,4586],{"class":693},[679,132266,3006],{"class":4508},[679,132268,4519],{"class":693},[679,132270,132271,132273,132275],{"class":681,"line":977},[679,132272,4586],{"class":693},[679,132274,4498],{"class":4508},[679,132276,4519],{"class":693},[4542,132278,132280],{"id":132279},"working-with-jte-templates","Working with JTE Templates",[651,132282,132283],{},"JTE's syntax is designed to be intuitive for Java developers. Let's explore some key features:",[5909,132285,132287],{"id":132286},"passing-variables-to-templates","Passing Variables to Templates",[651,132289,132290,132291,132293],{},"You can pass variables to your templates using the ",[676,132292,16802],{}," directive:",[669,132295,132297],{"className":4496,"code":132296,"language":4498,"meta":674,"style":674},"@param String name\n\n\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n \u003Ctitle>Page Title\u003C/title>\n \u003Cmeta name=\"description\" content=\"\">\n\u003C/head>\n\u003Cbody>\n \u003Ch1>Hello, ${name}!\u003C/h1>\n\u003C/body>\n\u003C/html>\n",[676,132298,132299,132303,132307,132317,132325,132333,132346,132366,132374,132382,132394,132402],{"__ignoreMap":674},[679,132300,132301],{"class":681,"line":682},[679,132302,132188],{"class":693},[679,132304,132305],{"class":681,"line":790},[679,132306,889],{"emptyLinePlaceholder":797},[679,132308,132309,132311,132313,132315],{"class":681,"line":892},[679,132310,11904],{"class":693},[679,132312,11907],{"class":4508},[679,132314,11910],{"class":880},[679,132316,4519],{"class":693},[679,132318,132319,132321,132323],{"class":681,"line":901},[679,132320,4505],{"class":693},[679,132322,4498],{"class":4508},[679,132324,4519],{"class":693},[679,132326,132327,132329,132331],{"class":681,"line":909},[679,132328,4505],{"class":693},[679,132330,11741],{"class":4508},[679,132332,4519],{"class":693},[679,132334,132335,132337,132339,132342,132344],{"class":681,"line":918},[679,132336,4524],{"class":693},[679,132338,11750],{"class":4508},[679,132340,132341],{"class":693},">Page Title\u003C/",[679,132343,11750],{"class":4508},[679,132345,4519],{"class":693},[679,132347,132348,132350,132352,132354,132356,132358,132360,132362,132364],{"class":681,"line":935},[679,132349,4524],{"class":693},[679,132351,11968],{"class":4508},[679,132353,5283],{"class":880},[679,132355,686],{"class":693},[679,132357,47908],{"class":689},[679,132359,11995],{"class":880},[679,132361,686],{"class":693},[679,132363,3579],{"class":689},[679,132365,4519],{"class":693},[679,132367,132368,132370,132372],{"class":681,"line":944},[679,132369,4586],{"class":693},[679,132371,11741],{"class":4508},[679,132373,4519],{"class":693},[679,132375,132376,132378,132380],{"class":681,"line":959},[679,132377,4505],{"class":693},[679,132379,3006],{"class":4508},[679,132381,4519],{"class":693},[679,132383,132384,132386,132388,132390,132392],{"class":681,"line":964},[679,132385,4524],{"class":693},[679,132387,11859],{"class":4508},[679,132389,132256],{"class":693},[679,132391,11859],{"class":4508},[679,132393,4519],{"class":693},[679,132395,132396,132398,132400],{"class":681,"line":977},[679,132397,4586],{"class":693},[679,132399,3006],{"class":4508},[679,132401,4519],{"class":693},[679,132403,132404,132406,132408],{"class":681,"line":982},[679,132405,4586],{"class":693},[679,132407,4498],{"class":4508},[679,132409,4519],{"class":693},[5909,132411,132413],{"id":132412},"using-records-for-structured-data","Using Records for Structured Data",[651,132415,132416],{},"JTE works seamlessly with Java records, making it easy to pass structured data to your templates:",[669,132418,132420],{"className":4107,"code":132419,"language":4109,"meta":674,"style":674},"public record Page(String title, String description) {}\n\n// In your controller\nmodel.addAttribute(\"page\", new Page(\"Hello JTE\", \"Welcome to JTE\"));\n",[676,132421,132422,132434,132438,132443],{"__ignoreMap":674},[679,132423,132424,132426,132428,132431],{"class":681,"line":682},[679,132425,6073],{"class":685},[679,132427,86928],{"class":685},[679,132429,132430],{"class":880}," Page",[679,132432,132433],{"class":693},"(String title, String description) {}\n",[679,132435,132436],{"class":681,"line":790},[679,132437,889],{"emptyLinePlaceholder":797},[679,132439,132440],{"class":681,"line":892},[679,132441,132442],{"class":1400},"// In your controller\n",[679,132444,132445,132448,132450,132452,132454,132456,132458,132460,132462,132465,132467,132470],{"class":681,"line":901},[679,132446,132447],{"class":693},"model.",[679,132449,10064],{"class":880},[679,132451,745],{"class":693},[679,132453,71966],{"class":689},[679,132455,2797],{"class":693},[679,132457,8930],{"class":685},[679,132459,132430],{"class":880},[679,132461,745],{"class":693},[679,132463,132464],{"class":689},"\"Hello JTE\"",[679,132466,2797],{"class":693},[679,132468,132469],{"class":689},"\"Welcome to JTE\"",[679,132471,1669],{"class":693},[669,132473,132475],{"className":4496,"code":132474,"language":4498,"meta":674,"style":674},"```html\n@param String name\n@param Page page\n\n\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n \u003Ctitle>${page.title()}\u003C/title>\n \u003Cmeta name=\"description\" content=\"${page.description()}\">\n\u003C/head>\n\u003Cbody>\n \u003Ch1>Hello, ${name}!\u003C/h1>\n\u003C/body>\n\u003C/html>\n",[676,132476,132477,132482,132486,132491,132495,132505,132513,132521,132534,132555,132563,132571,132583,132591],{"__ignoreMap":674},[679,132478,132479],{"class":681,"line":682},[679,132480,132481],{"class":693},"```html\n",[679,132483,132484],{"class":681,"line":790},[679,132485,132188],{"class":693},[679,132487,132488],{"class":681,"line":892},[679,132489,132490],{"class":693},"@param Page page\n",[679,132492,132493],{"class":681,"line":901},[679,132494,889],{"emptyLinePlaceholder":797},[679,132496,132497,132499,132501,132503],{"class":681,"line":909},[679,132498,11904],{"class":693},[679,132500,11907],{"class":4508},[679,132502,11910],{"class":880},[679,132504,4519],{"class":693},[679,132506,132507,132509,132511],{"class":681,"line":918},[679,132508,4505],{"class":693},[679,132510,4498],{"class":4508},[679,132512,4519],{"class":693},[679,132514,132515,132517,132519],{"class":681,"line":935},[679,132516,4505],{"class":693},[679,132518,11741],{"class":4508},[679,132520,4519],{"class":693},[679,132522,132523,132525,132527,132530,132532],{"class":681,"line":944},[679,132524,4524],{"class":693},[679,132526,11750],{"class":4508},[679,132528,132529],{"class":693},">${page.title()}\u003C/",[679,132531,11750],{"class":4508},[679,132533,4519],{"class":693},[679,132535,132536,132538,132540,132542,132544,132546,132548,132550,132553],{"class":681,"line":959},[679,132537,4524],{"class":693},[679,132539,11968],{"class":4508},[679,132541,5283],{"class":880},[679,132543,686],{"class":693},[679,132545,47908],{"class":689},[679,132547,11995],{"class":880},[679,132549,686],{"class":693},[679,132551,132552],{"class":689},"\"${page.description()}\"",[679,132554,4519],{"class":693},[679,132556,132557,132559,132561],{"class":681,"line":964},[679,132558,4586],{"class":693},[679,132560,11741],{"class":4508},[679,132562,4519],{"class":693},[679,132564,132565,132567,132569],{"class":681,"line":977},[679,132566,4505],{"class":693},[679,132568,3006],{"class":4508},[679,132570,4519],{"class":693},[679,132572,132573,132575,132577,132579,132581],{"class":681,"line":982},[679,132574,4524],{"class":693},[679,132576,11859],{"class":4508},[679,132578,132256],{"class":693},[679,132580,11859],{"class":4508},[679,132582,4519],{"class":693},[679,132584,132585,132587,132589],{"class":681,"line":988},[679,132586,4586],{"class":693},[679,132588,3006],{"class":4508},[679,132590,4519],{"class":693},[679,132592,132593,132595,132597],{"class":681,"line":993},[679,132594,4586],{"class":693},[679,132596,4498],{"class":4508},[679,132598,4519],{"class":693},[5909,132600,132602],{"id":132601},"iterating-over-collections","Iterating Over Collections",[651,132604,132605],{},"JTE provides a simple syntax for iterating over collections:",[669,132607,132609],{"className":4496,"code":132608,"language":4498,"meta":674,"style":674},"@param List\u003CString> items\n\n\u003Cul>\n@if(items.isEmpty())\n \u003Cli>You have no items\u003C/li>\n@else\n @for(String item : items)\n \u003Cli>${item}\u003C/li>\n @endfor\n@endif\n\u003C/ul>\n",[676,132610,132611,132621,132625,132633,132638,132651,132656,132661,132674,132679,132684],{"__ignoreMap":674},[679,132612,132613,132616,132618],{"class":681,"line":682},[679,132614,132615],{"class":693},"@param List\u003C",[679,132617,4758],{"class":6561},[679,132619,132620],{"class":693},"> items\n",[679,132622,132623],{"class":681,"line":790},[679,132624,889],{"emptyLinePlaceholder":797},[679,132626,132627,132629,132631],{"class":681,"line":892},[679,132628,4505],{"class":693},[679,132630,5316],{"class":4508},[679,132632,4519],{"class":693},[679,132634,132635],{"class":681,"line":901},[679,132636,132637],{"class":693},"@if(items.isEmpty())\n",[679,132639,132640,132642,132644,132647,132649],{"class":681,"line":909},[679,132641,4524],{"class":693},[679,132643,5332],{"class":4508},[679,132645,132646],{"class":693},">You have no items\u003C/",[679,132648,5332],{"class":4508},[679,132650,4519],{"class":693},[679,132652,132653],{"class":681,"line":918},[679,132654,132655],{"class":693},"@else\n",[679,132657,132658],{"class":681,"line":935},[679,132659,132660],{"class":693}," @for(String item : items)\n",[679,132662,132663,132665,132667,132670,132672],{"class":681,"line":944},[679,132664,4904],{"class":693},[679,132666,5332],{"class":4508},[679,132668,132669],{"class":693},">${item}\u003C/",[679,132671,5332],{"class":4508},[679,132673,4519],{"class":693},[679,132675,132676],{"class":681,"line":959},[679,132677,132678],{"class":693}," @endfor\n",[679,132680,132681],{"class":681,"line":964},[679,132682,132683],{"class":693},"@endif\n",[679,132685,132686,132688,132690],{"class":681,"line":977},[679,132687,4586],{"class":693},[679,132689,5316],{"class":4508},[679,132691,4519],{"class":693},[4542,132693,132695],{"id":132694},"jte-vs-other-template-engines","JTE vs Other Template Engines",[651,132697,132698],{},"While template engines like Thymeleaf have been popular choices in the Spring ecosystem, JTE offers some compelling advantages:",[27665,132700,132701,132706,132712,132717],{},[5332,132702,132703,132705],{},[2939,132704,74024],{},": JTE's compilation to bytecode can lead to better performance, especially in high-traffic applications.",[5332,132707,132708,132711],{},[2939,132709,132710],{},"Simpler syntax",": Many developers find JTE's syntax more intuitive and closer to writing Java code.",[5332,132713,132714,132716],{},[2939,132715,132028],{},": Catch more errors before runtime, improving development efficiency.",[5332,132718,132719,132721],{},[2939,132720,132046],{},": See changes immediately without restarting your application, enhancing the development experience.",[4542,132723,9042],{"id":9041},[651,132725,132726],{},"JTE brings a fresh approach to templating in the Spring Boot ecosystem. Its performance benefits, developer-friendly syntax, and seamless integration with Java features make it an attractive option for both new and experienced Spring developers.",[651,132728,132729],{},"As you plan your next Spring Boot project, consider giving JTE a try. Its addition to the Spring Initializer makes it easier than ever to get started, and you might find that it streamlines your development process and improves your application's performance.",[651,132731,132732],{},"Have you used JTE before? Are you excited to try it out in your next project? We'd love to hear your thoughts and experiences in the comments below!",[651,132734,78024],{},[786,132736,132737],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}",{"title":674,"searchDepth":790,"depth":790,"links":132739},[132740,132741,132742,132747,132748],{"id":132009,"depth":790,"text":132010},{"id":132062,"depth":790,"text":132063},{"id":132279,"depth":790,"text":132280,"children":132743},[132744,132745,132746],{"id":132286,"depth":892,"text":132287},{"id":132412,"depth":892,"text":132413},{"id":132601,"depth":892,"text":132602},{"id":132694,"depth":790,"text":132695},{"id":9041,"depth":790,"text":9042},"Explore JTE, the new Java Template Engine now available in Spring Initializer. Learn its benefits, how to get started, and why you might choose it for your next Spring Boot project.",{"slug":132751,"date":132752,"published":797,"author":798,"tags":132753,"video":132754,"keywords":132755},"hello-jte","2024-10-01T20:00:00.000Z",[7077,36422],"https://www.youtube.com/embed/KoWgHSWA1cc","Spring Framework, Spring Initializr, Spring Boot, Spring MVC, Java Template Engine, JTE",{"title":72,"description":132749},"blog/2024/10/01/hello-jte","Y9XenxPrjq4eTyjGpto-yQ-GrZmQgiATyj3cyeDuOZE",{"id":132760,"title":69,"body":132761,"description":133710,"extension":793,"meta":133711,"navigation":797,"path":70,"seo":133717,"stem":133718,"__hash__":133719},"content/blog/2024/10/03/jte-layouts.md",{"type":648,"value":132762,"toc":133700},[132763,132766,132770,132778,132781,132785,132792,132799,132804,132808,132811,132837,133080,133087,133091,133094,133118,133124,133204,133211,133215,133222,133508,133511,133515,133528,133532,133535,133677,133683,133685,133688,133691,133694,133697],[651,132764,132765],{},"Are you a Spring Boot developer looking to level up your templating game? If you've been using Thymeleaf and wondering how to achieve similar functionality with the Java Template Engine (JTE), you're in for a treat. In this tutorial, we'll take a look at layouts in Java Template Engine (JTE), a powerful and efficient templating engine that's gaining traction in the Spring ecosystem.",[4542,132767,132769],{"id":132768},"introduction-to-jte-layouts","Introduction to JTE Layouts",[651,132771,132772,132773,132777],{},"JTE, or Java Template Engine, has been around for a while, but it's recently gained more attention after being added to the Spring Initializer. If you're not familiar with JTE, I recommend checking out my ",[812,132774,112246],{"href":132775,"rel":132776},"https://www.danvega.dev/blog/hello-jte",[816]," for an introduction to this fantastic templating engine.",[651,132779,132780],{},"One of the most common questions I received after that introductory video was about creating layouts and fragments in JTE, similar to what developers are used to in Thymeleaf. Today, we're going to tackle that head-on and show you how to create reusable templates in your JTE-powered Spring Boot applications.",[4542,132782,132784],{"id":132783},"setting-up-your-spring-boot-project","Setting Up Your Spring Boot Project",[651,132786,132787,132788,132791],{},"Let's start by creating a new Spring Boot project. Head over to ",[812,132789,77478],{"href":51358,"rel":132790},[816]," and set up your project with the following dependencies:",[5316,132793,132794,132796],{},[5332,132795,80827],{},[5332,132797,132798],{},"JTE Template Engine",[651,132800,132801],{},[660,132802],{"alt":7117,"src":132803},"/images/blog/2024/10/03/spring_init_jte_layouts.png",[4542,132805,132807],{"id":132806},"creating-a-layout-template","Creating a Layout Template",[651,132809,132810],{},"The first step in our templating journey is to create a main layout that will serve as the wrapper for our application. This layout will contain the common elements that appear on every page, such as the navigation bar and footer.",[27665,132812,132813,132823,132832],{},[5332,132814,132815,132816,132819,132820,664],{},"In your project's ",[676,132817,132818],{},"src/main/jte"," directory, create a new folder called ",[676,132821,132822],{},"layout",[5332,132824,132825,132826,132828,132829,664],{},"Inside the ",[676,132827,132822],{}," folder, create a file named ",[676,132830,132831],{},"main.jte",[5332,132833,132834,132835,2391],{},"Add the following code to ",[676,132836,132831],{},[669,132838,132840],{"className":4496,"code":132839,"language":4498,"meta":674,"style":674},"@param Content content\n\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n \u003Cmeta charset=\"UTF-8\">\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003Ctitle>My JTE Application\u003C/title>\n \u003Cscript src=\"https://cdn.tailwindcss.com\">\u003C/script>\n\u003C/head>\n\u003Cbody class=\"bg-gray-100\">\n \u003Cnav class=\"bg-white shadow\">\n \u003C!-- Navigation content -->\n \u003C/nav>\n\n \u003Cmain class=\"container mx-auto mt-4 p-4\">\n ${content}\n \u003C/main>\n\n \u003Cfooter class=\"bg-white mt-8 py-4\">\n \u003C!-- Footer content -->\n \u003C/footer>\n\u003C/body>\n\u003C/html>\n",[676,132841,132842,132847,132851,132861,132875,132883,132897,132917,132930,132948,132956,132971,132987,132992,133000,133004,133019,133024,133032,133036,133051,133056,133064,133072],{"__ignoreMap":674},[679,132843,132844],{"class":681,"line":682},[679,132845,132846],{"class":693},"@param Content content\n",[679,132848,132849],{"class":681,"line":790},[679,132850,889],{"emptyLinePlaceholder":797},[679,132852,132853,132855,132857,132859],{"class":681,"line":892},[679,132854,11904],{"class":693},[679,132856,11907],{"class":4508},[679,132858,11910],{"class":880},[679,132860,4519],{"class":693},[679,132862,132863,132865,132867,132869,132871,132873],{"class":681,"line":901},[679,132864,4505],{"class":693},[679,132866,4498],{"class":4508},[679,132868,18806],{"class":880},[679,132870,686],{"class":693},[679,132872,57251],{"class":689},[679,132874,4519],{"class":693},[679,132876,132877,132879,132881],{"class":681,"line":909},[679,132878,4505],{"class":693},[679,132880,11741],{"class":4508},[679,132882,4519],{"class":693},[679,132884,132885,132887,132889,132891,132893,132895],{"class":681,"line":918},[679,132886,4524],{"class":693},[679,132888,11968],{"class":4508},[679,132890,11971],{"class":880},[679,132892,686],{"class":693},[679,132894,47958],{"class":689},[679,132896,4519],{"class":693},[679,132898,132899,132901,132903,132905,132907,132909,132911,132913,132915],{"class":681,"line":935},[679,132900,4524],{"class":693},[679,132902,11968],{"class":4508},[679,132904,5283],{"class":880},[679,132906,686],{"class":693},[679,132908,12015],{"class":689},[679,132910,11995],{"class":880},[679,132912,686],{"class":693},[679,132914,48042],{"class":689},[679,132916,4519],{"class":693},[679,132918,132919,132921,132923,132926,132928],{"class":681,"line":944},[679,132920,4524],{"class":693},[679,132922,11750],{"class":4508},[679,132924,132925],{"class":693},">My JTE Application\u003C/",[679,132927,11750],{"class":4508},[679,132929,4519],{"class":693},[679,132931,132932,132934,132936,132938,132940,132942,132944,132946],{"class":681,"line":959},[679,132933,4524],{"class":693},[679,132935,47668],{"class":4508},[679,132937,5361],{"class":880},[679,132939,686],{"class":693},[679,132941,124580],{"class":689},[679,132943,4563],{"class":693},[679,132945,47668],{"class":4508},[679,132947,4519],{"class":693},[679,132949,132950,132952,132954],{"class":681,"line":964},[679,132951,4586],{"class":693},[679,132953,11741],{"class":4508},[679,132955,4519],{"class":693},[679,132957,132958,132960,132962,132964,132966,132969],{"class":681,"line":977},[679,132959,4505],{"class":693},[679,132961,3006],{"class":4508},[679,132963,4512],{"class":880},[679,132965,686],{"class":693},[679,132967,132968],{"class":689},"\"bg-gray-100\"",[679,132970,4519],{"class":693},[679,132972,132973,132975,132978,132980,132982,132985],{"class":681,"line":982},[679,132974,4524],{"class":693},[679,132976,132977],{"class":4508},"nav",[679,132979,4512],{"class":880},[679,132981,686],{"class":693},[679,132983,132984],{"class":689},"\"bg-white shadow\"",[679,132986,4519],{"class":693},[679,132988,132989],{"class":681,"line":988},[679,132990,132991],{"class":1400}," \u003C!-- Navigation content -->\n",[679,132993,132994,132996,132998],{"class":681,"line":993},[679,132995,4577],{"class":693},[679,132997,132977],{"class":4508},[679,132999,4519],{"class":693},[679,133001,133002],{"class":681,"line":2129},[679,133003,889],{"emptyLinePlaceholder":797},[679,133005,133006,133008,133010,133012,133014,133017],{"class":681,"line":2140},[679,133007,4524],{"class":693},[679,133009,77538],{"class":4508},[679,133011,4512],{"class":880},[679,133013,686],{"class":693},[679,133015,133016],{"class":689},"\"container mx-auto mt-4 p-4\"",[679,133018,4519],{"class":693},[679,133020,133021],{"class":681,"line":2145},[679,133022,133023],{"class":693}," ${content}\n",[679,133025,133026,133028,133030],{"class":681,"line":2154},[679,133027,4577],{"class":693},[679,133029,77538],{"class":4508},[679,133031,4519],{"class":693},[679,133033,133034],{"class":681,"line":2159},[679,133035,889],{"emptyLinePlaceholder":797},[679,133037,133038,133040,133042,133044,133046,133049],{"class":681,"line":2164},[679,133039,4524],{"class":693},[679,133041,129375],{"class":4508},[679,133043,4512],{"class":880},[679,133045,686],{"class":693},[679,133047,133048],{"class":689},"\"bg-white mt-8 py-4\"",[679,133050,4519],{"class":693},[679,133052,133053],{"class":681,"line":3134},[679,133054,133055],{"class":1400}," \u003C!-- Footer content -->\n",[679,133057,133058,133060,133062],{"class":681,"line":3139},[679,133059,4577],{"class":693},[679,133061,129375],{"class":4508},[679,133063,4519],{"class":693},[679,133065,133066,133068,133070],{"class":681,"line":3144},[679,133067,4586],{"class":693},[679,133069,3006],{"class":4508},[679,133071,4519],{"class":693},[679,133073,133074,133076,133078],{"class":681,"line":3149},[679,133075,4586],{"class":693},[679,133077,4498],{"class":4508},[679,133079,4519],{"class":693},[651,133081,133082,133083,133086],{},"This layout uses Tailwind CSS for quick styling. The ",[676,133084,133085],{},"${content}"," placeholder is where our page-specific content will be injected.",[4542,133088,133090],{"id":133089},"creating-page-templates","Creating Page Templates",[651,133092,133093],{},"Now that we have our main layout, let's create some page-specific templates:",[27665,133095,133096,133103],{},[5332,133097,77744,133098,133100,133101,81509],{},[676,133099,102939],{}," folder in the ",[676,133102,132818],{},[5332,133104,133105,133106,133108,133109,2797,133112,48406,133115,664],{},"Inside ",[676,133107,102939],{},", create ",[676,133110,133111],{},"home.jte",[676,133113,133114],{},"team.jte",[676,133116,133117],{},"projects.jte",[651,133119,133120,133121,133123],{},"Here's an example of what ",[676,133122,133111],{}," might look like:",[669,133125,133127],{"className":4496,"code":133126,"language":4498,"meta":674,"style":674},"@param String username\n\n@template.layout.main(content = @`\n \u003Cdiv class=\"bg-white shadow rounded p-4\">\n \u003Ch1 class=\"text-2xl font-bold mb-4\">Welcome, ${username}!\u003C/h1>\n \u003Cp>This is your dashboard. Here you can view recent activity and manage your account.\u003C/p>\n \u003C/div>\n`)\n",[676,133128,133129,133134,133138,133143,133158,133178,133191,133199],{"__ignoreMap":674},[679,133130,133131],{"class":681,"line":682},[679,133132,133133],{"class":693},"@param String username\n",[679,133135,133136],{"class":681,"line":790},[679,133137,889],{"emptyLinePlaceholder":797},[679,133139,133140],{"class":681,"line":892},[679,133141,133142],{"class":693},"@template.layout.main(content = @`\n",[679,133144,133145,133147,133149,133151,133153,133156],{"class":681,"line":901},[679,133146,4524],{"class":693},[679,133148,4509],{"class":4508},[679,133150,4512],{"class":880},[679,133152,686],{"class":693},[679,133154,133155],{"class":689},"\"bg-white shadow rounded p-4\"",[679,133157,4519],{"class":693},[679,133159,133160,133162,133164,133166,133168,133171,133174,133176],{"class":681,"line":909},[679,133161,4904],{"class":693},[679,133163,11859],{"class":4508},[679,133165,4512],{"class":880},[679,133167,686],{"class":693},[679,133169,133170],{"class":689},"\"text-2xl font-bold mb-4\"",[679,133172,133173],{"class":693},">Welcome, ${username}!\u003C/",[679,133175,11859],{"class":4508},[679,133177,4519],{"class":693},[679,133179,133180,133182,133184,133187,133189],{"class":681,"line":918},[679,133181,4904],{"class":693},[679,133183,651],{"class":4508},[679,133185,133186],{"class":693},">This is your dashboard. Here you can view recent activity and manage your account.\u003C/",[679,133188,651],{"class":4508},[679,133190,4519],{"class":693},[679,133192,133193,133195,133197],{"class":681,"line":935},[679,133194,4577],{"class":693},[679,133196,4509],{"class":4508},[679,133198,4519],{"class":693},[679,133200,133201],{"class":681,"line":944},[679,133202,133203],{"class":693},"`)\n",[651,133205,133206,133207,133210],{},"Notice how we're using the ",[676,133208,133209],{},"@template.layout.main"," to wrap our content with the main layout.",[4542,133212,133214],{"id":133213},"implementing-the-controller","Implementing the Controller",[651,133216,133217,133218,133221],{},"To tie everything together, we need a controller to handle the routing and pass data to our templates. Create a new class called ",[676,133219,133220],{},"TemplateController"," in your main package:",[669,133223,133225],{"className":4107,"code":133224,"language":4109,"meta":674,"style":674},"@Controller\npublic class TemplateController {\n\n @GetMapping(\"/\")\n public String home(Model model) {\n model.addAttribute(\"username\", \"John Doe\");\n return \"pages/home\";\n }\n\n @GetMapping(\"/team\")\n public String team(Model model) {\n List\u003CString> teamMembers = List.of(\"Alice\", \"Bob\", \"Charlie\", \"David\");\n model.addAttribute(\"teamMembers\", teamMembers);\n return \"pages/team\";\n }\n\n @GetMapping(\"/projects\")\n public String projects(Model model) {\n model.addAttribute(\"username\", \"John Doe\");\n List\u003CString> projects = List.of(\"Project A\", \"Project B\", \"Project C\");\n model.addAttribute(\"projects\", projects);\n return \"pages/projects\";\n }\n}\n",[676,133226,133227,133233,133244,133248,133260,133274,133292,133301,133305,133309,133322,133337,133370,133384,133393,133397,133401,133414,133429,133445,133477,133491,133500,133504],{"__ignoreMap":674},[679,133228,133229,133231],{"class":681,"line":682},[679,133230,4116],{"class":693},[679,133232,9942],{"class":685},[679,133234,133235,133237,133239,133242],{"class":681,"line":790},[679,133236,6073],{"class":685},[679,133238,4512],{"class":685},[679,133240,133241],{"class":880}," TemplateController",[679,133243,884],{"class":693},[679,133245,133246],{"class":681,"line":892},[679,133247,889],{"emptyLinePlaceholder":797},[679,133249,133250,133252,133254,133256,133258],{"class":681,"line":901},[679,133251,6872],{"class":693},[679,133253,20852],{"class":685},[679,133255,745],{"class":693},[679,133257,10032],{"class":689},[679,133259,1339],{"class":693},[679,133261,133262,133264,133266,133268,133270,133272],{"class":681,"line":909},[679,133263,6089],{"class":685},[679,133265,9289],{"class":693},[679,133267,12642],{"class":880},[679,133269,10045],{"class":693},[679,133271,10048],{"class":2099},[679,133273,4390],{"class":693},[679,133275,133276,133278,133280,133282,133285,133287,133290],{"class":681,"line":918},[679,133277,10061],{"class":693},[679,133279,10064],{"class":880},[679,133281,745],{"class":693},[679,133283,133284],{"class":689},"\"username\"",[679,133286,2797],{"class":693},[679,133288,133289],{"class":689},"\"John Doe\"",[679,133291,1208],{"class":693},[679,133293,133294,133296,133299],{"class":681,"line":935},[679,133295,9444],{"class":685},[679,133297,133298],{"class":689}," \"pages/home\"",[679,133300,1186],{"class":693},[679,133302,133303],{"class":681,"line":944},[679,133304,985],{"class":693},[679,133306,133307],{"class":681,"line":959},[679,133308,889],{"emptyLinePlaceholder":797},[679,133310,133311,133313,133315,133317,133320],{"class":681,"line":964},[679,133312,6872],{"class":693},[679,133314,20852],{"class":685},[679,133316,745],{"class":693},[679,133318,133319],{"class":689},"\"/team\"",[679,133321,1339],{"class":693},[679,133323,133324,133326,133328,133331,133333,133335],{"class":681,"line":977},[679,133325,6089],{"class":685},[679,133327,9289],{"class":693},[679,133329,133330],{"class":880},"team",[679,133332,10045],{"class":693},[679,133334,10048],{"class":2099},[679,133336,4390],{"class":693},[679,133338,133339,133341,133343,133346,133348,133350,133352,133354,133356,133358,133360,133362,133364,133366,133368],{"class":681,"line":982},[679,133340,16659],{"class":693},[679,133342,4758],{"class":685},[679,133344,133345],{"class":693},"> teamMembers ",[679,133347,686],{"class":685},[679,133349,16669],{"class":693},[679,133351,16672],{"class":880},[679,133353,745],{"class":693},[679,133355,126291],{"class":689},[679,133357,2797],{"class":693},[679,133359,126296],{"class":689},[679,133361,2797],{"class":693},[679,133363,126301],{"class":689},[679,133365,2797],{"class":693},[679,133367,51614],{"class":689},[679,133369,1208],{"class":693},[679,133371,133372,133374,133376,133378,133381],{"class":681,"line":988},[679,133373,10061],{"class":693},[679,133375,10064],{"class":880},[679,133377,745],{"class":693},[679,133379,133380],{"class":689},"\"teamMembers\"",[679,133382,133383],{"class":693},", teamMembers);\n",[679,133385,133386,133388,133391],{"class":681,"line":993},[679,133387,9444],{"class":685},[679,133389,133390],{"class":689}," \"pages/team\"",[679,133392,1186],{"class":693},[679,133394,133395],{"class":681,"line":2129},[679,133396,985],{"class":693},[679,133398,133399],{"class":681,"line":2140},[679,133400,889],{"emptyLinePlaceholder":797},[679,133402,133403,133405,133407,133409,133412],{"class":681,"line":2145},[679,133404,6872],{"class":693},[679,133406,20852],{"class":685},[679,133408,745],{"class":693},[679,133410,133411],{"class":689},"\"/projects\"",[679,133413,1339],{"class":693},[679,133415,133416,133418,133420,133423,133425,133427],{"class":681,"line":2154},[679,133417,6089],{"class":685},[679,133419,9289],{"class":693},[679,133421,133422],{"class":880},"projects",[679,133424,10045],{"class":693},[679,133426,10048],{"class":2099},[679,133428,4390],{"class":693},[679,133430,133431,133433,133435,133437,133439,133441,133443],{"class":681,"line":2159},[679,133432,10061],{"class":693},[679,133434,10064],{"class":880},[679,133436,745],{"class":693},[679,133438,133284],{"class":689},[679,133440,2797],{"class":693},[679,133442,133289],{"class":689},[679,133444,1208],{"class":693},[679,133446,133447,133449,133451,133454,133456,133458,133460,133462,133465,133467,133470,133472,133475],{"class":681,"line":2164},[679,133448,16659],{"class":693},[679,133450,4758],{"class":685},[679,133452,133453],{"class":693},"> projects ",[679,133455,686],{"class":685},[679,133457,16669],{"class":693},[679,133459,16672],{"class":880},[679,133461,745],{"class":693},[679,133463,133464],{"class":689},"\"Project A\"",[679,133466,2797],{"class":693},[679,133468,133469],{"class":689},"\"Project B\"",[679,133471,2797],{"class":693},[679,133473,133474],{"class":689},"\"Project C\"",[679,133476,1208],{"class":693},[679,133478,133479,133481,133483,133485,133488],{"class":681,"line":3134},[679,133480,10061],{"class":693},[679,133482,10064],{"class":880},[679,133484,745],{"class":693},[679,133486,133487],{"class":689},"\"projects\"",[679,133489,133490],{"class":693},", projects);\n",[679,133492,133493,133495,133498],{"class":681,"line":3139},[679,133494,9444],{"class":685},[679,133496,133497],{"class":689}," \"pages/projects\"",[679,133499,1186],{"class":693},[679,133501,133502],{"class":681,"line":3144},[679,133503,985],{"class":693},[679,133505,133506],{"class":681,"line":3149},[679,133507,996],{"class":693},[651,133509,133510],{},"This controller sets up routes for our home, team, and projects pages, passing the necessary data to each template.",[4542,133512,133514],{"id":133513},"running-and-testing-your-application","Running and Testing Your Application",[651,133516,133517,133518,133520,133521,23212,133524,133527],{},"With everything in place, you can now run your Spring Boot application. Visit ",[676,133519,11697],{}," in your browser, and you should see your homepage with the main layout applied. Navigate to ",[676,133522,133523],{},"/team",[676,133525,133526],{},"/projects"," to see how the content changes while the layout remains consistent.",[4542,133529,133531],{"id":133530},"advanced-template-techniques","Advanced Template Techniques",[651,133533,133534],{},"JTE offers more advanced techniques for template reuse and composition:",[27665,133536,133537,133556],{},[5332,133538,133539,133542,133543,133546,133547],{},[2939,133540,133541],{},"Partial Templates",": You can create smaller, reusable template snippets (often called partials) and include them in your pages using the ",[676,133544,133545],{},"@template"," tag:",[669,133548,133550],{"className":4496,"code":133549,"language":4498,"meta":674,"style":674},"@template.partials.header(title = \"My Page Title\")\n",[676,133551,133552],{"__ignoreMap":674},[679,133553,133554],{"class":681,"line":682},[679,133555,133549],{"class":693},[5332,133557,133558,133561,133562],{},[2939,133559,133560],{},"Passing Content Blocks",": You can pass entire blocks of content to your templates, allowing for more complex layouts:",[669,133563,133565],{"className":4496,"code":133564,"language":4498,"meta":674,"style":674},"@template.layout.main(\n content = @`\n \u003Ch1>Main Content\u003C/h1>\n \u003Cp>This is the main content of the page.\u003C/p>\n `,\n sidebar = @`\n \u003Ch2>Sidebar\u003C/h2>\n \u003Cul>\n \u003Cli>Link 1\u003C/li>\n \u003Cli>Link 2\u003C/li>\n \u003C/ul>\n `\n)\n",[676,133566,133567,133572,133577,133590,133603,133608,133613,133626,133634,133647,133660,133668,133673],{"__ignoreMap":674},[679,133568,133569],{"class":681,"line":682},[679,133570,133571],{"class":693},"@template.layout.main(\n",[679,133573,133574],{"class":681,"line":790},[679,133575,133576],{"class":693}," content = @`\n",[679,133578,133579,133581,133583,133586,133588],{"class":681,"line":892},[679,133580,4904],{"class":693},[679,133582,11859],{"class":4508},[679,133584,133585],{"class":693},">Main Content\u003C/",[679,133587,11859],{"class":4508},[679,133589,4519],{"class":693},[679,133591,133592,133594,133596,133599,133601],{"class":681,"line":901},[679,133593,4904],{"class":693},[679,133595,651],{"class":4508},[679,133597,133598],{"class":693},">This is the main content of the page.\u003C/",[679,133600,651],{"class":4508},[679,133602,4519],{"class":693},[679,133604,133605],{"class":681,"line":909},[679,133606,133607],{"class":693}," `,\n",[679,133609,133610],{"class":681,"line":918},[679,133611,133612],{"class":693}," sidebar = @`\n",[679,133614,133615,133617,133619,133622,133624],{"class":681,"line":935},[679,133616,4904],{"class":693},[679,133618,4542],{"class":4508},[679,133620,133621],{"class":693},">Sidebar\u003C/",[679,133623,4542],{"class":4508},[679,133625,4519],{"class":693},[679,133627,133628,133630,133632],{"class":681,"line":944},[679,133629,4904],{"class":693},[679,133631,5316],{"class":4508},[679,133633,4519],{"class":693},[679,133635,133636,133638,133640,133643,133645],{"class":681,"line":959},[679,133637,5392],{"class":693},[679,133639,5332],{"class":4508},[679,133641,133642],{"class":693},">Link 1\u003C/",[679,133644,5332],{"class":4508},[679,133646,4519],{"class":693},[679,133648,133649,133651,133653,133656,133658],{"class":681,"line":964},[679,133650,5392],{"class":693},[679,133652,5332],{"class":4508},[679,133654,133655],{"class":693},">Link 2\u003C/",[679,133657,5332],{"class":4508},[679,133659,4519],{"class":693},[679,133661,133662,133664,133666],{"class":681,"line":977},[679,133663,5480],{"class":693},[679,133665,5316],{"class":4508},[679,133667,4519],{"class":693},[679,133669,133670],{"class":681,"line":982},[679,133671,133672],{"class":693}," `\n",[679,133674,133675],{"class":681,"line":988},[679,133676,1339],{"class":693},[651,133678,133679],{},[812,133680,89158],{"href":133681,"rel":133682},"https://github.com/danvega/jte-templates",[816],[4542,133684,9042],{"id":9041},[651,133686,133687],{},"Templates in JTE offer a powerful and flexible way to structure your Spring Boot applications. By separating your layout from your page-specific content, you can create more maintainable and DRY (Don't Repeat Yourself) code.",[651,133689,133690],{},"As you become more comfortable with JTE, you'll find that it offers a clean and intuitive syntax for creating complex layouts and reusable components. Its integration with Spring Boot makes it an excellent choice for developers looking for a modern, efficient templating solution.",[651,133692,133693],{},"Ready to take your JTE skills to the next level? Stay tuned for our upcoming tutorial on integrating JTE with HTMX, where we'll explore even more powerful ways to create dynamic web applications with Spring Boot.",[651,133695,133696],{},"Happy coding, and may your templates always be clean and your layouts always responsive!",[786,133698,133699],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":133701},[133702,133703,133704,133705,133706,133707,133708,133709],{"id":132768,"depth":790,"text":132769},{"id":132783,"depth":790,"text":132784},{"id":132806,"depth":790,"text":132807},{"id":133089,"depth":790,"text":133090},{"id":133213,"depth":790,"text":133214},{"id":133513,"depth":790,"text":133514},{"id":133530,"depth":790,"text":133531},{"id":9041,"depth":790,"text":9042},"Learn how to use layouts with Java Template Engine in your next Spring Boot Application",{"slug":133712,"date":133713,"published":797,"author":798,"tags":133714,"video":133715,"keywords":133716,"github":133681},"jte-layouts","2024-10-03T20:00:00.000Z",[7077,36422],"https://www.youtube.com/embed/dWe-C3-YQEg","Spring Framework, Spring Initializr, Spring Boot, Spring MVC, Java Template Engine, JTE, HTMX, Spring Boot htmx",{"title":69,"description":133710},"blog/2024/10/03/jte-layouts","8dW35KJ9-TQ8uXECNgPCQSKUBBQ1rb78dldXePop6mQ",{"id":133721,"title":66,"body":133722,"description":135262,"extension":793,"meta":135263,"navigation":797,"path":67,"seo":135268,"stem":135269,"__hash__":135270},"content/blog/2024/10/06/spring-boot-jte-htmx.md",{"type":648,"value":133723,"toc":135250},[133724,133727,133731,133734,133753,133756,133758,133761,133809,133812,133816,133820,133825,133922,133926,133932,134173,134177,134183,134497,134501,134507,135015,135021,135164,135168,135171,135213,135216,135218,135221,135241,135244,135247],[651,133725,133726],{},"In the ever-evolving world of web development, staying current with the latest tools and techniques is crucial. Today, we're diving into an exciting combination: Spring Boot, Java Template Engine (JTE), and HTMX. This powerful trio allows us to create dynamic, responsive web applications with minimal JavaScript. Let's explore how to build a simple task manager that showcases the strengths of this tech stack.",[4542,133728,133730],{"id":133729},"why-jte-and-htmx-in-spring-boot","Why JTE and HTMX in Spring Boot?",[651,133732,133733],{},"Before we dive in, let's briefly touch on why this combination is worth your attention:",[5316,133735,133736,133742,133748],{},[5332,133737,133738,133741],{},[2939,133739,133740],{},"JTE (Java Template Engine)",": A lightweight, high-performance templating engine for Java that's now available directly from the Spring Initializer.",[5332,133743,133744,133747],{},[2939,133745,133746],{},"HTMX",": A library that allows you to access AJAX, CSS Transitions, WebSockets, and Server Sent Events directly in HTML, without writing JavaScript.",[5332,133749,133750,133752],{},[2939,133751,7077],{},": Our trusted framework for building production-ready applications quickly and easily.",[651,133754,133755],{},"This combination allows for a clean separation of concerns, rapid development, and a fantastic developer experience.",[4542,133757,94281],{"id":94280},[651,133759,133760],{},"Let's start by creating a new Spring Boot project:",[27665,133762,133763,133768,133800],{},[5332,133764,77474,133765],{},[812,133766,77478],{"href":51358,"rel":133767},[816],[5332,133769,133770,133771],{},"Choose the following options:\n",[5316,133772,133773,133776,133778,133781,133786,133792,133795,133797],{},[5332,133774,133775],{},"Project: Maven",[5332,133777,102049],{},[5332,133779,133780],{},"Spring Boot: 3.x (latest stable version)",[5332,133782,133783,133784],{},"Group: ",[676,133785,92925],{},[5332,133787,133788,133789],{},"Artifact: ",[676,133790,133791],{},"tasks",[5332,133793,133794],{},"Description: Spring Boot JTE and HTMX demo",[5332,133796,102052],{},[5332,133798,133799],{},"Java: 17 or later",[5332,133801,133802,133803],{},"Add dependencies:\n",[5316,133804,133805,133807],{},[5332,133806,80827],{},[5332,133808,132798],{},[651,133810,133811],{},"Generate the project, download the zip file, and open it in your favorite IDE.",[4542,133813,133815],{"id":133814},"building-the-task-manager","Building the Task Manager",[5909,133817,133819],{"id":133818},"the-data-model","The Data Model",[651,133821,133822,133823,72934],{},"First, let's create a simple ",[676,133824,20878],{},[669,133826,133828],{"className":4107,"code":133827,"language":4109,"meta":674,"style":674},"public class Task {\n private final String id;\n private final String description;\n\n public Task(String description) {\n this.id = UUID.randomUUID().toString();\n this.description = description;\n }\n\n // Getters omitted for brevity\n}\n",[676,133829,133830,133840,133849,133857,133861,133873,133893,133905,133909,133913,133918],{"__ignoreMap":674},[679,133831,133832,133834,133836,133838],{"class":681,"line":682},[679,133833,6073],{"class":685},[679,133835,4512],{"class":685},[679,133837,21014],{"class":880},[679,133839,884],{"class":693},[679,133841,133842,133844,133846],{"class":681,"line":790},[679,133843,9232],{"class":685},[679,133845,12768],{"class":685},[679,133847,133848],{"class":693}," String id;\n",[679,133850,133851,133853,133855],{"class":681,"line":892},[679,133852,9232],{"class":685},[679,133854,12768],{"class":685},[679,133856,112368],{"class":693},[679,133858,133859],{"class":681,"line":901},[679,133860,889],{"emptyLinePlaceholder":797},[679,133862,133863,133865,133867,133869,133871],{"class":681,"line":909},[679,133864,6089],{"class":685},[679,133866,21014],{"class":880},[679,133868,11400],{"class":693},[679,133870,47867],{"class":2099},[679,133872,4390],{"class":693},[679,133874,133875,133877,133879,133881,133884,133887,133889,133891],{"class":681,"line":918},[679,133876,7862],{"class":931},[679,133878,11350],{"class":693},[679,133880,686],{"class":685},[679,133882,133883],{"class":693}," UUID.",[679,133885,133886],{"class":880},"randomUUID",[679,133888,10541],{"class":693},[679,133890,14391],{"class":880},[679,133892,9317],{"class":693},[679,133894,133895,133897,133900,133902],{"class":681,"line":935},[679,133896,7862],{"class":931},[679,133898,133899],{"class":693},".description ",[679,133901,686],{"class":685},[679,133903,133904],{"class":693}," description;\n",[679,133906,133907],{"class":681,"line":944},[679,133908,985],{"class":693},[679,133910,133911],{"class":681,"line":959},[679,133912,889],{"emptyLinePlaceholder":797},[679,133914,133915],{"class":681,"line":964},[679,133916,133917],{"class":1400}," // Getters omitted for brevity\n",[679,133919,133920],{"class":681,"line":977},[679,133921,996],{"class":693},[5909,133923,133925],{"id":133924},"the-repository","The Repository",[651,133927,114942,133928,133931],{},[676,133929,133930],{},"TaskRepository"," to manage our tasks:",[669,133933,133935],{"className":4107,"code":133934,"language":4109,"meta":674,"style":674},"@Repository\npublic class TaskRepository {\n private final List\u003CTask> tasks = new ArrayList\u003C>();\n\n public List\u003CTask> findAll() {\n return tasks;\n }\n\n public void create(Task task) {\n tasks.add(task);\n }\n\n public boolean remove(String id) {\n return tasks.removeIf(task -> task.getId().equals(id));\n }\n\n @PostConstruct\n private void init() {\n // Add some initial tasks\n tasks.add(new Task(\"Plan the next sprint\"));\n tasks.add(new Task(\"Review pull requests\"));\n tasks.add(new Task(\"Update documentation\"));\n }\n}\n",[676,133936,133937,133943,133954,133973,133977,133991,133998,134002,134006,134021,134031,134035,134039,134054,134079,134083,134087,134093,134103,134108,134127,134146,134165,134169],{"__ignoreMap":674},[679,133938,133939,133941],{"class":681,"line":682},[679,133940,4116],{"class":693},[679,133942,87182],{"class":685},[679,133944,133945,133947,133949,133952],{"class":681,"line":790},[679,133946,6073],{"class":685},[679,133948,4512],{"class":685},[679,133950,133951],{"class":880}," TaskRepository",[679,133953,884],{"class":693},[679,133955,133956,133958,133960,133962,133964,133967,133969,133971],{"class":681,"line":892},[679,133957,9232],{"class":685},[679,133959,12768],{"class":685},[679,133961,87217],{"class":693},[679,133963,20878],{"class":685},[679,133965,133966],{"class":693},"> tasks ",[679,133968,686],{"class":685},[679,133970,2054],{"class":685},[679,133972,87229],{"class":693},[679,133974,133975],{"class":681,"line":901},[679,133976,889],{"emptyLinePlaceholder":797},[679,133978,133979,133981,133983,133985,133987,133989],{"class":681,"line":909},[679,133980,6089],{"class":685},[679,133982,87217],{"class":693},[679,133984,20878],{"class":685},[679,133986,20881],{"class":693},[679,133988,34142],{"class":880},[679,133990,2667],{"class":693},[679,133992,133993,133995],{"class":681,"line":918},[679,133994,9444],{"class":685},[679,133996,133997],{"class":693}," tasks;\n",[679,133999,134000],{"class":681,"line":935},[679,134001,985],{"class":693},[679,134003,134004],{"class":681,"line":944},[679,134005,889],{"emptyLinePlaceholder":797},[679,134007,134008,134010,134012,134014,134017,134019],{"class":681,"line":959},[679,134009,6089],{"class":685},[679,134011,6095],{"class":685},[679,134013,49468],{"class":880},[679,134015,134016],{"class":693},"(Task ",[679,134018,21434],{"class":2099},[679,134020,4390],{"class":693},[679,134022,134023,134026,134028],{"class":681,"line":964},[679,134024,134025],{"class":693}," tasks.",[679,134027,12952],{"class":880},[679,134029,134030],{"class":693},"(task);\n",[679,134032,134033],{"class":681,"line":977},[679,134034,985],{"class":693},[679,134036,134037],{"class":681,"line":982},[679,134038,889],{"emptyLinePlaceholder":797},[679,134040,134041,134043,134045,134048,134050,134052],{"class":681,"line":988},[679,134042,6089],{"class":685},[679,134044,14493],{"class":685},[679,134046,134047],{"class":880}," remove",[679,134049,11400],{"class":693},[679,134051,11341],{"class":2099},[679,134053,4390],{"class":693},[679,134055,134056,134058,134061,134063,134066,134068,134071,134073,134075,134077],{"class":681,"line":993},[679,134057,9444],{"class":685},[679,134059,134060],{"class":693}," tasks.",[679,134062,92291],{"class":880},[679,134064,134065],{"class":693},"(task ",[679,134067,16955],{"class":685},[679,134069,134070],{"class":693}," task.",[679,134072,11309],{"class":880},[679,134074,10541],{"class":693},[679,134076,14592],{"class":880},[679,134078,92307],{"class":693},[679,134080,134081],{"class":681,"line":2129},[679,134082,985],{"class":693},[679,134084,134085],{"class":681,"line":2140},[679,134086,889],{"emptyLinePlaceholder":797},[679,134088,134089,134091],{"class":681,"line":2145},[679,134090,6872],{"class":693},[679,134092,87391],{"class":685},[679,134094,134095,134097,134099,134101],{"class":681,"line":2154},[679,134096,9232],{"class":685},[679,134098,6095],{"class":685},[679,134100,36742],{"class":880},[679,134102,2667],{"class":693},[679,134104,134105],{"class":681,"line":2159},[679,134106,134107],{"class":1400}," // Add some initial tasks\n",[679,134109,134110,134112,134114,134116,134118,134120,134122,134125],{"class":681,"line":2164},[679,134111,134025],{"class":693},[679,134113,12952],{"class":880},[679,134115,745],{"class":693},[679,134117,8930],{"class":685},[679,134119,21014],{"class":880},[679,134121,745],{"class":693},[679,134123,134124],{"class":689},"\"Plan the next sprint\"",[679,134126,1669],{"class":693},[679,134128,134129,134131,134133,134135,134137,134139,134141,134144],{"class":681,"line":3134},[679,134130,134025],{"class":693},[679,134132,12952],{"class":880},[679,134134,745],{"class":693},[679,134136,8930],{"class":685},[679,134138,21014],{"class":880},[679,134140,745],{"class":693},[679,134142,134143],{"class":689},"\"Review pull requests\"",[679,134145,1669],{"class":693},[679,134147,134148,134150,134152,134154,134156,134158,134160,134163],{"class":681,"line":3139},[679,134149,134025],{"class":693},[679,134151,12952],{"class":880},[679,134153,745],{"class":693},[679,134155,8930],{"class":685},[679,134157,21014],{"class":880},[679,134159,745],{"class":693},[679,134161,134162],{"class":689},"\"Update documentation\"",[679,134164,1669],{"class":693},[679,134166,134167],{"class":681,"line":3144},[679,134168,985],{"class":693},[679,134170,134171],{"class":681,"line":3149},[679,134172,996],{"class":693},[5909,134174,134176],{"id":134175},"the-controller","The Controller",[651,134178,134179,134180,2391],{},"Now, let's create our ",[676,134181,134182],{},"TaskController",[669,134184,134186],{"className":4107,"code":134185,"language":4109,"meta":674,"style":674},"@Controller\npublic class TaskController {\n private static final Logger log = LoggerFactory.getLogger(TaskController.class);\n private final TaskRepository repository;\n\n public TaskController(TaskRepository repository) {\n this.repository = repository;\n }\n\n @GetMapping(\"/\")\n public String index(Model model) {\n model.addAttribute(\"tasks\", repository.findAll());\n return \"index\";\n }\n\n @PostMapping(\"/add-task\")\n public String addTask(@RequestParam String description, Model model) {\n Task newTask = new Task(description);\n repository.create(newTask);\n model.addAttribute(\"task\", newTask);\n return \"task-row\";\n }\n\n @DeleteMapping(\"/delete-task/{id}\")\n public void deleteTask(@PathVariable String id) {\n boolean removed = repository.remove(id);\n if (removed) {\n log.info(\"Task with ID {} was deleted\", id);\n }\n }\n}\n",[676,134187,134188,134194,134204,134223,134232,134236,134249,134259,134263,134267,134279,134293,134311,134319,134323,134327,134340,134363,134377,134385,134399,134408,134412,134416,134429,134448,134464,134471,134485,134489,134493],{"__ignoreMap":674},[679,134189,134190,134192],{"class":681,"line":682},[679,134191,4116],{"class":693},[679,134193,9942],{"class":685},[679,134195,134196,134198,134200,134202],{"class":681,"line":790},[679,134197,6073],{"class":685},[679,134199,4512],{"class":685},[679,134201,20794],{"class":880},[679,134203,884],{"class":693},[679,134205,134206,134208,134210,134212,134214,134216,134218,134220],{"class":681,"line":892},[679,134207,9232],{"class":685},[679,134209,6092],{"class":685},[679,134211,12768],{"class":685},[679,134213,111111],{"class":693},[679,134215,686],{"class":685},[679,134217,9240],{"class":693},[679,134219,9243],{"class":880},[679,134221,134222],{"class":693},"(TaskController.class);\n",[679,134224,134225,134227,134229],{"class":681,"line":901},[679,134226,9232],{"class":685},[679,134228,12768],{"class":685},[679,134230,134231],{"class":693}," TaskRepository repository;\n",[679,134233,134234],{"class":681,"line":909},[679,134235,889],{"emptyLinePlaceholder":797},[679,134237,134238,134240,134242,134245,134247],{"class":681,"line":918},[679,134239,6089],{"class":685},[679,134241,20794],{"class":880},[679,134243,134244],{"class":693},"(TaskRepository ",[679,134246,16596],{"class":2099},[679,134248,4390],{"class":693},[679,134250,134251,134253,134255,134257],{"class":681,"line":935},[679,134252,7862],{"class":931},[679,134254,16605],{"class":693},[679,134256,686],{"class":685},[679,134258,16610],{"class":693},[679,134260,134261],{"class":681,"line":944},[679,134262,985],{"class":693},[679,134264,134265],{"class":681,"line":959},[679,134266,889],{"emptyLinePlaceholder":797},[679,134268,134269,134271,134273,134275,134277],{"class":681,"line":964},[679,134270,6872],{"class":693},[679,134272,20852],{"class":685},[679,134274,745],{"class":693},[679,134276,10032],{"class":689},[679,134278,1339],{"class":693},[679,134280,134281,134283,134285,134287,134289,134291],{"class":681,"line":977},[679,134282,6089],{"class":685},[679,134284,9289],{"class":693},[679,134286,4180],{"class":880},[679,134288,10045],{"class":693},[679,134290,10048],{"class":2099},[679,134292,4390],{"class":693},[679,134294,134295,134297,134299,134301,134304,134307,134309],{"class":681,"line":982},[679,134296,10061],{"class":693},[679,134298,10064],{"class":880},[679,134300,745],{"class":693},[679,134302,134303],{"class":689},"\"tasks\"",[679,134305,134306],{"class":693},", repository.",[679,134308,34142],{"class":880},[679,134310,9431],{"class":693},[679,134312,134313,134315,134317],{"class":681,"line":988},[679,134314,9444],{"class":685},[679,134316,18769],{"class":689},[679,134318,1186],{"class":693},[679,134320,134321],{"class":681,"line":993},[679,134322,985],{"class":693},[679,134324,134325],{"class":681,"line":2129},[679,134326,889],{"emptyLinePlaceholder":797},[679,134328,134329,134331,134333,134335,134338],{"class":681,"line":2140},[679,134330,6872],{"class":693},[679,134332,91165],{"class":685},[679,134334,745],{"class":693},[679,134336,134337],{"class":689},"\"/add-task\"",[679,134339,1339],{"class":693},[679,134341,134342,134344,134346,134348,134350,134352,134354,134356,134359,134361],{"class":681,"line":2145},[679,134343,6089],{"class":685},[679,134345,9289],{"class":693},[679,134347,27831],{"class":880},[679,134349,73246],{"class":693},[679,134351,73249],{"class":685},[679,134353,9289],{"class":693},[679,134355,47867],{"class":2099},[679,134357,134358],{"class":693},", Model ",[679,134360,10048],{"class":2099},[679,134362,4390],{"class":693},[679,134364,134365,134368,134370,134372,134374],{"class":681,"line":2154},[679,134366,134367],{"class":693}," Task newTask ",[679,134369,686],{"class":685},[679,134371,2054],{"class":685},[679,134373,21014],{"class":880},[679,134375,134376],{"class":693},"(description);\n",[679,134378,134379,134381,134383],{"class":681,"line":2159},[679,134380,16713],{"class":693},[679,134382,5697],{"class":880},[679,134384,27875],{"class":693},[679,134386,134387,134389,134391,134393,134396],{"class":681,"line":2164},[679,134388,10061],{"class":693},[679,134390,10064],{"class":880},[679,134392,745],{"class":693},[679,134394,134395],{"class":689},"\"task\"",[679,134397,134398],{"class":693},", newTask);\n",[679,134400,134401,134403,134406],{"class":681,"line":3134},[679,134402,9444],{"class":685},[679,134404,134405],{"class":689}," \"task-row\"",[679,134407,1186],{"class":693},[679,134409,134410],{"class":681,"line":3139},[679,134411,985],{"class":693},[679,134413,134414],{"class":681,"line":3144},[679,134415,889],{"emptyLinePlaceholder":797},[679,134417,134418,134420,134422,134424,134427],{"class":681,"line":3149},[679,134419,6872],{"class":693},[679,134421,92256],{"class":685},[679,134423,745],{"class":693},[679,134425,134426],{"class":689},"\"/delete-task/{id}\"",[679,134428,1339],{"class":693},[679,134430,134431,134433,134435,134438,134440,134442,134444,134446],{"class":681,"line":3169},[679,134432,6089],{"class":685},[679,134434,6095],{"class":685},[679,134436,134437],{"class":880}," deleteTask",[679,134439,73246],{"class":693},[679,134441,92277],{"class":685},[679,134443,9289],{"class":693},[679,134445,11341],{"class":2099},[679,134447,4390],{"class":693},[679,134449,134450,134453,134456,134458,134460,134462],{"class":681,"line":3185},[679,134451,134452],{"class":685}," boolean",[679,134454,134455],{"class":693}," removed ",[679,134457,686],{"class":685},[679,134459,85606],{"class":693},[679,134461,47585],{"class":880},[679,134463,88781],{"class":693},[679,134465,134466,134468],{"class":681,"line":3194},[679,134467,1249],{"class":685},[679,134469,134470],{"class":693}," (removed) {\n",[679,134472,134473,134475,134477,134479,134482],{"class":681,"line":3199},[679,134474,104907],{"class":693},[679,134476,9415],{"class":880},[679,134478,745],{"class":693},[679,134480,134481],{"class":689},"\"Task with ID {} was deleted\"",[679,134483,134484],{"class":693},", id);\n",[679,134486,134487],{"class":681,"line":3212},[679,134488,1290],{"class":693},[679,134490,134491],{"class":681,"line":3217},[679,134492,985],{"class":693},[679,134494,134495],{"class":681,"line":3222},[679,134496,996],{"class":693},[5909,134498,134500],{"id":134499},"jte-templates","JTE Templates",[651,134502,134503,134504,2391],{},"Now, let's create our JTE templates. First, ",[676,134505,134506],{},"index.jte",[669,134508,134510],{"className":4496,"code":134509,"language":4498,"meta":674,"style":674},"@import dev.danvega.tasks.model.Task\n@param List\u003CTask> tasks\n\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n \u003Cmeta charset=\"UTF-8\">\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003Ctitle>Task Manager\u003C/title>\n \u003Cscript src=\"https://unpkg.com/htmx.org@1.9.6\">\u003C/script>\n \u003Cscript src=\"https://cdn.tailwindcss.com\">\u003C/script>\n\u003C/head>\n\u003Cbody class=\"bg-gray-100 p-8\">\n \u003Cdiv class=\"max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl\">\n \u003Cdiv class=\"p-8\">\n \u003Cdiv class=\"uppercase tracking-wide text-sm text-indigo-500 font-semibold mb-1\">Task Manager\u003C/div>\n \u003Cp class=\"block mt-1 text-lg leading-tight font-medium text-black\">List of all your tasks\u003C/p>\n \u003Cform class=\"mt-4\" hx-post=\"/add-task\" hx-target=\"#task-table-body\" hx-swap=\"beforeend\" hx-on::after-request=\"this.reset()\">\n \u003Cinput type=\"text\" name=\"description\" placeholder=\"Enter new task\" class=\"w-full p-2 border rounded\">\n \u003Cbutton type=\"submit\" class=\"mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600\">Add Task\u003C/button>\n \u003C/form>\n \u003Ctable class=\"mt-4 w-full\">\n \u003Cthead>\n \u003Ctr>\n \u003Cth class=\"text-left\">Task\u003C/th>\n \u003Cth>\u003C/th>\n \u003C/tr>\n \u003C/thead>\n \u003Ctbody id=\"task-table-body\">\n @for(Task task : tasks)\n @template.task-row(task = task)\n @endfor\n \u003C/tbody>\n \u003C/table>\n \u003C/div>\n \u003C/div>\n\u003C/body>\n\u003C/html>\n",[676,134511,134512,134517,134526,134530,134540,134554,134562,134576,134596,134609,134628,134646,134654,134669,134684,134699,134718,134738,134785,134819,134846,134854,134869,134877,134886,134907,134919,134928,134937,134952,134957,134962,134967,134975,134983,134991,134999,135007],{"__ignoreMap":674},[679,134513,134514],{"class":681,"line":682},[679,134515,134516],{"class":693},"@import dev.danvega.tasks.model.Task\n",[679,134518,134519,134521,134523],{"class":681,"line":790},[679,134520,132615],{"class":693},[679,134522,20878],{"class":6561},[679,134524,134525],{"class":693},"> tasks\n",[679,134527,134528],{"class":681,"line":892},[679,134529,889],{"emptyLinePlaceholder":797},[679,134531,134532,134534,134536,134538],{"class":681,"line":901},[679,134533,11904],{"class":693},[679,134535,11907],{"class":4508},[679,134537,11910],{"class":880},[679,134539,4519],{"class":693},[679,134541,134542,134544,134546,134548,134550,134552],{"class":681,"line":909},[679,134543,4505],{"class":693},[679,134545,4498],{"class":4508},[679,134547,18806],{"class":880},[679,134549,686],{"class":693},[679,134551,57251],{"class":689},[679,134553,4519],{"class":693},[679,134555,134556,134558,134560],{"class":681,"line":918},[679,134557,4505],{"class":693},[679,134559,11741],{"class":4508},[679,134561,4519],{"class":693},[679,134563,134564,134566,134568,134570,134572,134574],{"class":681,"line":935},[679,134565,4524],{"class":693},[679,134567,11968],{"class":4508},[679,134569,11971],{"class":880},[679,134571,686],{"class":693},[679,134573,47958],{"class":689},[679,134575,4519],{"class":693},[679,134577,134578,134580,134582,134584,134586,134588,134590,134592,134594],{"class":681,"line":944},[679,134579,4524],{"class":693},[679,134581,11968],{"class":4508},[679,134583,5283],{"class":880},[679,134585,686],{"class":693},[679,134587,12015],{"class":689},[679,134589,11995],{"class":880},[679,134591,686],{"class":693},[679,134593,48042],{"class":689},[679,134595,4519],{"class":693},[679,134597,134598,134600,134602,134605,134607],{"class":681,"line":959},[679,134599,4524],{"class":693},[679,134601,11750],{"class":4508},[679,134603,134604],{"class":693},">Task Manager\u003C/",[679,134606,11750],{"class":4508},[679,134608,4519],{"class":693},[679,134610,134611,134613,134615,134617,134619,134622,134624,134626],{"class":681,"line":964},[679,134612,4524],{"class":693},[679,134614,47668],{"class":4508},[679,134616,5361],{"class":880},[679,134618,686],{"class":693},[679,134620,134621],{"class":689},"\"https://unpkg.com/htmx.org@1.9.6\"",[679,134623,4563],{"class":693},[679,134625,47668],{"class":4508},[679,134627,4519],{"class":693},[679,134629,134630,134632,134634,134636,134638,134640,134642,134644],{"class":681,"line":977},[679,134631,4524],{"class":693},[679,134633,47668],{"class":4508},[679,134635,5361],{"class":880},[679,134637,686],{"class":693},[679,134639,124580],{"class":689},[679,134641,4563],{"class":693},[679,134643,47668],{"class":4508},[679,134645,4519],{"class":693},[679,134647,134648,134650,134652],{"class":681,"line":982},[679,134649,4586],{"class":693},[679,134651,11741],{"class":4508},[679,134653,4519],{"class":693},[679,134655,134656,134658,134660,134662,134664,134667],{"class":681,"line":988},[679,134657,4505],{"class":693},[679,134659,3006],{"class":4508},[679,134661,4512],{"class":880},[679,134663,686],{"class":693},[679,134665,134666],{"class":689},"\"bg-gray-100 p-8\"",[679,134668,4519],{"class":693},[679,134670,134671,134673,134675,134677,134679,134682],{"class":681,"line":993},[679,134672,4524],{"class":693},[679,134674,4509],{"class":4508},[679,134676,4512],{"class":880},[679,134678,686],{"class":693},[679,134680,134681],{"class":689},"\"max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl\"",[679,134683,4519],{"class":693},[679,134685,134686,134688,134690,134692,134694,134697],{"class":681,"line":2129},[679,134687,4904],{"class":693},[679,134689,4509],{"class":4508},[679,134691,4512],{"class":880},[679,134693,686],{"class":693},[679,134695,134696],{"class":689},"\"p-8\"",[679,134698,4519],{"class":693},[679,134700,134701,134703,134705,134707,134709,134712,134714,134716],{"class":681,"line":2140},[679,134702,5392],{"class":693},[679,134704,4509],{"class":4508},[679,134706,4512],{"class":880},[679,134708,686],{"class":693},[679,134710,134711],{"class":689},"\"uppercase tracking-wide text-sm text-indigo-500 font-semibold mb-1\"",[679,134713,134604],{"class":693},[679,134715,4509],{"class":4508},[679,134717,4519],{"class":693},[679,134719,134720,134722,134724,134726,134728,134731,134734,134736],{"class":681,"line":2145},[679,134721,5392],{"class":693},[679,134723,651],{"class":4508},[679,134725,4512],{"class":880},[679,134727,686],{"class":693},[679,134729,134730],{"class":689},"\"block mt-1 text-lg leading-tight font-medium text-black\"",[679,134732,134733],{"class":693},">List of all your tasks\u003C/",[679,134735,651],{"class":4508},[679,134737,4519],{"class":693},[679,134739,134740,134742,134745,134747,134749,134752,134755,134757,134759,134762,134764,134767,134770,134772,134775,134778,134780,134783],{"class":681,"line":2154},[679,134741,5392],{"class":693},[679,134743,134744],{"class":4508},"form",[679,134746,4512],{"class":880},[679,134748,686],{"class":693},[679,134750,134751],{"class":689},"\"mt-4\"",[679,134753,134754],{"class":880}," hx-post",[679,134756,686],{"class":693},[679,134758,134337],{"class":689},[679,134760,134761],{"class":880}," hx-target",[679,134763,686],{"class":693},[679,134765,134766],{"class":689},"\"#task-table-body\"",[679,134768,134769],{"class":880}," hx-swap",[679,134771,686],{"class":693},[679,134773,134774],{"class":689},"\"beforeend\"",[679,134776,134777],{"class":880}," hx-on::after-request",[679,134779,686],{"class":693},[679,134781,134782],{"class":689},"\"this.reset()\"",[679,134784,4519],{"class":693},[679,134786,134787,134789,134791,134793,134795,134797,134799,134801,134803,134805,134807,134810,134812,134814,134817],{"class":681,"line":2159},[679,134788,125341],{"class":693},[679,134790,27722],{"class":4508},[679,134792,27725],{"class":880},[679,134794,686],{"class":693},[679,134796,54445],{"class":689},[679,134798,5283],{"class":880},[679,134800,686],{"class":693},[679,134802,47908],{"class":689},[679,134804,54448],{"class":880},[679,134806,686],{"class":693},[679,134808,134809],{"class":689},"\"Enter new task\"",[679,134811,4512],{"class":880},[679,134813,686],{"class":693},[679,134815,134816],{"class":689},"\"w-full p-2 border rounded\"",[679,134818,4519],{"class":693},[679,134820,134821,134823,134825,134827,134829,134832,134834,134836,134839,134842,134844],{"class":681,"line":2164},[679,134822,125341],{"class":693},[679,134824,54258],{"class":4508},[679,134826,27725],{"class":880},[679,134828,686],{"class":693},[679,134830,134831],{"class":689},"\"submit\"",[679,134833,4512],{"class":880},[679,134835,686],{"class":693},[679,134837,134838],{"class":689},"\"mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600\"",[679,134840,134841],{"class":693},">Add Task\u003C/",[679,134843,54258],{"class":4508},[679,134845,4519],{"class":693},[679,134847,134848,134850,134852],{"class":681,"line":3134},[679,134849,125356],{"class":693},[679,134851,134744],{"class":4508},[679,134853,4519],{"class":693},[679,134855,134856,134858,134860,134862,134864,134867],{"class":681,"line":3139},[679,134857,5392],{"class":693},[679,134859,1031],{"class":4508},[679,134861,4512],{"class":880},[679,134863,686],{"class":693},[679,134865,134866],{"class":689},"\"mt-4 w-full\"",[679,134868,4519],{"class":693},[679,134870,134871,134873,134875],{"class":681,"line":3144},[679,134872,125341],{"class":693},[679,134874,1034],{"class":4508},[679,134876,4519],{"class":693},[679,134878,134879,134882,134884],{"class":681,"line":3149},[679,134880,134881],{"class":693}," \u003C",[679,134883,1037],{"class":4508},[679,134885,4519],{"class":693},[679,134887,134888,134891,134893,134895,134897,134900,134903,134905],{"class":681,"line":3169},[679,134889,134890],{"class":693}," \u003C",[679,134892,1040],{"class":4508},[679,134894,4512],{"class":880},[679,134896,686],{"class":693},[679,134898,134899],{"class":689},"\"text-left\"",[679,134901,134902],{"class":693},">Task\u003C/",[679,134904,1040],{"class":4508},[679,134906,4519],{"class":693},[679,134908,134909,134911,134913,134915,134917],{"class":681,"line":3185},[679,134910,134890],{"class":693},[679,134912,1040],{"class":4508},[679,134914,4563],{"class":693},[679,134916,1040],{"class":4508},[679,134918,4519],{"class":693},[679,134920,134921,134924,134926],{"class":681,"line":3194},[679,134922,134923],{"class":693}," \u003C/",[679,134925,1037],{"class":4508},[679,134927,4519],{"class":693},[679,134929,134930,134933,134935],{"class":681,"line":3199},[679,134931,134932],{"class":693}," \u003C/",[679,134934,1034],{"class":4508},[679,134936,4519],{"class":693},[679,134938,134939,134941,134943,134945,134947,134950],{"class":681,"line":3212},[679,134940,125341],{"class":693},[679,134942,1050],{"class":4508},[679,134944,5578],{"class":880},[679,134946,686],{"class":693},[679,134948,134949],{"class":689},"\"task-table-body\"",[679,134951,4519],{"class":693},[679,134953,134954],{"class":681,"line":3217},[679,134955,134956],{"class":693}," @for(Task task : tasks)\n",[679,134958,134959],{"class":681,"line":3222},[679,134960,134961],{"class":693}," @template.task-row(task = task)\n",[679,134963,134964],{"class":681,"line":3227},[679,134965,134966],{"class":693}," @endfor\n",[679,134968,134969,134971,134973],{"class":681,"line":3232},[679,134970,134932],{"class":693},[679,134972,1050],{"class":4508},[679,134974,4519],{"class":693},[679,134976,134977,134979,134981],{"class":681,"line":3499},[679,134978,125356],{"class":693},[679,134980,1031],{"class":4508},[679,134982,4519],{"class":693},[679,134984,134985,134987,134989],{"class":681,"line":3509},[679,134986,5480],{"class":693},[679,134988,4509],{"class":4508},[679,134990,4519],{"class":693},[679,134992,134993,134995,134997],{"class":681,"line":3516},[679,134994,4577],{"class":693},[679,134996,4509],{"class":4508},[679,134998,4519],{"class":693},[679,135000,135001,135003,135005],{"class":681,"line":3531},[679,135002,4586],{"class":693},[679,135004,3006],{"class":4508},[679,135006,4519],{"class":693},[679,135008,135009,135011,135013],{"class":681,"line":3536},[679,135010,4586],{"class":693},[679,135012,4498],{"class":4508},[679,135014,4519],{"class":693},[651,135016,135017,135018,2391],{},"And ",[676,135019,135020],{},"task-row.jte",[669,135022,135024],{"className":4496,"code":135023,"language":4498,"meta":674,"style":674},"@import dev.danvega.tasks.model.Task\n@param Task task\n\n\u003Ctr id=\"task-${task.getId()}\">\n \u003Ctd class=\"py-2\">${task.getDescription()}\u003C/td>\n \u003Ctd class=\"text-right\">\n \u003Cbutton hx-delete=\"/delete-task/${task.getId()}\"\n hx-target=\"closest tr\"\n hx-swap=\"outerHTML swap:1s\"\n class=\"px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600\">\n Delete\n \u003C/button>\n \u003C/td>\n\u003C/tr>\n",[676,135025,135026,135030,135035,135039,135054,135074,135089,135103,135113,135123,135135,135140,135148,135156],{"__ignoreMap":674},[679,135027,135028],{"class":681,"line":682},[679,135029,134516],{"class":693},[679,135031,135032],{"class":681,"line":790},[679,135033,135034],{"class":693},"@param Task task\n",[679,135036,135037],{"class":681,"line":892},[679,135038,889],{"emptyLinePlaceholder":797},[679,135040,135041,135043,135045,135047,135049,135052],{"class":681,"line":901},[679,135042,4505],{"class":693},[679,135044,1037],{"class":4508},[679,135046,5578],{"class":880},[679,135048,686],{"class":693},[679,135050,135051],{"class":689},"\"task-${task.getId()}\"",[679,135053,4519],{"class":693},[679,135055,135056,135058,135060,135062,135064,135067,135070,135072],{"class":681,"line":909},[679,135057,4524],{"class":693},[679,135059,1055],{"class":4508},[679,135061,4512],{"class":880},[679,135063,686],{"class":693},[679,135065,135066],{"class":689},"\"py-2\"",[679,135068,135069],{"class":693},">${task.getDescription()}\u003C/",[679,135071,1055],{"class":4508},[679,135073,4519],{"class":693},[679,135075,135076,135078,135080,135082,135084,135087],{"class":681,"line":918},[679,135077,4524],{"class":693},[679,135079,1055],{"class":4508},[679,135081,4512],{"class":880},[679,135083,686],{"class":693},[679,135085,135086],{"class":689},"\"text-right\"",[679,135088,4519],{"class":693},[679,135090,135091,135093,135095,135098,135100],{"class":681,"line":935},[679,135092,4904],{"class":693},[679,135094,54258],{"class":4508},[679,135096,135097],{"class":880}," hx-delete",[679,135099,686],{"class":693},[679,135101,135102],{"class":689},"\"/delete-task/${task.getId()}\"\n",[679,135104,135105,135108,135110],{"class":681,"line":944},[679,135106,135107],{"class":880}," hx-target",[679,135109,686],{"class":693},[679,135111,135112],{"class":689},"\"closest tr\"\n",[679,135114,135115,135118,135120],{"class":681,"line":959},[679,135116,135117],{"class":880}," hx-swap",[679,135119,686],{"class":693},[679,135121,135122],{"class":689},"\"outerHTML swap:1s\"\n",[679,135124,135125,135128,135130,135133],{"class":681,"line":964},[679,135126,135127],{"class":880}," class",[679,135129,686],{"class":693},[679,135131,135132],{"class":689},"\"px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600\"",[679,135134,4519],{"class":693},[679,135136,135137],{"class":681,"line":977},[679,135138,135139],{"class":693}," Delete\n",[679,135141,135142,135144,135146],{"class":681,"line":982},[679,135143,5480],{"class":693},[679,135145,54258],{"class":4508},[679,135147,4519],{"class":693},[679,135149,135150,135152,135154],{"class":681,"line":988},[679,135151,4577],{"class":693},[679,135153,1055],{"class":4508},[679,135155,4519],{"class":693},[679,135157,135158,135160,135162],{"class":681,"line":993},[679,135159,4586],{"class":693},[679,135161,1037],{"class":4508},[679,135163,4519],{"class":693},[4542,135165,135167],{"id":135166},"integrating-htmx-for-dynamic-interactions","Integrating HTMX for Dynamic Interactions",[651,135169,135170],{},"Now that we have our basic structure in place, let's look at how HTMX makes our application dynamic:",[27665,135172,135173,135193],{},[5332,135174,135175,135178,135179,135181,135182,135185,135186,23212,135189,135192],{},[2939,135176,135177],{},"Adding Tasks",": In the form within ",[676,135180,134506],{},", we use ",[676,135183,135184],{},"hx-post=\"/add-task\""," to send a POST request to our controller. The ",[676,135187,135188],{},"hx-target=\"#task-table-body\"",[676,135190,135191],{},"hx-swap=\"beforeend\""," attributes ensure the new task is appended to our table without a full page reload.",[5332,135194,135195,135198,135199,135201,135202,135205,135206,23212,135209,135212],{},[2939,135196,135197],{},"Deleting Tasks",": In ",[676,135200,135020],{},", the delete button uses ",[676,135203,135204],{},"hx-delete=\"/delete-task/${task.getId()}\""," to send a DELETE request. The ",[676,135207,135208],{},"hx-target=\"closest tr\"",[676,135210,135211],{},"hx-swap=\"outerHTML swap:1s\""," attributes remove the task row with a smooth fade-out effect.",[651,135214,135215],{},"These HTMX attributes allow us to create a dynamic, interactive interface without writing any JavaScript!",[4542,135217,9042],{"id":9041},[651,135219,135220],{},"By combining Spring Boot, JTE, and HTMX, we've created a responsive task manager application with minimal code. This approach offers several benefits:",[27665,135222,135223,135229,135235],{},[5332,135224,135225,135228],{},[2939,135226,135227],{},"Simplified Frontend",": HTMX allows us to create dynamic interfaces without complex JavaScript frameworks.",[5332,135230,135231,135234],{},[2939,135232,135233],{},"Clean Templates",": JTE provides a straightforward templating solution that integrates seamlessly with Spring Boot.",[5332,135236,135237,135240],{},[2939,135238,135239],{},"Rapid Development",": The combination of these technologies allows for quick iteration and prototyping.",[651,135242,135243],{},"I encourage you to try this stack in your next project. It's a great way to build interactive web applications while keeping your codebase clean and maintainable.",[651,135245,135246],{},"Remember, the key to mastering these technologies is practice. Start with small projects, experiment with different HTMX attributes, and explore more complex JTE features. Happy coding!",[786,135248,135249],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .shOWo, html code.shiki .shOWo{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-sepia:#B31D28;--shiki-sepia-font-style:italic}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":135251},[135252,135253,135254,135260,135261],{"id":133729,"depth":790,"text":133730},{"id":94280,"depth":790,"text":94281},{"id":133814,"depth":790,"text":133815,"children":135255},[135256,135257,135258,135259],{"id":133818,"depth":892,"text":133819},{"id":133924,"depth":892,"text":133925},{"id":134175,"depth":892,"text":134176},{"id":134499,"depth":892,"text":134500},{"id":135166,"depth":790,"text":135167},{"id":9041,"depth":790,"text":9042},"Learn how to build a dynamic Task manager using Spring Boot, Java Template Engine (JTE), HTMX and Tailwind CSS.",{"slug":135264,"date":135265,"published":797,"author":798,"tags":135266,"video":135267,"keywords":133716},"spring-boot-jte-htmx","2024-10-06T20:00:00.000Z",[7077,36422],"https://www.youtube.com/embed/kFksiDRZ824",{"title":66,"description":135262},"blog/2024/10/06/spring-boot-jte-htmx","mXKYTRneq8kxfKOOPUaWTN5pPQJxuiQP1k6fkRDEXlg",{"id":135272,"title":63,"body":135273,"description":136068,"extension":793,"meta":136069,"navigation":797,"path":64,"seo":136075,"stem":136076,"__hash__":136077},"content/blog/2024/10/07/jte-forms.md",{"type":648,"value":135274,"toc":136054},[135275,135278,135282,135289,135293,135296,135313,135315,135318,135337,135345,135349,135353,135358,135459,135463,135466,135497,135501,135504,135686,135690,135696,135880,135884,135887,135909,135999,136001,136007,136013,136015,136018,136021,136035,136038,136041,136043,136051],[651,135276,135277],{},"Hello, fellow developers! Dan Vega here, and today we're diving into an exciting topic: working with forms using Java Template Engine (JTE) in Spring Boot applications. If you've been following along with my recent posts on JTE, you know it's a powerful new addition to the Spring ecosystem. Today, we're addressing a common question: \"How can we bind objects to forms in JTE, similar to Thymeleaf's th:object and th:text?\"",[4542,135279,135281],{"id":135280},"the-power-of-jte-in-form-handling","The Power of JTE in Form Handling",[651,135283,135284,135285,135288],{},"Good news! It's surprisingly simple to work with forms in JTE. We can easily include an object in our page and bind form fields to its properties. For instance, we can say ",[676,135286,135287],{},"user.getFirstName()"," to populate a field with the user's first name. Let's build a practical example to showcase this functionality.",[4542,135290,135292],{"id":135291},"our-project-a-feature-rich-user-registration-form","Our Project: A Feature-Rich User Registration Form",[651,135294,135295],{},"We're going beyond a basic example today. We'll create a fully functional user registration system with the following features:",[27665,135297,135298,135301,135304,135307,135310],{},[5332,135299,135300],{},"A visually appealing form using Tailwind CSS",[5332,135302,135303],{},"Form submission handling",[5332,135305,135306],{},"Data persistence in a PostgreSQL database",[5332,135308,135309],{},"Docker integration for easy database setup",[5332,135311,135312],{},"Validation and error handling",[4542,135314,94281],{"id":94280},[651,135316,135317],{},"To get started quickly, I've prepared a Spring Initializr configuration with all the necessary dependencies. Here's what we're using:",[5316,135319,135320,135322,135325,135328,135331,135334],{},[5332,135321,80827],{},[5332,135323,135324],{},"JTE (now available in Spring Initializr!)",[5332,135326,135327],{},"Spring Data JDBC for database operations",[5332,135329,135330],{},"PostgreSQL driver",[5332,135332,135333],{},"Spring Boot Docker Compose support",[5332,135335,135336],{},"Spring Boot DevTools for development convenience",[651,135338,135339,135340,135344],{},"You can check out the ",[812,135341,89158],{"href":135342,"rel":135343},"https://github.com/danvega/jte-forms",[816]," for all the source code we are going to go through.",[4542,135346,135348],{"id":135347},"key-components-of-our-application","Key Components of Our Application",[5909,135350,135352],{"id":135351},"_1-the-user-model","1. The User Model",[651,135354,114895,135355,135357],{},[676,135356,34017],{}," class to represent our form data:",[669,135359,135361],{"className":4107,"code":135360,"language":4109,"meta":674,"style":674},"@Table(\"users\")\npublic class User {\n @Id\n private Long id;\n @NotBlank(message = \"First Name should not be blank.\")\n private String firstName;\n @NotBlank(message = \"Last Name should not be blank.\")\n private String lastName;\n private String email;\n // ... other fields, getters, and setters\n}\n",[676,135362,135363,135376,135386,135392,135398,135415,135421,135438,135444,135450,135455],{"__ignoreMap":674},[679,135364,135365,135367,135370,135372,135374],{"class":681,"line":682},[679,135366,4116],{"class":693},[679,135368,135369],{"class":685},"Table",[679,135371,745],{"class":693},[679,135373,46918],{"class":689},[679,135375,1339],{"class":693},[679,135377,135378,135380,135382,135384],{"class":681,"line":790},[679,135379,6073],{"class":685},[679,135381,4512],{"class":685},[679,135383,881],{"class":880},[679,135385,884],{"class":693},[679,135387,135388,135390],{"class":681,"line":892},[679,135389,6872],{"class":693},[679,135391,33530],{"class":685},[679,135393,135394,135396],{"class":681,"line":901},[679,135395,9232],{"class":685},[679,135397,11268],{"class":693},[679,135399,135400,135402,135404,135406,135408,135410,135413],{"class":681,"line":909},[679,135401,6872],{"class":693},[679,135403,111822],{"class":685},[679,135405,745],{"class":693},[679,135407,20198],{"class":931},[679,135409,6883],{"class":685},[679,135411,135412],{"class":689}," \"First Name should not be blank.\"",[679,135414,1339],{"class":693},[679,135416,135417,135419],{"class":681,"line":918},[679,135418,9232],{"class":685},[679,135420,35749],{"class":693},[679,135422,135423,135425,135427,135429,135431,135433,135436],{"class":681,"line":935},[679,135424,6872],{"class":693},[679,135426,111822],{"class":685},[679,135428,745],{"class":693},[679,135430,20198],{"class":931},[679,135432,6883],{"class":685},[679,135434,135435],{"class":689}," \"Last Name should not be blank.\"",[679,135437,1339],{"class":693},[679,135439,135440,135442],{"class":681,"line":944},[679,135441,9232],{"class":685},[679,135443,83965],{"class":693},[679,135445,135446,135448],{"class":681,"line":959},[679,135447,9232],{"class":685},[679,135449,13626],{"class":693},[679,135451,135452],{"class":681,"line":964},[679,135453,135454],{"class":1400}," // ... other fields, getters, and setters\n",[679,135456,135457],{"class":681,"line":977},[679,135458,996],{"class":693},[5909,135460,135462],{"id":135461},"_2-the-user-repository","2. The User Repository",[651,135464,135465],{},"Next, we'll create a simple repository interface:",[669,135467,135469],{"className":4107,"code":135468,"language":4109,"meta":674,"style":674},"public interface UserRepository extends CrudRepository\u003CUser, Long> {\n}\n",[676,135470,135471,135493],{"__ignoreMap":674},[679,135472,135473,135475,135477,135479,135481,135483,135485,135487,135489,135491],{"class":681,"line":682},[679,135474,6073],{"class":685},[679,135476,6994],{"class":685},[679,135478,36074],{"class":880},[679,135480,2767],{"class":685},[679,135482,16385],{"class":880},[679,135484,4505],{"class":693},[679,135486,34017],{"class":685},[679,135488,2797],{"class":693},[679,135490,1094],{"class":685},[679,135492,16397],{"class":693},[679,135494,135495],{"class":681,"line":790},[679,135496,996],{"class":693},[5909,135498,135500],{"id":135499},"_3-the-controller","3. The Controller",[651,135502,135503],{},"Our controller will handle form display and submission:",[669,135505,135507],{"className":4107,"code":135506,"language":4109,"meta":674,"style":674},"@Controller\npublic class UserController {\n private final UserRepository repository;\n\n @GetMapping(\"/\")\n public String index(Model model) {\n model.addAttribute(\"user\", new User());\n return \"index\";\n }\n\n @PostMapping(\"/save\")\n public String saveUser(@Valid User user, Model model) {\n repository.save(user);\n model.addAttribute(\"message\", \"User information saved successfully!\");\n return \"index\";\n }\n\n // ... error handling methods\n}\n",[676,135508,135509,135515,135525,135534,135538,135550,135564,135582,135590,135594,135598,135610,135635,135643,135661,135669,135673,135677,135682],{"__ignoreMap":674},[679,135510,135511,135513],{"class":681,"line":682},[679,135512,4116],{"class":693},[679,135514,9942],{"class":685},[679,135516,135517,135519,135521,135523],{"class":681,"line":790},[679,135518,6073],{"class":685},[679,135520,4512],{"class":685},[679,135522,2013],{"class":880},[679,135524,884],{"class":693},[679,135526,135527,135529,135531],{"class":681,"line":892},[679,135528,9232],{"class":685},[679,135530,12768],{"class":685},[679,135532,135533],{"class":693}," UserRepository repository;\n",[679,135535,135536],{"class":681,"line":901},[679,135537,889],{"emptyLinePlaceholder":797},[679,135539,135540,135542,135544,135546,135548],{"class":681,"line":909},[679,135541,6872],{"class":693},[679,135543,20852],{"class":685},[679,135545,745],{"class":693},[679,135547,10032],{"class":689},[679,135549,1339],{"class":693},[679,135551,135552,135554,135556,135558,135560,135562],{"class":681,"line":918},[679,135553,6089],{"class":685},[679,135555,9289],{"class":693},[679,135557,4180],{"class":880},[679,135559,10045],{"class":693},[679,135561,10048],{"class":2099},[679,135563,4390],{"class":693},[679,135565,135566,135568,135570,135572,135574,135576,135578,135580],{"class":681,"line":935},[679,135567,10061],{"class":693},[679,135569,10064],{"class":880},[679,135571,745],{"class":693},[679,135573,46004],{"class":689},[679,135575,2797],{"class":693},[679,135577,8930],{"class":685},[679,135579,881],{"class":880},[679,135581,9431],{"class":693},[679,135583,135584,135586,135588],{"class":681,"line":944},[679,135585,9444],{"class":685},[679,135587,18769],{"class":689},[679,135589,1186],{"class":693},[679,135591,135592],{"class":681,"line":959},[679,135593,985],{"class":693},[679,135595,135596],{"class":681,"line":964},[679,135597,889],{"emptyLinePlaceholder":797},[679,135599,135600,135602,135604,135606,135608],{"class":681,"line":977},[679,135601,6872],{"class":693},[679,135603,91165],{"class":685},[679,135605,745],{"class":693},[679,135607,12995],{"class":689},[679,135609,1339],{"class":693},[679,135611,135612,135614,135616,135619,135621,135624,135627,135629,135631,135633],{"class":681,"line":982},[679,135613,6089],{"class":685},[679,135615,9289],{"class":693},[679,135617,135618],{"class":880},"saveUser",[679,135620,73246],{"class":693},[679,135622,135623],{"class":685},"Valid",[679,135625,135626],{"class":693}," User ",[679,135628,9575],{"class":2099},[679,135630,134358],{"class":693},[679,135632,10048],{"class":2099},[679,135634,4390],{"class":693},[679,135636,135637,135639,135641],{"class":681,"line":988},[679,135638,16713],{"class":693},[679,135640,7629],{"class":880},[679,135642,9386],{"class":693},[679,135644,135645,135647,135649,135651,135654,135656,135659],{"class":681,"line":993},[679,135646,10061],{"class":693},[679,135648,10064],{"class":880},[679,135650,745],{"class":693},[679,135652,135653],{"class":689},"\"message\"",[679,135655,2797],{"class":693},[679,135657,135658],{"class":689},"\"User information saved successfully!\"",[679,135660,1208],{"class":693},[679,135662,135663,135665,135667],{"class":681,"line":2129},[679,135664,9444],{"class":685},[679,135666,18769],{"class":689},[679,135668,1186],{"class":693},[679,135670,135671],{"class":681,"line":2140},[679,135672,985],{"class":693},[679,135674,135675],{"class":681,"line":2145},[679,135676,889],{"emptyLinePlaceholder":797},[679,135678,135679],{"class":681,"line":2154},[679,135680,135681],{"class":1400}," // ... error handling methods\n",[679,135683,135684],{"class":681,"line":2159},[679,135685,996],{"class":693},[5909,135687,135689],{"id":135688},"_4-the-jte-template","4. The JTE Template",[651,135691,135692,135693,135695],{},"Here's where the magic happens. Our ",[676,135694,134506],{}," file will look something like this:",[669,135697,135699],{"className":4496,"code":135698,"language":4498,"meta":674,"style":674},"@param User user\n@param String message\n@param String error\n\n\u003Cform method=\"post\" action=\"/save\">\n \u003Cinput type=\"text\" name=\"firstName\" value=\"${user.firstName}\">\n \u003Cinput type=\"text\" name=\"lastName\" value=\"${user.lastName}\">\n \u003C!-- More form fields -->\n \u003Cbutton type=\"submit\">Save\u003C/button>\n\u003C/form>\n\n@if(message != null)\n \u003Cdiv>${message}\u003C/div>\n@endif\n\n@if(error != null)\n \u003Cdiv>${error}\u003C/div>\n@endif\n",[676,135700,135701,135706,135711,135716,135720,135741,135768,135796,135801,135820,135828,135832,135837,135850,135854,135858,135863,135876],{"__ignoreMap":674},[679,135702,135703],{"class":681,"line":682},[679,135704,135705],{"class":693},"@param User user\n",[679,135707,135708],{"class":681,"line":790},[679,135709,135710],{"class":693},"@param String message\n",[679,135712,135713],{"class":681,"line":892},[679,135714,135715],{"class":693},"@param String error\n",[679,135717,135718],{"class":681,"line":901},[679,135719,889],{"emptyLinePlaceholder":797},[679,135721,135722,135724,135726,135728,135730,135732,135735,135737,135739],{"class":681,"line":909},[679,135723,4505],{"class":693},[679,135725,134744],{"class":4508},[679,135727,90100],{"class":880},[679,135729,686],{"class":693},[679,135731,66390],{"class":689},[679,135733,135734],{"class":880}," action",[679,135736,686],{"class":693},[679,135738,12995],{"class":689},[679,135740,4519],{"class":693},[679,135742,135743,135745,135747,135749,135751,135753,135755,135757,135759,135761,135763,135766],{"class":681,"line":918},[679,135744,4524],{"class":693},[679,135746,27722],{"class":4508},[679,135748,27725],{"class":880},[679,135750,686],{"class":693},[679,135752,54445],{"class":689},[679,135754,5283],{"class":880},[679,135756,686],{"class":693},[679,135758,54460],{"class":689},[679,135760,65142],{"class":880},[679,135762,686],{"class":693},[679,135764,135765],{"class":689},"\"${user.firstName}\"",[679,135767,4519],{"class":693},[679,135769,135770,135772,135774,135776,135778,135780,135782,135784,135787,135789,135791,135794],{"class":681,"line":935},[679,135771,4524],{"class":693},[679,135773,27722],{"class":4508},[679,135775,27725],{"class":880},[679,135777,686],{"class":693},[679,135779,54445],{"class":689},[679,135781,5283],{"class":880},[679,135783,686],{"class":693},[679,135785,135786],{"class":689},"\"lastName\"",[679,135788,65142],{"class":880},[679,135790,686],{"class":693},[679,135792,135793],{"class":689},"\"${user.lastName}\"",[679,135795,4519],{"class":693},[679,135797,135798],{"class":681,"line":944},[679,135799,135800],{"class":1400}," \u003C!-- More form fields -->\n",[679,135802,135803,135805,135807,135809,135811,135813,135816,135818],{"class":681,"line":959},[679,135804,4524],{"class":693},[679,135806,54258],{"class":4508},[679,135808,27725],{"class":880},[679,135810,686],{"class":693},[679,135812,134831],{"class":689},[679,135814,135815],{"class":693},">Save\u003C/",[679,135817,54258],{"class":4508},[679,135819,4519],{"class":693},[679,135821,135822,135824,135826],{"class":681,"line":964},[679,135823,4586],{"class":693},[679,135825,134744],{"class":4508},[679,135827,4519],{"class":693},[679,135829,135830],{"class":681,"line":977},[679,135831,889],{"emptyLinePlaceholder":797},[679,135833,135834],{"class":681,"line":982},[679,135835,135836],{"class":693},"@if(message != null)\n",[679,135838,135839,135841,135843,135846,135848],{"class":681,"line":988},[679,135840,4524],{"class":693},[679,135842,4509],{"class":4508},[679,135844,135845],{"class":693},">${message}\u003C/",[679,135847,4509],{"class":4508},[679,135849,4519],{"class":693},[679,135851,135852],{"class":681,"line":993},[679,135853,132683],{"class":693},[679,135855,135856],{"class":681,"line":2129},[679,135857,889],{"emptyLinePlaceholder":797},[679,135859,135860],{"class":681,"line":2140},[679,135861,135862],{"class":693},"@if(error != null)\n",[679,135864,135865,135867,135869,135872,135874],{"class":681,"line":2145},[679,135866,4524],{"class":693},[679,135868,4509],{"class":4508},[679,135870,135871],{"class":693},">${error}\u003C/",[679,135873,4509],{"class":4508},[679,135875,4519],{"class":693},[679,135877,135878],{"class":681,"line":2154},[679,135879,132683],{"class":693},[4542,135881,135883],{"id":135882},"adding-validation-and-error-handling","Adding Validation and Error Handling",[651,135885,135886],{},"We'll use Spring's validation framework to ensure data integrity:",[27665,135888,135889,135897,135906],{},[5332,135890,77547,135891,135894,135895,80369],{},[676,135892,135893],{},"@NotBlank"," annotations to required fields in the ",[676,135896,34017],{},[5332,135898,135899,135900,135903,135904,78287],{},"Use ",[676,135901,135902],{},"@Valid"," in the controller's ",[676,135905,135618],{},[5332,135907,135908],{},"Implement an exception handler for validation errors:",[669,135910,135912],{"className":4107,"code":135911,"language":4109,"meta":674,"style":674},"@ExceptionHandler(MethodArgumentNotValidException.class)\npublic String handleValidationExceptions(MethodArgumentNotValidException ex, Model model) {\n User user = (User) ex.getBindingResult().getTarget();\n model.addAttribute(\"user\", user);\n model.addAttribute(\"error\", \"Please fill out all required fields.\");\n return \"index\";\n}\n",[676,135913,135914,135924,135936,135956,135970,135987,135995],{"__ignoreMap":674},[679,135915,135916,135918,135921],{"class":681,"line":682},[679,135917,4116],{"class":693},[679,135919,135920],{"class":685},"ExceptionHandler",[679,135922,135923],{"class":693},"(MethodArgumentNotValidException.class)\n",[679,135925,135926,135928,135930,135933],{"class":681,"line":790},[679,135927,6073],{"class":685},[679,135929,9289],{"class":693},[679,135931,135932],{"class":880},"handleValidationExceptions",[679,135934,135935],{"class":693},"(MethodArgumentNotValidException ex, Model model) {\n",[679,135937,135938,135941,135943,135946,135949,135951,135954],{"class":681,"line":892},[679,135939,135940],{"class":693}," User user ",[679,135942,686],{"class":685},[679,135944,135945],{"class":693}," (User) ex.",[679,135947,135948],{"class":880},"getBindingResult",[679,135950,10541],{"class":693},[679,135952,135953],{"class":880},"getTarget",[679,135955,9317],{"class":693},[679,135957,135958,135961,135963,135965,135967],{"class":681,"line":901},[679,135959,135960],{"class":693}," model.",[679,135962,10064],{"class":880},[679,135964,745],{"class":693},[679,135966,46004],{"class":689},[679,135968,135969],{"class":693},", user);\n",[679,135971,135972,135974,135976,135978,135980,135982,135985],{"class":681,"line":909},[679,135973,135960],{"class":693},[679,135975,10064],{"class":880},[679,135977,745],{"class":693},[679,135979,101853],{"class":689},[679,135981,2797],{"class":693},[679,135983,135984],{"class":689},"\"Please fill out all required fields.\"",[679,135986,1208],{"class":693},[679,135988,135989,135991,135993],{"class":681,"line":918},[679,135990,21478],{"class":685},[679,135992,18769],{"class":689},[679,135994,1186],{"class":693},[679,135996,135997],{"class":681,"line":935},[679,135998,996],{"class":693},[4542,136000,98048],{"id":7309},[651,136002,136003,136004,136006],{},"With Docker installed, simply run your application, and Spring Boot will automatically start a PostgreSQL container for you. Access your form at ",[676,136005,11697],{},", and you're ready to go!",[651,136008,136009],{},[660,136010],{"alt":136011,"src":136012},"Form","/images/blog/2024/10/07/personal_info_form.png",[4542,136014,9042],{"id":9041},[651,136016,136017],{},"And there you have it! We've built a robust user registration system using Spring Boot and JTE. This example demonstrates how easy it is to work with forms in JTE, rivaling the convenience of Thymeleaf's form binding capabilities.",[651,136019,136020],{},"Key takeaways:",[5316,136022,136023,136026,136029,136032],{},[5332,136024,136025],{},"JTE offers a clean, intuitive syntax for template creation",[5332,136027,136028],{},"Binding form fields to object properties is straightforward in JTE",[5332,136030,136031],{},"Spring Boot's integration with Docker Compose simplifies database setup",[5332,136033,136034],{},"Validation and error handling can be implemented efficiently",[651,136036,136037],{},"I hope this tutorial helps you in your journey with Spring Boot and JTE.",[651,136039,136040],{},"Happy coding, friends! Until next time!",[25332,136042],{},[651,136044,136045],{},[7300,136046,136047,136048],{},"Don't forget to check out the full source code on my GitHub repository: ",[812,136049,135342],{"href":135342,"rel":136050},[816],[786,136052,136053],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":136055},[136056,136057,136058,136059,136065,136066,136067],{"id":135280,"depth":790,"text":135281},{"id":135291,"depth":790,"text":135292},{"id":94280,"depth":790,"text":94281},{"id":135347,"depth":790,"text":135348,"children":136060},[136061,136062,136063,136064],{"id":135351,"depth":892,"text":135352},{"id":135461,"depth":892,"text":135462},{"id":135499,"depth":892,"text":135500},{"id":135688,"depth":892,"text":135689},{"id":135882,"depth":790,"text":135883},{"id":7309,"depth":790,"text":98048},{"id":9041,"depth":790,"text":9042},"Learn how to create interactive web forms using Spring Boot and Java Template Engine (JTE) in this comprehensive tutorial.",{"slug":136070,"date":136071,"published":797,"author":798,"tags":136072,"video":136073,"keywords":136074},"jte-forms","2024-10-07T20:00:00.000Z",[7077,36422],"https://www.youtube.com/embed/ifnv4kGtZgo","Spring Framework, Spring Boot, Spring Web, Spring MVC, Java Template Engine, JTE, Form Handling, Validation, PostgreSQL, Docker",{"title":63,"description":136068},"blog/2024/10/07/jte-forms","bbBkFimf7FmZ1nPAzBlKmILdXn6VW_t79653Szg8tPg",{"id":136079,"title":60,"body":136080,"description":137963,"extension":793,"meta":137964,"navigation":797,"path":61,"seo":137970,"stem":137971,"__hash__":137972},"content/blog/2024/10/08/spring-ai-streaming-chatbot.md",{"type":648,"value":136081,"toc":137953},[136082,136085,136089,136092,136096,136099,136101,136103,136140,136143,136147,136153,136198,136203,136220,136227,136231,136237,136461,136464,136481,136492,136496,136502,136509,136955,136963,137887,137891,137894,137912,137915,137917,137920,137944,137947,137950],[651,136083,136084],{},"In the world of AI-powered applications, responsiveness and user experience are crucial. When interacting with Large Language Models (LLMs), users expect quick feedback and a natural conversational flow. This is where streaming responses come into play. In this tutorial, we'll explore how to build a streaming chat bot using Spring Boot and Spring AI, providing a seamless and engaging user experience.",[4542,136086,136088],{"id":136087},"why-streaming-matters","Why Streaming Matters",[651,136090,136091],{},"Imagine typing a query into ChatGPT and waiting for the entire response to be generated before seeing anything on your screen. That wouldn't be very user-friendly, would it? Streaming allows us to display the AI's response as it's being generated, giving users immediate feedback and a more interactive experience.",[4542,136093,136095],{"id":136094},"enter-spring-ai","Enter Spring AI",[651,136097,136098],{},"Spring AI is a powerful library that simplifies working with various LLMs, providing a portable chat completion API. This means you can easily switch between different AI providers (like Anthropic, OpenAI, or Google Gemini) without changing your core application code. Today, we'll be using Anthropic's Claude 3.5 Sonnet model, but the concepts apply to other LLMs as well.",[4542,136100,94281],{"id":94280},[651,136102,133760],{},[27665,136104,136105,136110,136130],{},[5332,136106,104720,136107],{},[812,136108,77478],{"href":51358,"rel":136109},[816],[5332,136111,133770,136112],{},[5316,136113,136114,136116,136118,136121,136124,136127],{},[5332,136115,133775],{},[5332,136117,102049],{},[5332,136119,136120],{},"Spring Boot: 3.3.4 (or the latest version)",[5332,136122,136123],{},"Group: dev.danvega (use your own group ID)",[5332,136125,136126],{},"Artifact: streaming",[5332,136128,136129],{},"Java version: 23 (or your preferred version)",[5332,136131,136132,136133],{},"Add the following dependencies:\n",[5316,136134,136135,136137],{},[5332,136136,80827],{},[5332,136138,136139],{},"Spring AI OpenAI (we'll change this to the Anthropic starter later)",[651,136141,136142],{},"Generate the project and open it in your favorite IDE.",[4542,136144,136146],{"id":136145},"configuring-the-project","Configuring the Project",[651,136148,136149,136150,136152],{},"First, let's update the ",[676,136151,81517],{}," file to use the Anthropic starter:",[669,136154,136156],{"className":9101,"code":136155,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.ai\u003C/groupId>\n \u003CartifactId>spring-ai-anthropic-spring-boot-starter\u003C/artifactId>\n\u003C/dependency>\n",[676,136157,136158,136166,136178,136190],{"__ignoreMap":674},[679,136159,136160,136162,136164],{"class":681,"line":682},[679,136161,4505],{"class":693},[679,136163,119838],{"class":4508},[679,136165,4519],{"class":693},[679,136167,136168,136170,136172,136174,136176],{"class":681,"line":790},[679,136169,4524],{"class":693},[679,136171,119847],{"class":4508},[679,136173,129701],{"class":693},[679,136175,119847],{"class":4508},[679,136177,4519],{"class":693},[679,136179,136180,136182,136184,136186,136188],{"class":681,"line":892},[679,136181,4524],{"class":693},[679,136183,119861],{"class":4508},[679,136185,129714],{"class":693},[679,136187,119861],{"class":4508},[679,136189,4519],{"class":693},[679,136191,136192,136194,136196],{"class":681,"line":901},[679,136193,4586],{"class":693},[679,136195,119838],{"class":4508},[679,136197,4519],{"class":693},[651,136199,136200,136201,94061],{},"Next, configure the ",[676,136202,16242],{},[669,136204,136206],{"className":76589,"code":136205,"language":35538,"meta":674,"style":674},"spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}\nspring.ai.anthropic.chat.options.model=claude-3-5-sonnet-20240620\n",[676,136207,136208,136214],{"__ignoreMap":674},[679,136209,136210,136212],{"class":681,"line":682},[679,136211,129765],{"class":685},[679,136213,129768],{"class":693},[679,136215,136216,136218],{"class":681,"line":790},[679,136217,129773],{"class":685},[679,136219,129776],{"class":693},[651,136221,136222,136223,136226],{},"Make sure to set the ",[676,136224,136225],{},"ANTHROPIC_API_KEY"," environment variable with your actual API key.",[4542,136228,136230],{"id":136229},"building-the-chat-bot","Building the Chat Bot",[651,136232,136233,136234,136236],{},"Let's create a ",[676,136235,122514],{}," class to handle our chat requests:",[669,136238,136240],{"className":4107,"code":136239,"language":4109,"meta":674,"style":674},"@RestController\n@CrossOrigin\npublic class ChatController {\n private final ChatClient chatClient;\n\n public ChatController(ChatClient.Builder builder) {\n this.chatClient = builder.build();\n }\n\n @PostMapping(\"/chat\")\n public String chat(@RequestParam String message) {\n return chatClient.prompt()\n .user(message)\n .call()\n .content();\n }\n\n @GetMapping(\"/stream\")\n public Flux\u003CString> chatWithStream(@RequestParam String message) {\n return chatClient.prompt()\n .user(message)\n .stream()\n .content();\n }\n}\n",[676,136241,136242,136248,136254,136264,136272,136276,136288,136303,136307,136311,136323,136341,136351,136359,136367,136375,136379,136383,136395,136419,136429,136437,136445,136453,136457],{"__ignoreMap":674},[679,136243,136244,136246],{"class":681,"line":682},[679,136245,4116],{"class":693},[679,136247,9212],{"class":685},[679,136249,136250,136252],{"class":681,"line":790},[679,136251,4116],{"class":693},[679,136253,92395],{"class":685},[679,136255,136256,136258,136260,136262],{"class":681,"line":892},[679,136257,6073],{"class":685},[679,136259,4512],{"class":685},[679,136261,121635],{"class":880},[679,136263,884],{"class":693},[679,136265,136266,136268,136270],{"class":681,"line":901},[679,136267,9232],{"class":685},[679,136269,12768],{"class":685},[679,136271,121650],{"class":693},[679,136273,136274],{"class":681,"line":909},[679,136275,889],{"emptyLinePlaceholder":797},[679,136277,136278,136280,136282,136284,136286],{"class":681,"line":918},[679,136279,6089],{"class":685},[679,136281,121635],{"class":880},[679,136283,121663],{"class":693},[679,136285,90934],{"class":2099},[679,136287,4390],{"class":693},[679,136289,136290,136292,136294,136296,136299,136301],{"class":681,"line":935},[679,136291,7862],{"class":931},[679,136293,121674],{"class":693},[679,136295,686],{"class":685},[679,136297,136298],{"class":693}," builder.",[679,136300,23612],{"class":880},[679,136302,9317],{"class":693},[679,136304,136305],{"class":681,"line":944},[679,136306,985],{"class":693},[679,136308,136309],{"class":681,"line":959},[679,136310,889],{"emptyLinePlaceholder":797},[679,136312,136313,136315,136317,136319,136321],{"class":681,"line":964},[679,136314,6872],{"class":693},[679,136316,91165],{"class":685},[679,136318,745],{"class":693},[679,136320,123403],{"class":689},[679,136322,1339],{"class":693},[679,136324,136325,136327,136329,136331,136333,136335,136337,136339],{"class":681,"line":977},[679,136326,6089],{"class":685},[679,136328,9289],{"class":693},[679,136330,129981],{"class":880},[679,136332,73246],{"class":693},[679,136334,73249],{"class":685},[679,136336,9289],{"class":693},[679,136338,20198],{"class":2099},[679,136340,4390],{"class":693},[679,136342,136343,136345,136347,136349],{"class":681,"line":982},[679,136344,9444],{"class":685},[679,136346,121763],{"class":693},[679,136348,55494],{"class":880},[679,136350,17545],{"class":693},[679,136352,136353,136355,136357],{"class":681,"line":988},[679,136354,73482],{"class":693},[679,136356,9575],{"class":880},[679,136358,121776],{"class":693},[679,136360,136361,136363,136365],{"class":681,"line":993},[679,136362,73482],{"class":693},[679,136364,121783],{"class":880},[679,136366,17545],{"class":693},[679,136368,136369,136371,136373],{"class":681,"line":2129},[679,136370,73482],{"class":693},[679,136372,47833],{"class":880},[679,136374,9317],{"class":693},[679,136376,136377],{"class":681,"line":2140},[679,136378,985],{"class":693},[679,136380,136381],{"class":681,"line":2145},[679,136382,889],{"emptyLinePlaceholder":797},[679,136384,136385,136387,136389,136391,136393],{"class":681,"line":2154},[679,136386,6872],{"class":693},[679,136388,20852],{"class":685},[679,136390,745],{"class":693},[679,136392,123167],{"class":689},[679,136394,1339],{"class":693},[679,136396,136397,136399,136402,136404,136406,136409,136411,136413,136415,136417],{"class":681,"line":2159},[679,136398,6089],{"class":685},[679,136400,136401],{"class":693}," Flux\u003C",[679,136403,4758],{"class":685},[679,136405,20881],{"class":693},[679,136407,136408],{"class":880},"chatWithStream",[679,136410,73246],{"class":693},[679,136412,73249],{"class":685},[679,136414,9289],{"class":693},[679,136416,20198],{"class":2099},[679,136418,4390],{"class":693},[679,136420,136421,136423,136425,136427],{"class":681,"line":2164},[679,136422,9444],{"class":685},[679,136424,121763],{"class":693},[679,136426,55494],{"class":880},[679,136428,17545],{"class":693},[679,136430,136431,136433,136435],{"class":681,"line":3134},[679,136432,73482],{"class":693},[679,136434,9575],{"class":880},[679,136436,121776],{"class":693},[679,136438,136439,136441,136443],{"class":681,"line":3139},[679,136440,73482],{"class":693},[679,136442,87323],{"class":880},[679,136444,17545],{"class":693},[679,136446,136447,136449,136451],{"class":681,"line":3144},[679,136448,73482],{"class":693},[679,136450,47833],{"class":880},[679,136452,9317],{"class":693},[679,136454,136455],{"class":681,"line":3149},[679,136456,985],{"class":693},[679,136458,136459],{"class":681,"line":3169},[679,136460,996],{"class":693},[651,136462,136463],{},"This controller provides two endpoints:",[5316,136465,136466,136472],{},[5332,136467,136468,136471],{},[676,136469,136470],{},"/chat",": A traditional, non-streaming endpoint",[5332,136473,136474,136477,136478],{},[676,136475,136476],{},"/stream",": A streaming endpoint that returns a ",[676,136479,136480],{},"Flux\u003CString>",[651,136482,136483,136484,136487,136488,136491],{},"The key difference is in the use of ",[676,136485,136486],{},".call()"," for non-streaming and ",[676,136489,136490],{},".stream()"," for streaming responses.",[4542,136493,136495],{"id":136494},"frontend-implementation","Frontend Implementation",[651,136497,136498,136499,136501],{},"To demonstrate the difference between streaming and non-streaming responses, let's create two HTML files in the ",[676,136500,124823],{}," directory:",[27665,136503,136504],{},[5332,136505,136506,136508],{},[676,136507,57223],{}," (non-streaming):",[669,136510,136512],{"className":4496,"code":136511,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n \u003Cmeta charset=\"UTF-8\">\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003Ctitle>Dan's Chatbot\u003C/title>\n \u003Cscript src=\"https://unpkg.com/htmx.org@1.9.10\">\u003C/script>\n \u003Cscript src=\"https://cdn.tailwindcss.com\">\u003C/script>\n\u003C/head>\n\u003Cbody class=\"bg-gray-100 h-screen flex flex-col\">\n \u003Cdiv class=\"flex-1 overflow-y-auto p-4\">\n \u003Cdiv id=\"chat-messages\" class=\"space-y-4\">\n \u003Cpre>\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"p-4 bg-white\">\n \u003Cform hx-post=\"/chat\" hx-target=\"#chat-messages\" hx-swap=\"beforeend\">\n \u003Cdiv class=\"flex space-x-4\">\n \u003Cinput type=\"text\" name=\"message\" class=\"flex-1 border rounded p-2\" placeholder=\"Type your message...\">\n \u003Cbutton type=\"submit\" class=\"bg-blue-500 text-white px-4 py-2 rounded\">Send\u003C/button>\n \u003C/div>\n \u003C/form>\n \u003C/div>\n \u003Cscript>\n document.body.addEventListener('htmx:afterRequest', function(event) {\n if (event.detail.elt.getAttribute('name') === 'message') {\n event.detail.elt.value = '';\n }\n });\n \u003C/script>\n\u003C/body>\n\u003C/html>\n",[676,136513,136514,136524,136538,136546,136560,136580,136593,136612,136630,136638,136653,136668,136690,136702,136710,136718,136733,136760,136775,136809,136835,136843,136851,136859,136867,136889,136912,136923,136927,136931,136939,136947],{"__ignoreMap":674},[679,136515,136516,136518,136520,136522],{"class":681,"line":682},[679,136517,11904],{"class":693},[679,136519,11907],{"class":4508},[679,136521,11910],{"class":880},[679,136523,4519],{"class":693},[679,136525,136526,136528,136530,136532,136534,136536],{"class":681,"line":790},[679,136527,4505],{"class":693},[679,136529,4498],{"class":4508},[679,136531,18806],{"class":880},[679,136533,686],{"class":693},[679,136535,57251],{"class":689},[679,136537,4519],{"class":693},[679,136539,136540,136542,136544],{"class":681,"line":892},[679,136541,4505],{"class":693},[679,136543,11741],{"class":4508},[679,136545,4519],{"class":693},[679,136547,136548,136550,136552,136554,136556,136558],{"class":681,"line":901},[679,136549,4524],{"class":693},[679,136551,11968],{"class":4508},[679,136553,11971],{"class":880},[679,136555,686],{"class":693},[679,136557,47958],{"class":689},[679,136559,4519],{"class":693},[679,136561,136562,136564,136566,136568,136570,136572,136574,136576,136578],{"class":681,"line":909},[679,136563,4524],{"class":693},[679,136565,11968],{"class":4508},[679,136567,5283],{"class":880},[679,136569,686],{"class":693},[679,136571,12015],{"class":689},[679,136573,11995],{"class":880},[679,136575,686],{"class":693},[679,136577,48042],{"class":689},[679,136579,4519],{"class":693},[679,136581,136582,136584,136586,136589,136591],{"class":681,"line":918},[679,136583,4524],{"class":693},[679,136585,11750],{"class":4508},[679,136587,136588],{"class":693},">Dan's Chatbot\u003C/",[679,136590,11750],{"class":4508},[679,136592,4519],{"class":693},[679,136594,136595,136597,136599,136601,136603,136606,136608,136610],{"class":681,"line":935},[679,136596,4524],{"class":693},[679,136598,47668],{"class":4508},[679,136600,5361],{"class":880},[679,136602,686],{"class":693},[679,136604,136605],{"class":689},"\"https://unpkg.com/htmx.org@1.9.10\"",[679,136607,4563],{"class":693},[679,136609,47668],{"class":4508},[679,136611,4519],{"class":693},[679,136613,136614,136616,136618,136620,136622,136624,136626,136628],{"class":681,"line":944},[679,136615,4524],{"class":693},[679,136617,47668],{"class":4508},[679,136619,5361],{"class":880},[679,136621,686],{"class":693},[679,136623,124580],{"class":689},[679,136625,4563],{"class":693},[679,136627,47668],{"class":4508},[679,136629,4519],{"class":693},[679,136631,136632,136634,136636],{"class":681,"line":959},[679,136633,4586],{"class":693},[679,136635,11741],{"class":4508},[679,136637,4519],{"class":693},[679,136639,136640,136642,136644,136646,136648,136651],{"class":681,"line":964},[679,136641,4505],{"class":693},[679,136643,3006],{"class":4508},[679,136645,4512],{"class":880},[679,136647,686],{"class":693},[679,136649,136650],{"class":689},"\"bg-gray-100 h-screen flex flex-col\"",[679,136652,4519],{"class":693},[679,136654,136655,136657,136659,136661,136663,136666],{"class":681,"line":977},[679,136656,4524],{"class":693},[679,136658,4509],{"class":4508},[679,136660,4512],{"class":880},[679,136662,686],{"class":693},[679,136664,136665],{"class":689},"\"flex-1 overflow-y-auto p-4\"",[679,136667,4519],{"class":693},[679,136669,136670,136672,136674,136676,136678,136681,136683,136685,136688],{"class":681,"line":982},[679,136671,4904],{"class":693},[679,136673,4509],{"class":4508},[679,136675,5578],{"class":880},[679,136677,686],{"class":693},[679,136679,136680],{"class":689},"\"chat-messages\"",[679,136682,4512],{"class":880},[679,136684,686],{"class":693},[679,136686,136687],{"class":689},"\"space-y-4\"",[679,136689,4519],{"class":693},[679,136691,136692,136694,136696,136698,136700],{"class":681,"line":988},[679,136693,5392],{"class":693},[679,136695,669],{"class":4508},[679,136697,4563],{"class":693},[679,136699,669],{"class":4508},[679,136701,4519],{"class":693},[679,136703,136704,136706,136708],{"class":681,"line":993},[679,136705,5480],{"class":693},[679,136707,4509],{"class":4508},[679,136709,4519],{"class":693},[679,136711,136712,136714,136716],{"class":681,"line":2129},[679,136713,4577],{"class":693},[679,136715,4509],{"class":4508},[679,136717,4519],{"class":693},[679,136719,136720,136722,136724,136726,136728,136731],{"class":681,"line":2140},[679,136721,4524],{"class":693},[679,136723,4509],{"class":4508},[679,136725,4512],{"class":880},[679,136727,686],{"class":693},[679,136729,136730],{"class":689},"\"p-4 bg-white\"",[679,136732,4519],{"class":693},[679,136734,136735,136737,136739,136741,136743,136745,136747,136749,136752,136754,136756,136758],{"class":681,"line":2145},[679,136736,4904],{"class":693},[679,136738,134744],{"class":4508},[679,136740,134754],{"class":880},[679,136742,686],{"class":693},[679,136744,123403],{"class":689},[679,136746,134761],{"class":880},[679,136748,686],{"class":693},[679,136750,136751],{"class":689},"\"#chat-messages\"",[679,136753,134769],{"class":880},[679,136755,686],{"class":693},[679,136757,134774],{"class":689},[679,136759,4519],{"class":693},[679,136761,136762,136764,136766,136768,136770,136773],{"class":681,"line":2154},[679,136763,5392],{"class":693},[679,136765,4509],{"class":4508},[679,136767,4512],{"class":880},[679,136769,686],{"class":693},[679,136771,136772],{"class":689},"\"flex space-x-4\"",[679,136774,4519],{"class":693},[679,136776,136777,136779,136781,136783,136785,136787,136789,136791,136793,136795,136797,136800,136802,136804,136807],{"class":681,"line":2159},[679,136778,125341],{"class":693},[679,136780,27722],{"class":4508},[679,136782,27725],{"class":880},[679,136784,686],{"class":693},[679,136786,54445],{"class":689},[679,136788,5283],{"class":880},[679,136790,686],{"class":693},[679,136792,135653],{"class":689},[679,136794,4512],{"class":880},[679,136796,686],{"class":693},[679,136798,136799],{"class":689},"\"flex-1 border rounded p-2\"",[679,136801,54448],{"class":880},[679,136803,686],{"class":693},[679,136805,136806],{"class":689},"\"Type your message...\"",[679,136808,4519],{"class":693},[679,136810,136811,136813,136815,136817,136819,136821,136823,136825,136828,136831,136833],{"class":681,"line":2164},[679,136812,125341],{"class":693},[679,136814,54258],{"class":4508},[679,136816,27725],{"class":880},[679,136818,686],{"class":693},[679,136820,134831],{"class":689},[679,136822,4512],{"class":880},[679,136824,686],{"class":693},[679,136826,136827],{"class":689},"\"bg-blue-500 text-white px-4 py-2 rounded\"",[679,136829,136830],{"class":693},">Send\u003C/",[679,136832,54258],{"class":4508},[679,136834,4519],{"class":693},[679,136836,136837,136839,136841],{"class":681,"line":3134},[679,136838,125356],{"class":693},[679,136840,4509],{"class":4508},[679,136842,4519],{"class":693},[679,136844,136845,136847,136849],{"class":681,"line":3139},[679,136846,5480],{"class":693},[679,136848,134744],{"class":4508},[679,136850,4519],{"class":693},[679,136852,136853,136855,136857],{"class":681,"line":3144},[679,136854,4577],{"class":693},[679,136856,4509],{"class":4508},[679,136858,4519],{"class":693},[679,136860,136861,136863,136865],{"class":681,"line":3149},[679,136862,4524],{"class":693},[679,136864,47668],{"class":4508},[679,136866,4519],{"class":693},[679,136868,136869,136872,136874,136876,136879,136881,136883,136885,136887],{"class":681,"line":3169},[679,136870,136871],{"class":693}," document.body.",[679,136873,64879],{"class":880},[679,136875,745],{"class":693},[679,136877,136878],{"class":689},"'htmx:afterRequest'",[679,136880,2797],{"class":693},[679,136882,55109],{"class":685},[679,136884,745],{"class":693},[679,136886,51092],{"class":2099},[679,136888,4390],{"class":693},[679,136890,136891,136893,136896,136898,136900,136903,136905,136907,136910],{"class":681,"line":3185},[679,136892,112604],{"class":685},[679,136894,136895],{"class":693}," (event.detail.elt.",[679,136897,73950],{"class":880},[679,136899,745],{"class":693},[679,136901,136902],{"class":689},"'name'",[679,136904,2378],{"class":693},[679,136906,51108],{"class":685},[679,136908,136909],{"class":689}," 'message'",[679,136911,4390],{"class":693},[679,136913,136914,136917,136919,136921],{"class":681,"line":3194},[679,136915,136916],{"class":693}," event.detail.elt.value ",[679,136918,686],{"class":685},[679,136920,2216],{"class":689},[679,136922,1186],{"class":693},[679,136924,136925],{"class":681,"line":3199},[679,136926,25517],{"class":693},[679,136928,136929],{"class":681,"line":3212},[679,136930,131556],{"class":693},[679,136932,136933,136935,136937],{"class":681,"line":3217},[679,136934,4577],{"class":693},[679,136936,47668],{"class":4508},[679,136938,4519],{"class":693},[679,136940,136941,136943,136945],{"class":681,"line":3222},[679,136942,4586],{"class":693},[679,136944,3006],{"class":4508},[679,136946,4519],{"class":693},[679,136948,136949,136951,136953],{"class":681,"line":3227},[679,136950,4586],{"class":693},[679,136952,4498],{"class":4508},[679,136954,4519],{"class":693},[27665,136956,136957],{"start":790},[5332,136958,136959,136962],{},[676,136960,136961],{},"stream.html"," (streaming):",[669,136964,136966],{"className":4496,"code":136965,"language":4498,"meta":674,"style":674},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n \u003Cmeta charset=\"UTF-8\">\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003Ctitle>Dan's Streaming Chatbot\u003C/title>\n \u003Cscript src=\"https://cdn.tailwindcss.com\">\u003C/script>\n\u003C/head>\n\u003Cbody class=\"bg-gray-100 h-screen flex flex-col\">\n \u003Cdiv class=\"flex-1 overflow-y-auto p-4\">\n \u003Cdiv id=\"chat-messages\" class=\"space-y-4\">\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"p-4 bg-white\">\n \u003Cform id=\"chat-form\">\n \u003Cdiv class=\"flex space-x-4\">\n \u003Cinput type=\"text\" id=\"message-input\" class=\"flex-1 border rounded p-2\" placeholder=\"Type your message...\">\n \u003Cbutton type=\"submit\" class=\"bg-blue-500 text-white px-4 py-2 rounded\">Send\u003C/button>\n \u003C/div>\n \u003C/form>\n \u003C/div>\n \u003Cscript>\n const chatMessages = document.getElementById('chat-messages');\n const chatForm = document.getElementById('chat-form');\n const messageInput = document.getElementById('message-input');\n\n chatForm.addEventListener('submit', async (e) => {\n e.preventDefault();\n const message = messageInput.value;\n messageInput.value = '';\n\n // Add user message to chat\n chatMessages.innerHTML += `\u003Cdiv class=\"bg-blue-100 p-2 rounded\">\u003Cstrong>You:\u003C/strong> ${message}\u003C/div>`;\n\n // Add AI message container\n const aiMessageContainer = document.createElement('div');\n aiMessageContainer.className = 'bg-green-100 p-2 rounded';\n aiMessageContainer.innerHTML = '\u003Cstrong>AI:\u003C/strong> ';\n const aiMessageContent = document.createElement('span');\n aiMessageContainer.appendChild(aiMessageContent);\n chatMessages.appendChild(aiMessageContainer);\n\n try {\n const response = await fetchStreamWithRetry(`/stream?message=${encodeURIComponent(message)}`);\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n const decodedChunk = decoder.decode(value, { stream: true });\n aiMessageContent.textContent += decodedChunk;\n }\n } catch (error) {\n console.error('Error:', error);\n aiMessageContent.textContent += 'Error occurred while fetching the response.';\n }\n\n chatMessages.scrollTop = chatMessages.scrollHeight;\n });\n\n async function fetchStreamWithRetry(url, retries = 3) {\n for (let i = 0; i \u003C retries; i++) {\n try {\n const response = await fetch(url);\n if (response.ok) return response;\n } catch (error) {\n if (i === retries - 1) throw error;\n }\n }\n }\n \u003C/script>\n\u003C/body>\n\u003C/html>\n",[676,136967,136968,136978,136992,137000,137014,137034,137047,137065,137073,137087,137101,137125,137133,137147,137162,137176,137209,137233,137241,137249,137257,137265,137285,137305,137325,137329,137355,137365,137377,137388,137392,137397,137414,137418,137423,137443,137455,137467,137487,137497,137507,137511,137517,137550,137567,137582,137586,137597,137624,137637,137658,137668,137672,137681,137696,137708,137712,137716,137726,137730,137734,137758,137784,137791,137806,137818,137827,137851,137855,137859,137863,137871,137879],{"__ignoreMap":674},[679,136969,136970,136972,136974,136976],{"class":681,"line":682},[679,136971,11904],{"class":693},[679,136973,11907],{"class":4508},[679,136975,11910],{"class":880},[679,136977,4519],{"class":693},[679,136979,136980,136982,136984,136986,136988,136990],{"class":681,"line":790},[679,136981,4505],{"class":693},[679,136983,4498],{"class":4508},[679,136985,18806],{"class":880},[679,136987,686],{"class":693},[679,136989,57251],{"class":689},[679,136991,4519],{"class":693},[679,136993,136994,136996,136998],{"class":681,"line":892},[679,136995,4505],{"class":693},[679,136997,11741],{"class":4508},[679,136999,4519],{"class":693},[679,137001,137002,137004,137006,137008,137010,137012],{"class":681,"line":901},[679,137003,4524],{"class":693},[679,137005,11968],{"class":4508},[679,137007,11971],{"class":880},[679,137009,686],{"class":693},[679,137011,47958],{"class":689},[679,137013,4519],{"class":693},[679,137015,137016,137018,137020,137022,137024,137026,137028,137030,137032],{"class":681,"line":909},[679,137017,4524],{"class":693},[679,137019,11968],{"class":4508},[679,137021,5283],{"class":880},[679,137023,686],{"class":693},[679,137025,12015],{"class":689},[679,137027,11995],{"class":880},[679,137029,686],{"class":693},[679,137031,48042],{"class":689},[679,137033,4519],{"class":693},[679,137035,137036,137038,137040,137043,137045],{"class":681,"line":918},[679,137037,4524],{"class":693},[679,137039,11750],{"class":4508},[679,137041,137042],{"class":693},">Dan's Streaming Chatbot\u003C/",[679,137044,11750],{"class":4508},[679,137046,4519],{"class":693},[679,137048,137049,137051,137053,137055,137057,137059,137061,137063],{"class":681,"line":935},[679,137050,4524],{"class":693},[679,137052,47668],{"class":4508},[679,137054,5361],{"class":880},[679,137056,686],{"class":693},[679,137058,124580],{"class":689},[679,137060,4563],{"class":693},[679,137062,47668],{"class":4508},[679,137064,4519],{"class":693},[679,137066,137067,137069,137071],{"class":681,"line":944},[679,137068,4586],{"class":693},[679,137070,11741],{"class":4508},[679,137072,4519],{"class":693},[679,137074,137075,137077,137079,137081,137083,137085],{"class":681,"line":959},[679,137076,4505],{"class":693},[679,137078,3006],{"class":4508},[679,137080,4512],{"class":880},[679,137082,686],{"class":693},[679,137084,136650],{"class":689},[679,137086,4519],{"class":693},[679,137088,137089,137091,137093,137095,137097,137099],{"class":681,"line":964},[679,137090,4524],{"class":693},[679,137092,4509],{"class":4508},[679,137094,4512],{"class":880},[679,137096,686],{"class":693},[679,137098,136665],{"class":689},[679,137100,4519],{"class":693},[679,137102,137103,137105,137107,137109,137111,137113,137115,137117,137119,137121,137123],{"class":681,"line":977},[679,137104,4904],{"class":693},[679,137106,4509],{"class":4508},[679,137108,5578],{"class":880},[679,137110,686],{"class":693},[679,137112,136680],{"class":689},[679,137114,4512],{"class":880},[679,137116,686],{"class":693},[679,137118,136687],{"class":689},[679,137120,4563],{"class":693},[679,137122,4509],{"class":4508},[679,137124,4519],{"class":693},[679,137126,137127,137129,137131],{"class":681,"line":982},[679,137128,4577],{"class":693},[679,137130,4509],{"class":4508},[679,137132,4519],{"class":693},[679,137134,137135,137137,137139,137141,137143,137145],{"class":681,"line":988},[679,137136,4524],{"class":693},[679,137138,4509],{"class":4508},[679,137140,4512],{"class":880},[679,137142,686],{"class":693},[679,137144,136730],{"class":689},[679,137146,4519],{"class":693},[679,137148,137149,137151,137153,137155,137157,137160],{"class":681,"line":993},[679,137150,4904],{"class":693},[679,137152,134744],{"class":4508},[679,137154,5578],{"class":880},[679,137156,686],{"class":693},[679,137158,137159],{"class":689},"\"chat-form\"",[679,137161,4519],{"class":693},[679,137163,137164,137166,137168,137170,137172,137174],{"class":681,"line":2129},[679,137165,5392],{"class":693},[679,137167,4509],{"class":4508},[679,137169,4512],{"class":880},[679,137171,686],{"class":693},[679,137173,136772],{"class":689},[679,137175,4519],{"class":693},[679,137177,137178,137180,137182,137184,137186,137188,137190,137192,137195,137197,137199,137201,137203,137205,137207],{"class":681,"line":2140},[679,137179,125341],{"class":693},[679,137181,27722],{"class":4508},[679,137183,27725],{"class":880},[679,137185,686],{"class":693},[679,137187,54445],{"class":689},[679,137189,5578],{"class":880},[679,137191,686],{"class":693},[679,137193,137194],{"class":689},"\"message-input\"",[679,137196,4512],{"class":880},[679,137198,686],{"class":693},[679,137200,136799],{"class":689},[679,137202,54448],{"class":880},[679,137204,686],{"class":693},[679,137206,136806],{"class":689},[679,137208,4519],{"class":693},[679,137210,137211,137213,137215,137217,137219,137221,137223,137225,137227,137229,137231],{"class":681,"line":2145},[679,137212,125341],{"class":693},[679,137214,54258],{"class":4508},[679,137216,27725],{"class":880},[679,137218,686],{"class":693},[679,137220,134831],{"class":689},[679,137222,4512],{"class":880},[679,137224,686],{"class":693},[679,137226,136827],{"class":689},[679,137228,136830],{"class":693},[679,137230,54258],{"class":4508},[679,137232,4519],{"class":693},[679,137234,137235,137237,137239],{"class":681,"line":2154},[679,137236,125356],{"class":693},[679,137238,4509],{"class":4508},[679,137240,4519],{"class":693},[679,137242,137243,137245,137247],{"class":681,"line":2159},[679,137244,5480],{"class":693},[679,137246,134744],{"class":4508},[679,137248,4519],{"class":693},[679,137250,137251,137253,137255],{"class":681,"line":2164},[679,137252,4577],{"class":693},[679,137254,4509],{"class":4508},[679,137256,4519],{"class":693},[679,137258,137259,137261,137263],{"class":681,"line":3134},[679,137260,4524],{"class":693},[679,137262,47668],{"class":4508},[679,137264,4519],{"class":693},[679,137266,137267,137269,137272,137274,137276,137278,137280,137283],{"class":681,"line":3139},[679,137268,47128],{"class":685},[679,137270,137271],{"class":931}," chatMessages",[679,137273,6883],{"class":685},[679,137275,46823],{"class":693},[679,137277,46913],{"class":880},[679,137279,745],{"class":693},[679,137281,137282],{"class":689},"'chat-messages'",[679,137284,1208],{"class":693},[679,137286,137287,137289,137292,137294,137296,137298,137300,137303],{"class":681,"line":3144},[679,137288,47128],{"class":685},[679,137290,137291],{"class":931}," chatForm",[679,137293,6883],{"class":685},[679,137295,46823],{"class":693},[679,137297,46913],{"class":880},[679,137299,745],{"class":693},[679,137301,137302],{"class":689},"'chat-form'",[679,137304,1208],{"class":693},[679,137306,137307,137309,137312,137314,137316,137318,137320,137323],{"class":681,"line":3149},[679,137308,47128],{"class":685},[679,137310,137311],{"class":931}," messageInput",[679,137313,6883],{"class":685},[679,137315,46823],{"class":693},[679,137317,46913],{"class":880},[679,137319,745],{"class":693},[679,137321,137322],{"class":689},"'message-input'",[679,137324,1208],{"class":693},[679,137326,137327],{"class":681,"line":3169},[679,137328,889],{"emptyLinePlaceholder":797},[679,137330,137331,137334,137336,137338,137341,137343,137345,137347,137349,137351,137353],{"class":681,"line":3185},[679,137332,137333],{"class":693}," chatForm.",[679,137335,64879],{"class":880},[679,137337,745],{"class":693},[679,137339,137340],{"class":689},"'submit'",[679,137342,2797],{"class":693},[679,137344,55212],{"class":685},[679,137346,4193],{"class":693},[679,137348,9400],{"class":2099},[679,137350,2378],{"class":693},[679,137352,21350],{"class":685},[679,137354,884],{"class":693},[679,137356,137357,137360,137363],{"class":681,"line":3194},[679,137358,137359],{"class":693}," e.",[679,137361,137362],{"class":880},"preventDefault",[679,137364,9317],{"class":693},[679,137366,137367,137370,137372,137374],{"class":681,"line":3199},[679,137368,137369],{"class":685}," const",[679,137371,74854],{"class":931},[679,137373,6883],{"class":685},[679,137375,137376],{"class":693}," messageInput.value;\n",[679,137378,137379,137382,137384,137386],{"class":681,"line":3212},[679,137380,137381],{"class":693}," messageInput.value ",[679,137383,686],{"class":685},[679,137385,2216],{"class":689},[679,137387,1186],{"class":693},[679,137389,137390],{"class":681,"line":3217},[679,137391,889],{"emptyLinePlaceholder":797},[679,137393,137394],{"class":681,"line":3222},[679,137395,137396],{"class":1400}," // Add user message to chat\n",[679,137398,137399,137402,137404,137407,137409,137412],{"class":681,"line":3227},[679,137400,137401],{"class":693}," chatMessages.innerHTML ",[679,137403,50520],{"class":685},[679,137405,137406],{"class":689}," `\u003Cdiv class=\"bg-blue-100 p-2 rounded\">\u003Cstrong>You:\u003C/strong> ${",[679,137408,20198],{"class":693},[679,137410,137411],{"class":689},"}\u003C/div>`",[679,137413,1186],{"class":693},[679,137415,137416],{"class":681,"line":3232},[679,137417,889],{"emptyLinePlaceholder":797},[679,137419,137420],{"class":681,"line":3499},[679,137421,137422],{"class":1400}," // Add AI message container\n",[679,137424,137425,137427,137430,137432,137434,137436,137438,137441],{"class":681,"line":3509},[679,137426,137369],{"class":685},[679,137428,137429],{"class":931}," aiMessageContainer",[679,137431,6883],{"class":685},[679,137433,46823],{"class":693},[679,137435,45903],{"class":880},[679,137437,745],{"class":693},[679,137439,137440],{"class":689},"'div'",[679,137442,1208],{"class":693},[679,137444,137445,137448,137450,137453],{"class":681,"line":3516},[679,137446,137447],{"class":693}," aiMessageContainer.className ",[679,137449,686],{"class":685},[679,137451,137452],{"class":689}," 'bg-green-100 p-2 rounded'",[679,137454,1186],{"class":693},[679,137456,137457,137460,137462,137465],{"class":681,"line":3531},[679,137458,137459],{"class":693}," aiMessageContainer.innerHTML ",[679,137461,686],{"class":685},[679,137463,137464],{"class":689}," '\u003Cstrong>AI:\u003C/strong> '",[679,137466,1186],{"class":693},[679,137468,137469,137471,137474,137476,137478,137480,137482,137485],{"class":681,"line":3536},[679,137470,137369],{"class":685},[679,137472,137473],{"class":931}," aiMessageContent",[679,137475,6883],{"class":685},[679,137477,46823],{"class":693},[679,137479,45903],{"class":880},[679,137481,745],{"class":693},[679,137483,137484],{"class":689},"'span'",[679,137486,1208],{"class":693},[679,137488,137489,137492,137494],{"class":681,"line":3541},[679,137490,137491],{"class":693}," aiMessageContainer.",[679,137493,47285],{"class":880},[679,137495,137496],{"class":693},"(aiMessageContent);\n",[679,137498,137499,137502,137504],{"class":681,"line":3546},[679,137500,137501],{"class":693}," chatMessages.",[679,137503,47285],{"class":880},[679,137505,137506],{"class":693},"(aiMessageContainer);\n",[679,137508,137509],{"class":681,"line":3551},[679,137510,889],{"emptyLinePlaceholder":797},[679,137512,137513,137515],{"class":681,"line":3557},[679,137514,34414],{"class":685},[679,137516,884],{"class":693},[679,137518,137519,137522,137525,137527,137529,137532,137534,137537,137540,137542,137544,137546,137548],{"class":681,"line":3567},[679,137520,137521],{"class":685}," const",[679,137523,137524],{"class":931}," response",[679,137526,6883],{"class":685},[679,137528,55488],{"class":685},[679,137530,137531],{"class":880}," fetchStreamWithRetry",[679,137533,745],{"class":693},[679,137535,137536],{"class":689},"`/stream?message=${",[679,137538,137539],{"class":880},"encodeURIComponent",[679,137541,745],{"class":689},[679,137543,20198],{"class":693},[679,137545,50653],{"class":689},[679,137547,47580],{"class":689},[679,137549,1208],{"class":693},[679,137551,137552,137554,137557,137559,137562,137565],{"class":681,"line":3574},[679,137553,137521],{"class":685},[679,137555,137556],{"class":931}," reader",[679,137558,6883],{"class":685},[679,137560,137561],{"class":693}," response.body.",[679,137563,137564],{"class":880},"getReader",[679,137566,9317],{"class":693},[679,137568,137569,137571,137574,137576,137578,137580],{"class":681,"line":3589},[679,137570,137521],{"class":685},[679,137572,137573],{"class":931}," decoder",[679,137575,6883],{"class":685},[679,137577,2054],{"class":685},[679,137579,77123],{"class":880},[679,137581,9317],{"class":693},[679,137583,137584],{"class":681,"line":3594},[679,137585,889],{"emptyLinePlaceholder":797},[679,137587,137588,137591,137593,137595],{"class":681,"line":3602},[679,137589,137590],{"class":685}," while",[679,137592,4193],{"class":693},[679,137594,3441],{"class":931},[679,137596,4390],{"class":693},[679,137598,137599,137602,137604,137606,137608,137611,137613,137615,137617,137620,137622],{"class":681,"line":3608},[679,137600,137601],{"class":685}," const",[679,137603,2998],{"class":693},[679,137605,19934],{"class":931},[679,137607,2797],{"class":693},[679,137609,137610],{"class":931},"done",[679,137612,55483],{"class":693},[679,137614,686],{"class":685},[679,137616,55488],{"class":685},[679,137618,137619],{"class":693}," reader.",[679,137621,77021],{"class":880},[679,137623,9317],{"class":693},[679,137625,137626,137629,137632,137635],{"class":681,"line":3619},[679,137627,137628],{"class":685}," if",[679,137630,137631],{"class":693}," (done) ",[679,137633,137634],{"class":685},"break",[679,137636,1186],{"class":693},[679,137638,137639,137641,137644,137646,137649,137651,137654,137656],{"class":681,"line":3624},[679,137640,137601],{"class":685},[679,137642,137643],{"class":931}," decodedChunk",[679,137645,6883],{"class":685},[679,137647,137648],{"class":693}," decoder.",[679,137650,77044],{"class":880},[679,137652,137653],{"class":693},"(value, { stream: ",[679,137655,3441],{"class":931},[679,137657,49189],{"class":693},[679,137659,137660,137663,137665],{"class":681,"line":3629},[679,137661,137662],{"class":693}," aiMessageContent.textContent ",[679,137664,50520],{"class":685},[679,137666,137667],{"class":693}," decodedChunk;\n",[679,137669,137670],{"class":681,"line":3639},[679,137671,106697],{"class":693},[679,137673,137674,137676,137678],{"class":681,"line":3644},[679,137675,34465],{"class":693},[679,137677,9394],{"class":685},[679,137679,137680],{"class":693}," (error) {\n",[679,137682,137683,137686,137688,137690,137693],{"class":681,"line":3649},[679,137684,137685],{"class":693}," console.",[679,137687,29446],{"class":880},[679,137689,745],{"class":693},[679,137691,137692],{"class":689},"'Error:'",[679,137694,137695],{"class":693},", error);\n",[679,137697,137698,137701,137703,137706],{"class":681,"line":3659},[679,137699,137700],{"class":693}," aiMessageContent.textContent ",[679,137702,50520],{"class":685},[679,137704,137705],{"class":689}," 'Error occurred while fetching the response.'",[679,137707,1186],{"class":693},[679,137709,137710],{"class":681,"line":3664},[679,137711,25517],{"class":693},[679,137713,137714],{"class":681,"line":3669},[679,137715,889],{"emptyLinePlaceholder":797},[679,137717,137718,137721,137723],{"class":681,"line":3679},[679,137719,137720],{"class":693}," chatMessages.scrollTop ",[679,137722,686],{"class":685},[679,137724,137725],{"class":693}," chatMessages.scrollHeight;\n",[679,137727,137728],{"class":681,"line":3684},[679,137729,131556],{"class":693},[679,137731,137732],{"class":681,"line":3689},[679,137733,889],{"emptyLinePlaceholder":797},[679,137735,137736,137739,137741,137743,137745,137747,137749,137752,137754,137756],{"class":681,"line":3699},[679,137737,137738],{"class":685}," async",[679,137740,21700],{"class":685},[679,137742,137531],{"class":880},[679,137744,745],{"class":693},[679,137746,101711],{"class":2099},[679,137748,2797],{"class":693},[679,137750,137751],{"class":2099},"retries",[679,137753,6883],{"class":685},[679,137755,55460],{"class":931},[679,137757,4390],{"class":693},[679,137759,137760,137763,137765,137767,137769,137771,137773,137775,137777,137780,137782],{"class":681,"line":3704},[679,137761,137762],{"class":685}," for",[679,137764,4193],{"class":693},[679,137766,51795],{"class":685},[679,137768,36971],{"class":693},[679,137770,686],{"class":685},[679,137772,14987],{"class":931},[679,137774,59465],{"class":693},[679,137776,4505],{"class":685},[679,137778,137779],{"class":693}," retries; i",[679,137781,1569],{"class":685},[679,137783,4390],{"class":693},[679,137785,137786,137789],{"class":681,"line":3709},[679,137787,137788],{"class":685}," try",[679,137790,884],{"class":693},[679,137792,137793,137795,137797,137799,137801,137803],{"class":681,"line":3719},[679,137794,137601],{"class":685},[679,137796,137524],{"class":931},[679,137798,6883],{"class":685},[679,137800,55488],{"class":685},[679,137802,75114],{"class":880},[679,137804,137805],{"class":693},"(url);\n",[679,137807,137808,137810,137813,137815],{"class":681,"line":3724},[679,137809,137628],{"class":685},[679,137811,137812],{"class":693}," (response.ok) ",[679,137814,1307],{"class":685},[679,137816,137817],{"class":693}," response;\n",[679,137819,137820,137823,137825],{"class":681,"line":3729},[679,137821,137822],{"class":693}," } ",[679,137824,9394],{"class":685},[679,137826,137680],{"class":693},[679,137828,137829,137831,137834,137836,137839,137841,137843,137845,137848],{"class":681,"line":3739},[679,137830,137628],{"class":685},[679,137832,137833],{"class":693}," (i ",[679,137835,51108],{"class":685},[679,137837,137838],{"class":693}," retries ",[679,137840,6453],{"class":685},[679,137842,48606],{"class":931},[679,137844,2378],{"class":693},[679,137846,137847],{"class":685},"throw",[679,137849,137850],{"class":693}," error;\n",[679,137852,137853],{"class":681,"line":3744},[679,137854,106697],{"class":693},[679,137856,137857],{"class":681,"line":3749},[679,137858,25517],{"class":693},[679,137860,137861],{"class":681,"line":3759},[679,137862,1290],{"class":693},[679,137864,137865,137867,137869],{"class":681,"line":3764},[679,137866,4577],{"class":693},[679,137868,47668],{"class":4508},[679,137870,4519],{"class":693},[679,137872,137873,137875,137877],{"class":681,"line":3769},[679,137874,4586],{"class":693},[679,137876,3006],{"class":4508},[679,137878,4519],{"class":693},[679,137880,137881,137883,137885],{"class":681,"line":3779},[679,137882,4586],{"class":693},[679,137884,4498],{"class":4508},[679,137886,4519],{"class":693},[4542,137888,137890],{"id":137889},"demonstrating-the-chat-bot","Demonstrating the Chat Bot",[651,137892,137893],{},"Now that we have both streaming and non-streaming implementations, let's compare them:",[27665,137895,137896,137899,137909],{},[5332,137897,137898],{},"Run the Spring Boot application.",[5332,137900,137901,137902,137904,137905,137908],{},"Open ",[676,137903,11697],{}," for the non-streaming version and ",[676,137906,137907],{},"http://localhost:8080/stream.html"," for the streaming version.",[5332,137910,137911],{},"Try asking the same question on both pages, such as \"Write a short overview of the Java programming language and what it is used for.\"",[651,137913,137914],{},"You'll notice that the non-streaming version waits for the entire response before displaying it, while the streaming version shows the response as it's being generated. This immediate feedback creates a much better user experience, especially for longer responses.",[4542,137916,9042],{"id":9041},[651,137918,137919],{},"Building a streaming chat bot with Spring Boot and Spring AI is surprisingly simple. The key advantages of this approach include:",[27665,137921,137922,137928,137933,137939],{},[5332,137923,137924,137927],{},[2939,137925,137926],{},"Better user experience",": Immediate feedback keeps users engaged.",[5332,137929,137930,137932],{},[2939,137931,129642],{},": Spring AI's portable API makes it easy to switch between different LLMs.",[5332,137934,137935,137938],{},[2939,137936,137937],{},"Simplicity",": The Spring ecosystem simplifies configuration and implementation.",[5332,137940,137941,137943],{},[2939,137942,129636],{},": Built on Spring WebFlux, the streaming solution can handle many concurrent connections efficiently.",[651,137945,137946],{},"As AI becomes more integral to our applications, tools like Spring AI will be crucial in simplifying integration and improving user experiences. I encourage you to explore Spring AI further and consider how streaming responses can enhance your own AI-powered applications.",[651,137948,137949],{},"Remember, the world of AI is rapidly evolving, and Spring AI is keeping pace. Stay curious, keep experimenting, and happy coding!",[786,137951,137952],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":137954},[137955,137956,137957,137958,137959,137960,137961,137962],{"id":136087,"depth":790,"text":136088},{"id":136094,"depth":790,"text":136095},{"id":94280,"depth":790,"text":94281},{"id":136145,"depth":790,"text":136146},{"id":136229,"depth":790,"text":136230},{"id":136494,"depth":790,"text":136495},{"id":137889,"depth":790,"text":137890},{"id":9041,"depth":790,"text":9042},"Learn how to create a responsive and engaging chat bot using Spring Boot and Spring AI, featuring both traditional and streaming response capabilities for an enhanced user experience.",{"slug":137965,"date":137966,"published":797,"author":798,"tags":137967,"video":137968,"keywords":137969},"spring-ai-streaming-chatbot","2024-10-08T20:00:00.000Z",[7077,36422,123549],"https://www.youtube.com/embed/q2p0mG4RICM","Spring Framework, Spring Boot, Java, Spring AI, Streaming, Chat Bot, LLM, Anthropic, Claude, WebFlux",{"title":60,"description":137963},"blog/2024/10/08/spring-ai-streaming-chatbot","cUfdg93INPHIvmvFE1vOZWyX0EVxHrRq-REanVJrGqg",{"id":137974,"title":57,"body":137975,"description":138661,"extension":793,"meta":138662,"navigation":797,"path":58,"seo":138669,"stem":138670,"__hash__":138671},"content/blog/2024/10/11/spring-ai-chat-memory.md",{"type":648,"value":137976,"toc":138648},[137977,137980,137984,137987,137990,137993,137996,138010,138013,138017,138020,138023,138030,138036,138139,138144,138163,138166,138170,138175,138342,138345,138376,138384,138386,138389,138480,138483,138487,138490,138517,138520,138531,138535,138538,138542,138545,138559,138563,138566,138574,138577,138588,138592,138595,138621,138624,138626,138634,138637,138640,138643,138646],[651,137978,137979],{},"As artificial intelligence continues to reshape the software development landscape, creating intelligent and context-aware chatbots has become increasingly important. In this tutorial, we'll explore how to build a chatbot with memory capabilities using Spring AI and OpenAI. By the end, you'll have a solid understanding of how to create a conversational AI application that can maintain context across multiple interactions.",[4542,137981,137983],{"id":137982},"understanding-chat-memory-in-ai-applications","Understanding Chat Memory in AI Applications",[651,137985,137986],{},"Before we dive into the code, it's crucial to understand the concept of chat memory and why it's essential for building effective conversational AI applications.",[651,137988,137989],{},"When you interact with a large language model (LLM) like GPT-4, the model itself doesn't inherently remember previous interactions. Each request is processed independently, without any context from prior conversations. This stateless nature can lead to disjointed and less engaging interactions.",[651,137991,137992],{},"However, products built on top of these LLMs, like ChatGPT, implement chat memory by storing and managing conversation history. This allows the AI to reference previous messages and maintain context throughout a conversation, resulting in more coherent and contextually relevant responses.",[651,137994,137995],{},"For example, consider this interaction:",[27665,137997,137998,138001,138004,138007],{},[5332,137999,138000],{},"User: \"My name is Alice.\"",[5332,138002,138003],{},"AI: \"Hello Alice! How can I assist you today?\"",[5332,138005,138006],{},"User: \"What's my name?\"",[5332,138008,138009],{},"AI: \"Your name is Alice.\"",[651,138011,138012],{},"In this scenario, the AI remembers the user's name from the first interaction and can recall it in subsequent messages. This is not an inherent capability of the LLM but rather a feature implemented by the chat application.",[4542,138014,138016],{"id":138015},"setting-up-the-spring-ai-project","Setting Up the Spring AI Project",[651,138018,138019],{},"Let's start by setting up a new Spring Boot project with Spring AI. We'll use Maven as our build tool and Java 23 as our programming language.",[651,138021,138022],{},"First, create a new Spring Boot project using Spring Initializr or your preferred IDE. Make sure to include the following dependencies:",[5316,138024,138025,138027],{},[5332,138026,80827],{},[5332,138028,138029],{},"Spring AI OpenAI",[651,138031,138032,138033,138035],{},"Your ",[676,138034,81517],{}," file should include these dependencies:",[669,138037,138039],{"className":9101,"code":138038,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.ai\u003C/groupId>\n \u003CartifactId>spring-ai-openai-spring-boot-starter\u003C/artifactId>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,138040,138041,138049,138057,138069,138082,138090,138098,138110,138123,138131],{"__ignoreMap":674},[679,138042,138043,138045,138047],{"class":681,"line":682},[679,138044,4505],{"class":693},[679,138046,129682],{"class":4508},[679,138048,4519],{"class":693},[679,138050,138051,138053,138055],{"class":681,"line":790},[679,138052,4524],{"class":693},[679,138054,119838],{"class":4508},[679,138056,4519],{"class":693},[679,138058,138059,138061,138063,138065,138067],{"class":681,"line":892},[679,138060,4904],{"class":693},[679,138062,119847],{"class":4508},[679,138064,119850],{"class":693},[679,138066,119847],{"class":4508},[679,138068,4519],{"class":693},[679,138070,138071,138073,138075,138078,138080],{"class":681,"line":901},[679,138072,4904],{"class":693},[679,138074,119861],{"class":4508},[679,138076,138077],{"class":693},">spring-boot-starter-web\u003C/",[679,138079,119861],{"class":4508},[679,138081,4519],{"class":693},[679,138083,138084,138086,138088],{"class":681,"line":909},[679,138085,4577],{"class":693},[679,138087,119838],{"class":4508},[679,138089,4519],{"class":693},[679,138091,138092,138094,138096],{"class":681,"line":918},[679,138093,4524],{"class":693},[679,138095,119838],{"class":4508},[679,138097,4519],{"class":693},[679,138099,138100,138102,138104,138106,138108],{"class":681,"line":935},[679,138101,4904],{"class":693},[679,138103,119847],{"class":4508},[679,138105,129701],{"class":693},[679,138107,119847],{"class":4508},[679,138109,4519],{"class":693},[679,138111,138112,138114,138116,138119,138121],{"class":681,"line":944},[679,138113,4904],{"class":693},[679,138115,119861],{"class":4508},[679,138117,138118],{"class":693},">spring-ai-openai-spring-boot-starter\u003C/",[679,138120,119861],{"class":4508},[679,138122,4519],{"class":693},[679,138124,138125,138127,138129],{"class":681,"line":959},[679,138126,4577],{"class":693},[679,138128,119838],{"class":4508},[679,138130,4519],{"class":693},[679,138132,138133,138135,138137],{"class":681,"line":964},[679,138134,4586],{"class":693},[679,138136,129682],{"class":4508},[679,138138,4519],{"class":693},[651,138140,138141,138142,94061],{},"Next, configure your OpenAI API key in the ",[676,138143,16242],{},[669,138145,138147],{"className":76589,"code":138146,"language":35538,"meta":674,"style":674},"spring.ai.openai.api-key=${OPENAI_API_KEY}\nspring.ai.openai.model=gpt-4\n",[676,138148,138149,138155],{"__ignoreMap":674},[679,138150,138151,138153],{"class":681,"line":682},[679,138152,122493],{"class":685},[679,138154,122496],{"class":693},[679,138156,138157,138160],{"class":681,"line":790},[679,138158,138159],{"class":685},"spring.ai.openai.model",[679,138161,138162],{"class":693},"=gpt-4\n",[651,138164,138165],{},"Make sure to set your OpenAI API key as an environment variable for security reasons.",[4542,138167,138169],{"id":138168},"implementing-chat-memory-with-spring-ai","Implementing Chat Memory with Spring AI",[651,138171,107011,138172,138174],{},[676,138173,122514],{}," class to handle our chatbot interactions:",[669,138176,138178],{"className":4107,"code":138177,"language":4109,"meta":674,"style":674},"@RestController\npublic class ChatController {\n\n private final ChatClient chatClient;\n\n public ChatController(ChatClient.Builder builder) {\n this.chatClient = builder\n .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))\n .build();\n }\n\n @GetMapping(\"/\")\n public String chat(@RequestParam String message) {\n return chatClient.prompt()\n .user(message)\n .call()\n .content();\n }\n}\n",[676,138179,138180,138186,138196,138200,138208,138212,138224,138234,138254,138262,138266,138270,138282,138300,138310,138318,138326,138334,138338],{"__ignoreMap":674},[679,138181,138182,138184],{"class":681,"line":682},[679,138183,4116],{"class":693},[679,138185,9212],{"class":685},[679,138187,138188,138190,138192,138194],{"class":681,"line":790},[679,138189,6073],{"class":685},[679,138191,4512],{"class":685},[679,138193,121635],{"class":880},[679,138195,884],{"class":693},[679,138197,138198],{"class":681,"line":892},[679,138199,889],{"emptyLinePlaceholder":797},[679,138201,138202,138204,138206],{"class":681,"line":901},[679,138203,9232],{"class":685},[679,138205,12768],{"class":685},[679,138207,121650],{"class":693},[679,138209,138210],{"class":681,"line":909},[679,138211,889],{"emptyLinePlaceholder":797},[679,138213,138214,138216,138218,138220,138222],{"class":681,"line":918},[679,138215,6089],{"class":685},[679,138217,121635],{"class":880},[679,138219,121663],{"class":693},[679,138221,90934],{"class":2099},[679,138223,4390],{"class":693},[679,138225,138226,138228,138230,138232],{"class":681,"line":935},[679,138227,7862],{"class":931},[679,138229,121674],{"class":693},[679,138231,686],{"class":685},[679,138233,119021],{"class":693},[679,138235,138236,138238,138240,138242,138244,138246,138248,138250,138252],{"class":681,"line":944},[679,138237,73482],{"class":693},[679,138239,121917],{"class":880},[679,138241,745],{"class":693},[679,138243,8930],{"class":685},[679,138245,121924],{"class":880},[679,138247,745],{"class":693},[679,138249,8930],{"class":685},[679,138251,121931],{"class":880},[679,138253,104226],{"class":693},[679,138255,138256,138258,138260],{"class":681,"line":959},[679,138257,73482],{"class":693},[679,138259,23612],{"class":880},[679,138261,9317],{"class":693},[679,138263,138264],{"class":681,"line":964},[679,138265,985],{"class":693},[679,138267,138268],{"class":681,"line":977},[679,138269,889],{"emptyLinePlaceholder":797},[679,138271,138272,138274,138276,138278,138280],{"class":681,"line":982},[679,138273,6872],{"class":693},[679,138275,20852],{"class":685},[679,138277,745],{"class":693},[679,138279,10032],{"class":689},[679,138281,1339],{"class":693},[679,138283,138284,138286,138288,138290,138292,138294,138296,138298],{"class":681,"line":988},[679,138285,6089],{"class":685},[679,138287,9289],{"class":693},[679,138289,129981],{"class":880},[679,138291,73246],{"class":693},[679,138293,73249],{"class":685},[679,138295,9289],{"class":693},[679,138297,20198],{"class":2099},[679,138299,4390],{"class":693},[679,138301,138302,138304,138306,138308],{"class":681,"line":993},[679,138303,9444],{"class":685},[679,138305,121763],{"class":693},[679,138307,55494],{"class":880},[679,138309,17545],{"class":693},[679,138311,138312,138314,138316],{"class":681,"line":2129},[679,138313,73482],{"class":693},[679,138315,9575],{"class":880},[679,138317,121776],{"class":693},[679,138319,138320,138322,138324],{"class":681,"line":2140},[679,138321,73482],{"class":693},[679,138323,121783],{"class":880},[679,138325,17545],{"class":693},[679,138327,138328,138330,138332],{"class":681,"line":2145},[679,138329,73482],{"class":693},[679,138331,47833],{"class":880},[679,138333,9317],{"class":693},[679,138335,138336],{"class":681,"line":2154},[679,138337,985],{"class":693},[679,138339,138340],{"class":681,"line":2159},[679,138341,996],{"class":693},[651,138343,138344],{},"Let's break down this code:",[27665,138346,138347,138352,138365],{},[5332,138348,138349,138350,664],{},"We use constructor injection to get a ",[676,138351,122521],{},[5332,138353,138354,138355,108003,138357,138360,138361,138364],{},"We build a ",[676,138356,122385],{},[676,138358,138359],{},"MessageChatMemoryAdvisor"," that uses ",[676,138362,138363],{},"InMemoryChatMemory",". This setup allows our chatbot to remember previous interactions.",[5332,138366,40060,138367,138369,138370,138372,138373,138375],{},[676,138368,129981],{}," method handles GET requests to the root endpoint. It takes a ",[676,138371,20198],{}," parameter and uses the ",[676,138374,122385],{}," to generate a response.",[651,138377,138378,138379,23212,138381,138383],{},"The key to implementing chat memory lies in the use of ",[676,138380,138359],{},[676,138382,138363],{},". These classes, provided by Spring AI, handle the storage and retrieval of conversation history, allowing our chatbot to maintain context across multiple interactions.",[4542,138385,93754],{"id":93753},[651,138387,138388],{},"To test our chatbot with memory capabilities, run the Spring Boot application and use an HTTP client like cURL or HTTPie to send requests. Here's an example interaction using HTTPie:",[669,138390,138392],{"className":5851,"code":138391,"language":5853,"meta":674,"style":674},"$ http :8080 message==\"My name is Bob\"\nHTTP/1.1 200 \nContent-Type: text/plain;charset=UTF-8\n\nHello Bob! It's nice to meet you. How can I assist you today?\n\n$ http :8080 message==\"What's my name?\"\nHTTP/1.1 200 \nContent-Type: text/plain;charset=UTF-8\n\nYour name is Bob.\n",[676,138393,138394,138405,138415,138431,138435,138446,138450,138461,138466,138471,138475],{"__ignoreMap":674},[679,138395,138396,138398,138400,138402],{"class":681,"line":682},[679,138397,30246],{"class":880},[679,138399,122723],{"class":689},[679,138401,91371],{"class":689},[679,138403,138404],{"class":689}," message==\"My name is Bob\"\n",[679,138406,138407,138410,138412],{"class":681,"line":790},[679,138408,138409],{"class":880},"HTTP/1.1",[679,138411,67965],{"class":931},[679,138413,138414],{"class":693}," \n",[679,138416,138417,138420,138423,138426,138428],{"class":681,"line":892},[679,138418,138419],{"class":880},"Content-Type:",[679,138421,138422],{"class":689}," text/plain",[679,138424,138425],{"class":693},";charset",[679,138427,686],{"class":685},[679,138429,138430],{"class":689},"UTF-8\n",[679,138432,138433],{"class":681,"line":901},[679,138434,889],{"emptyLinePlaceholder":797},[679,138436,138437,138440,138443],{"class":681,"line":909},[679,138438,138439],{"class":880},"Hello",[679,138441,138442],{"class":689}," Bob!",[679,138444,138445],{"class":689}," It's nice to meet you. How can I assist you today?\n",[679,138447,138448],{"class":681,"line":918},[679,138449,889],{"emptyLinePlaceholder":797},[679,138451,138452,138455,138458],{"class":681,"line":935},[679,138453,138454],{"class":689},"$ http :8080 message==\"What's",[679,138456,138457],{"class":689}," my",[679,138459,138460],{"class":689}," name?\"\n",[679,138462,138463],{"class":681,"line":944},[679,138464,138465],{"class":689},"HTTP/1.1 200 \n",[679,138467,138468],{"class":681,"line":959},[679,138469,138470],{"class":689},"Content-Type: text/plain;charset=UTF-8\n",[679,138472,138473],{"class":681,"line":964},[679,138474,889],{"emptyLinePlaceholder":797},[679,138476,138477],{"class":681,"line":977},[679,138478,138479],{"class":689},"Your name is Bob.\n",[651,138481,138482],{},"As you can see, the chatbot remembers the user's name from the first interaction and can recall it in the second interaction, demonstrating the effectiveness of our chat memory implementation.",[4542,138484,138486],{"id":138485},"benefits-and-use-cases","Benefits and Use Cases",[651,138488,138489],{},"Implementing chat memory with Spring AI offers several advantages:",[27665,138491,138492,138498,138504,138512],{},[5332,138493,138494,138497],{},[2939,138495,138496],{},"Improved User Experience",": By maintaining context, the chatbot can provide more coherent and personalized responses.",[5332,138499,138500,138503],{},[2939,138501,138502],{},"Simplified Development",": Spring AI abstracts away the complexities of managing conversation history, allowing developers to focus on building features.",[5332,138505,138506,138508,138509,138511],{},[2939,138507,129636],{},": The ",[676,138510,138363],{}," can be easily replaced with other implementations for different storage backends as your application grows.",[5332,138513,138514,138516],{},[2939,138515,129642],{},": The same approach can be used with different LLM providers, making it easy to switch or compare different AI models.",[651,138518,138519],{},"Some potential use cases for chatbots with memory include:",[5316,138521,138522,138525,138528],{},[5332,138523,138524],{},"Customer support systems that can remember user details and previous issues",[5332,138526,138527],{},"Educational platforms that can track a student's progress across multiple sessions",[5332,138529,138530],{},"Personal assistants that can recall user preferences and past interactions",[4542,138532,138534],{"id":138533},"important-considerations-token-usage-and-context-window-limitations","Important Considerations: Token Usage and Context Window Limitations",[651,138536,138537],{},"While implementing chat memory can greatly enhance the user experience, it's crucial to be aware of some important limitations and considerations:",[5909,138539,138541],{"id":138540},"token-usage","Token Usage",[651,138543,138544],{},"Each interaction stored in the chat memory consumes tokens. Tokens are the basic units that LLMs process, and they roughly correspond to parts of words. As you add more context to each request by including previous interactions, you increase the number of tokens used. This has two main implications:",[27665,138546,138547,138553],{},[5332,138548,138549,138552],{},[2939,138550,138551],{},"Cost",": Most AI providers, including OpenAI, charge based on the number of tokens processed. As your chat history grows, so does the cost per request.",[5332,138554,138555,138558],{},[2939,138556,138557],{},"Response Time",": Processing more tokens generally takes more time, potentially increasing the latency of your chatbot's responses.",[5909,138560,138562],{"id":138561},"context-window-limitations","Context Window Limitations",[651,138564,138565],{},"LLMs have a maximum limit on the number of tokens they can process in a single request, known as the context window. For example:",[5316,138567,138568,138571],{},[5332,138569,138570],{},"GPT-3.5 has a context window of 4,096 tokens",[5332,138572,138573],{},"GPT-4 has a context window of 8,192 tokens for the standard model, and 32,768 tokens for the larger model",[651,138575,138576],{},"When your chat history plus the new user input exceeds this limit, you'll need to implement strategies to manage the context, such as:",[5316,138578,138579,138582,138585],{},[5332,138580,138581],{},"Summarizing previous conversations",[5332,138583,138584],{},"Selectively including only the most relevant past interactions",[5332,138586,138587],{},"Truncating the history to fit within the token limit",[5909,138589,138591],{"id":138590},"balancing-memory-and-efficiency","Balancing Memory and Efficiency",[651,138593,138594],{},"To address these challenges, consider implementing the following strategies:",[27665,138596,138597,138603,138609,138615],{},[5332,138598,138599,138602],{},[2939,138600,138601],{},"Limit History",": Only include a certain number of previous interactions in the context.",[5332,138604,138605,138608],{},[2939,138606,138607],{},"Implement Forgetting",": Gradually reduce the importance of older messages or remove them entirely after a certain point.",[5332,138610,138611,138614],{},[2939,138612,138613],{},"Use Embeddings",": Instead of including full message history, use embeddings to capture the essence of previous interactions more efficiently.",[5332,138616,138617,138620],{},[2939,138618,138619],{},"Dynamic Context Management",": Adjust the amount of history included based on the complexity of the current interaction or the user's needs.",[651,138622,138623],{},"By being mindful of these considerations, you can create chatbots that balance the benefits of memory with the practical constraints of token usage and context windows, resulting in more efficient and cost-effective AI applications.",[4542,138625,9042],{"id":9041},[651,138627,138628,138629,23212,138631,138633],{},"In this tutorial, we've explored how to build an intelligent chatbot with memory capabilities using Spring AI and OpenAI. By leveraging the ",[676,138630,138359],{},[676,138632,138363],{},", we've created a chatbot that can maintain context across multiple interactions, providing a more engaging and coherent conversation experience.",[651,138635,138636],{},"The simplicity of implementing chat memory with Spring AI demonstrates the power and flexibility of the framework. As you continue to build and expand your AI applications, consider how maintaining conversation history can enhance the user experience and open up new possibilities for your chatbots.",[651,138638,138639],{},"We encourage you to experiment with this project, perhaps by implementing more complex conversation flows or integrating with different AI models. The world of conversational AI is rapidly evolving, and with tools like Spring AI, you're well-equipped to create the next generation of intelligent chatbots.",[651,138641,138642],{},"As you continue to develop your AI applications, remember to monitor token usage and implement strategies to manage your chat memory efficiently. This will help you create chatbots that are not only intelligent and context-aware but also performant and cost-effective.",[651,138644,138645],{},"Happy coding, and may your chatbots always remember the important stuff – but not too much! 🤖💬🧠💡",[786,138647,130622],{},{"title":674,"searchDepth":790,"depth":790,"links":138649},[138650,138651,138652,138653,138654,138655,138660],{"id":137982,"depth":790,"text":137983},{"id":138015,"depth":790,"text":138016},{"id":138168,"depth":790,"text":138169},{"id":93753,"depth":790,"text":93754},{"id":138485,"depth":790,"text":138486},{"id":138533,"depth":790,"text":138534,"children":138656},[138657,138658,138659],{"id":138540,"depth":892,"text":138541},{"id":138561,"depth":892,"text":138562},{"id":138590,"depth":892,"text":138591},{"id":9041,"depth":790,"text":9042},"Learn how to create a context-aware chatbot using Spring AI and OpenAI. This tutorial covers implementing chat memory, handling token usage, and managing context window limitations for more engaging AI conversations.",{"slug":138663,"date":138664,"published":797,"author":798,"tags":138665,"video":138667,"keywords":138668},"spring-ai-chat-memory","2024-10-11T09:00:00.000Z",[7077,36422,138666],"Spring AI~~","https://www.youtube.com/embed/6VdM1MOOMrw","Spring AI, OpenAI, chat memory, conversational AI, ChatClient, MessageChatMemoryAdvisor, InMemoryChatMemory, token usage, context window, Spring Framework, Spring Boot, Java",{"title":57,"description":138661},"blog/2024/10/11/spring-ai-chat-memory","OeoQsCIEu-aTS7Ljz0F0hbeHyxL9OSFoPRacNroIrGY",{"id":138673,"title":54,"body":138674,"description":139407,"extension":793,"meta":139408,"navigation":797,"path":55,"seo":139414,"stem":139415,"__hash__":139416},"content/blog/2024/10/14/spring-ai-multiple-llms.md",{"type":648,"value":138675,"toc":139399},[138676,138679,138683,138686,138712,138714,138717,138731,138736,138794,138799,138838,138844,138848,138854,139000,139006,139010,139013,139164,139173,139176,139324,139326,139329,139362,139365,139367,139373,139376,139387,139390,139393,139396],[651,138677,138678],{},"In the rapidly evolving world of AI-powered applications, developers often find themselves needing to leverage multiple Large Language Models (LLMs) within a single project. Whether it's to compare outputs, utilize specific strengths of different models, or provide fallback options, the ability to seamlessly integrate multiple LLMs can be a game-changer. Thanks to Spring AI, this process is now more straightforward than ever. In this post, we'll explore how to call multiple LLMs, specifically OpenAI's GPT-4 and Anthropic's Claude, within a Spring Boot application.",[4542,138680,138682],{"id":138681},"why-use-multiple-llms","Why Use Multiple LLMs?",[651,138684,138685],{},"Before diving into the implementation, let's consider why you might want to use multiple LLMs in your application:",[27665,138687,138688,138694,138700,138706],{},[5332,138689,138690,138693],{},[2939,138691,138692],{},"Comparative Analysis",": Different models may excel in various tasks. By using multiple LLMs, you can compare outputs and choose the best result.",[5332,138695,138696,138699],{},[2939,138697,138698],{},"Specialized Capabilities",": Some models might be better at certain tasks, like code generation or creative writing.",[5332,138701,138702,138705],{},[2939,138703,138704],{},"Redundancy",": Having multiple LLMs can provide fallback options if one service is unavailable or rate-limited.",[5332,138707,138708,138711],{},[2939,138709,138710],{},"Cost Optimization",": Different providers have varying pricing models. You can route requests to the most cost-effective option based on the task.",[4542,138713,94281],{"id":94280},[651,138715,138716],{},"Let's start by creating a new Spring Boot project using Spring Initializr. Here are the key details:",[5316,138718,138719,138721,138723,138725,138728],{},[5332,138720,133775],{},[5332,138722,102049],{},[5332,138724,136120],{},[5332,138726,138727],{},"Java Version: 23",[5332,138729,138730],{},"Dependencies: Spring Web, Spring AI OpenAI",[651,138732,138733,138734,2391],{},"After generating the project, we need to add the Anthropic dependency manually to our ",[676,138735,81517],{},[669,138737,138739],{"className":9101,"code":138738,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.ai\u003C/groupId>\n \u003CartifactId>spring-ai-anthropic-spring-boot-starter\u003C/artifactId>\n \u003Cversion>0.8.0-SNAPSHOT\u003C/version>\n\u003C/dependency>\n",[676,138740,138741,138749,138761,138773,138786],{"__ignoreMap":674},[679,138742,138743,138745,138747],{"class":681,"line":682},[679,138744,4505],{"class":693},[679,138746,119838],{"class":4508},[679,138748,4519],{"class":693},[679,138750,138751,138753,138755,138757,138759],{"class":681,"line":790},[679,138752,4524],{"class":693},[679,138754,119847],{"class":4508},[679,138756,129701],{"class":693},[679,138758,119847],{"class":4508},[679,138760,4519],{"class":693},[679,138762,138763,138765,138767,138769,138771],{"class":681,"line":892},[679,138764,4524],{"class":693},[679,138766,119861],{"class":4508},[679,138768,129714],{"class":693},[679,138770,119861],{"class":4508},[679,138772,4519],{"class":693},[679,138774,138775,138777,138779,138782,138784],{"class":681,"line":901},[679,138776,4524],{"class":693},[679,138778,107166],{"class":4508},[679,138780,138781],{"class":693},">0.8.0-SNAPSHOT\u003C/",[679,138783,107166],{"class":4508},[679,138785,4519],{"class":693},[679,138787,138788,138790,138792],{"class":681,"line":909},[679,138789,4586],{"class":693},[679,138791,119838],{"class":4508},[679,138793,4519],{"class":693},[651,138795,138796,138797,2391],{},"Next, let's configure our ",[676,138798,16242],{},[669,138800,138802],{"className":76589,"code":138801,"language":35538,"meta":674,"style":674},"spring.ai.chat.client.enabled=false\nspring.ai.openai.api-key=${OPENAI_API_KEY}\nspring.ai.openai.chat.model=gpt-4\nspring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}\nspring.ai.anthropic.chat.model=claude-3-sonnet-20240229\n",[676,138803,138804,138811,138817,138824,138830],{"__ignoreMap":674},[679,138805,138806,138809],{"class":681,"line":682},[679,138807,138808],{"class":685},"spring.ai.chat.client.enabled",[679,138810,103241],{"class":693},[679,138812,138813,138815],{"class":681,"line":790},[679,138814,122493],{"class":685},[679,138816,122496],{"class":693},[679,138818,138819,138822],{"class":681,"line":892},[679,138820,138821],{"class":685},"spring.ai.openai.chat.model",[679,138823,138162],{"class":693},[679,138825,138826,138828],{"class":681,"line":901},[679,138827,129765],{"class":685},[679,138829,129768],{"class":693},[679,138831,138832,138835],{"class":681,"line":909},[679,138833,138834],{"class":685},"spring.ai.anthropic.chat.model",[679,138836,138837],{"class":693},"=claude-3-sonnet-20240229\n",[651,138839,138840,138841,138843],{},"Note that we're disabling the auto-configuration for ",[676,138842,122385],{}," and using environment variables for API keys to keep our credentials secure.",[4542,138845,138847],{"id":138846},"configuring-multiple-chatclients","Configuring Multiple ChatClients",[651,138849,138850,138851,138853],{},"With auto-configuration disabled, we need to manually create bean definitions for each ",[676,138852,122385],{},". We can do this in our main application class:",[669,138855,138857],{"className":4107,"code":138856,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class TwoLlmsApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(TwoLlmsApplication.class, args);\n }\n\n @Bean\n public ChatClient openAIChatClient(OpenAiChatModel chatModel) {\n return ChatClient.create(chatModel);\n }\n\n @Bean\n public ChatClient anthropicChatClient(AnthropicChatModel chatModel) {\n return ChatClient.create(chatModel);\n }\n}\n",[676,138858,138859,138865,138876,138880,138900,138909,138913,138917,138923,138941,138952,138956,138960,138966,138982,138992,138996],{"__ignoreMap":674},[679,138860,138861,138863],{"class":681,"line":682},[679,138862,4116],{"class":693},[679,138864,6068],{"class":685},[679,138866,138867,138869,138871,138874],{"class":681,"line":790},[679,138868,6073],{"class":685},[679,138870,4512],{"class":685},[679,138872,138873],{"class":880}," TwoLlmsApplication",[679,138875,884],{"class":693},[679,138877,138878],{"class":681,"line":892},[679,138879,889],{"emptyLinePlaceholder":797},[679,138881,138882,138884,138886,138888,138890,138892,138894,138896,138898],{"class":681,"line":901},[679,138883,6089],{"class":685},[679,138885,6092],{"class":685},[679,138887,6095],{"class":685},[679,138889,6098],{"class":880},[679,138891,745],{"class":693},[679,138893,4758],{"class":685},[679,138895,16901],{"class":693},[679,138897,6108],{"class":2099},[679,138899,4390],{"class":693},[679,138901,138902,138904,138906],{"class":681,"line":909},[679,138903,6115],{"class":693},[679,138905,6118],{"class":880},[679,138907,138908],{"class":693},"(TwoLlmsApplication.class, args);\n",[679,138910,138911],{"class":681,"line":918},[679,138912,985],{"class":693},[679,138914,138915],{"class":681,"line":935},[679,138916,889],{"emptyLinePlaceholder":797},[679,138918,138919,138921],{"class":681,"line":944},[679,138920,6872],{"class":693},[679,138922,16929],{"class":685},[679,138924,138925,138927,138930,138933,138936,138939],{"class":681,"line":959},[679,138926,6089],{"class":685},[679,138928,138929],{"class":693}," ChatClient ",[679,138931,138932],{"class":880},"openAIChatClient",[679,138934,138935],{"class":693},"(OpenAiChatModel ",[679,138937,138938],{"class":2099},"chatModel",[679,138940,4390],{"class":693},[679,138942,138943,138945,138947,138949],{"class":681,"line":964},[679,138944,9444],{"class":685},[679,138946,121819],{"class":693},[679,138948,5697],{"class":880},[679,138950,138951],{"class":693},"(chatModel);\n",[679,138953,138954],{"class":681,"line":977},[679,138955,985],{"class":693},[679,138957,138958],{"class":681,"line":982},[679,138959,889],{"emptyLinePlaceholder":797},[679,138961,138962,138964],{"class":681,"line":988},[679,138963,6872],{"class":693},[679,138965,16929],{"class":685},[679,138967,138968,138970,138972,138975,138978,138980],{"class":681,"line":993},[679,138969,6089],{"class":685},[679,138971,138929],{"class":693},[679,138973,138974],{"class":880},"anthropicChatClient",[679,138976,138977],{"class":693},"(AnthropicChatModel ",[679,138979,138938],{"class":2099},[679,138981,4390],{"class":693},[679,138983,138984,138986,138988,138990],{"class":681,"line":2129},[679,138985,9444],{"class":685},[679,138987,121819],{"class":693},[679,138989,5697],{"class":880},[679,138991,138951],{"class":693},[679,138993,138994],{"class":681,"line":2140},[679,138995,985],{"class":693},[679,138997,138998],{"class":681,"line":2145},[679,138999,996],{"class":693},[651,139001,139002,139003,139005],{},"This configuration creates two separate ",[676,139004,122385],{}," beans, one for OpenAI and one for Anthropic.",[4542,139007,139009],{"id":139008},"implementing-controllers","Implementing Controllers",[651,139011,139012],{},"Now, let's create controllers to interact with each LLM. We'll start with the OpenAI controller:",[669,139014,139016],{"className":4107,"code":139015,"language":4109,"meta":674,"style":674},"@RestController\npublic class OpenAiChatController {\n\n private final ChatClient chatClient;\n\n public OpenAiChatController(@Qualifier(\"openAIChatClient\") ChatClient chatClient) {\n this.chatClient = chatClient;\n }\n\n @GetMapping(\"/openai\")\n public String openAi() {\n return chatClient.prompt()\n .user(\"Tell me an interesting fact about OpenAI\")\n .call()\n .content();\n }\n}\n",[676,139017,139018,139024,139035,139039,139047,139051,139074,139085,139089,139093,139106,139117,139127,139140,139148,139156,139160],{"__ignoreMap":674},[679,139019,139020,139022],{"class":681,"line":682},[679,139021,4116],{"class":693},[679,139023,9212],{"class":685},[679,139025,139026,139028,139030,139033],{"class":681,"line":790},[679,139027,6073],{"class":685},[679,139029,4512],{"class":685},[679,139031,139032],{"class":880}," OpenAiChatController",[679,139034,884],{"class":693},[679,139036,139037],{"class":681,"line":892},[679,139038,889],{"emptyLinePlaceholder":797},[679,139040,139041,139043,139045],{"class":681,"line":901},[679,139042,9232],{"class":685},[679,139044,12768],{"class":685},[679,139046,121650],{"class":693},[679,139048,139049],{"class":681,"line":909},[679,139050,889],{"emptyLinePlaceholder":797},[679,139052,139053,139055,139057,139059,139061,139063,139066,139069,139072],{"class":681,"line":918},[679,139054,6089],{"class":685},[679,139056,139032],{"class":880},[679,139058,73246],{"class":693},[679,139060,116970],{"class":685},[679,139062,745],{"class":693},[679,139064,139065],{"class":689},"\"openAIChatClient\"",[679,139067,139068],{"class":693},") ChatClient ",[679,139070,139071],{"class":2099},"chatClient",[679,139073,4390],{"class":693},[679,139075,139076,139078,139080,139082],{"class":681,"line":935},[679,139077,7862],{"class":931},[679,139079,121674],{"class":693},[679,139081,686],{"class":685},[679,139083,139084],{"class":693}," chatClient;\n",[679,139086,139087],{"class":681,"line":944},[679,139088,985],{"class":693},[679,139090,139091],{"class":681,"line":959},[679,139092,889],{"emptyLinePlaceholder":797},[679,139094,139095,139097,139099,139101,139104],{"class":681,"line":964},[679,139096,6872],{"class":693},[679,139098,20852],{"class":685},[679,139100,745],{"class":693},[679,139102,139103],{"class":689},"\"/openai\"",[679,139105,1339],{"class":693},[679,139107,139108,139110,139112,139115],{"class":681,"line":977},[679,139109,6089],{"class":685},[679,139111,9289],{"class":693},[679,139113,139114],{"class":880},"openAi",[679,139116,2667],{"class":693},[679,139118,139119,139121,139123,139125],{"class":681,"line":982},[679,139120,9444],{"class":685},[679,139122,121763],{"class":693},[679,139124,55494],{"class":880},[679,139126,17545],{"class":693},[679,139128,139129,139131,139133,139135,139138],{"class":681,"line":988},[679,139130,73482],{"class":693},[679,139132,9575],{"class":880},[679,139134,745],{"class":693},[679,139136,139137],{"class":689},"\"Tell me an interesting fact about OpenAI\"",[679,139139,1339],{"class":693},[679,139141,139142,139144,139146],{"class":681,"line":993},[679,139143,73482],{"class":693},[679,139145,121783],{"class":880},[679,139147,17545],{"class":693},[679,139149,139150,139152,139154],{"class":681,"line":2129},[679,139151,73482],{"class":693},[679,139153,47833],{"class":880},[679,139155,9317],{"class":693},[679,139157,139158],{"class":681,"line":2140},[679,139159,985],{"class":693},[679,139161,139162],{"class":681,"line":2145},[679,139163,996],{"class":693},[651,139165,139166,139167,139169,139170,139172],{},"Notice the use of ",[676,139168,116910],{}," to specify which ",[676,139171,122385],{}," bean to inject. This ensures we're using the correct client for OpenAI.",[651,139174,139175],{},"Similarly, let's create an Anthropic controller:",[669,139177,139179],{"className":4107,"code":139178,"language":4109,"meta":674,"style":674},"@RestController\npublic class AnthropicChatController {\n\n private final ChatClient chatClient;\n\n public AnthropicChatController(@Qualifier(\"anthropicChatClient\") ChatClient chatClient) {\n this.chatClient = chatClient;\n }\n\n @GetMapping(\"/claude\")\n public String claude() {\n return chatClient.prompt()\n .user(\"Tell me an interesting fact about Anthropic\")\n .call()\n .content();\n }\n}\n",[676,139180,139181,139187,139198,139202,139210,139214,139235,139245,139249,139253,139266,139277,139287,139300,139308,139316,139320],{"__ignoreMap":674},[679,139182,139183,139185],{"class":681,"line":682},[679,139184,4116],{"class":693},[679,139186,9212],{"class":685},[679,139188,139189,139191,139193,139196],{"class":681,"line":790},[679,139190,6073],{"class":685},[679,139192,4512],{"class":685},[679,139194,139195],{"class":880}," AnthropicChatController",[679,139197,884],{"class":693},[679,139199,139200],{"class":681,"line":892},[679,139201,889],{"emptyLinePlaceholder":797},[679,139203,139204,139206,139208],{"class":681,"line":901},[679,139205,9232],{"class":685},[679,139207,12768],{"class":685},[679,139209,121650],{"class":693},[679,139211,139212],{"class":681,"line":909},[679,139213,889],{"emptyLinePlaceholder":797},[679,139215,139216,139218,139220,139222,139224,139226,139229,139231,139233],{"class":681,"line":918},[679,139217,6089],{"class":685},[679,139219,139195],{"class":880},[679,139221,73246],{"class":693},[679,139223,116970],{"class":685},[679,139225,745],{"class":693},[679,139227,139228],{"class":689},"\"anthropicChatClient\"",[679,139230,139068],{"class":693},[679,139232,139071],{"class":2099},[679,139234,4390],{"class":693},[679,139236,139237,139239,139241,139243],{"class":681,"line":935},[679,139238,7862],{"class":931},[679,139240,121674],{"class":693},[679,139242,686],{"class":685},[679,139244,139084],{"class":693},[679,139246,139247],{"class":681,"line":944},[679,139248,985],{"class":693},[679,139250,139251],{"class":681,"line":959},[679,139252,889],{"emptyLinePlaceholder":797},[679,139254,139255,139257,139259,139261,139264],{"class":681,"line":964},[679,139256,6872],{"class":693},[679,139258,20852],{"class":685},[679,139260,745],{"class":693},[679,139262,139263],{"class":689},"\"/claude\"",[679,139265,1339],{"class":693},[679,139267,139268,139270,139272,139275],{"class":681,"line":977},[679,139269,6089],{"class":685},[679,139271,9289],{"class":693},[679,139273,139274],{"class":880},"claude",[679,139276,2667],{"class":693},[679,139278,139279,139281,139283,139285],{"class":681,"line":982},[679,139280,9444],{"class":685},[679,139282,121763],{"class":693},[679,139284,55494],{"class":880},[679,139286,17545],{"class":693},[679,139288,139289,139291,139293,139295,139298],{"class":681,"line":988},[679,139290,73482],{"class":693},[679,139292,9575],{"class":880},[679,139294,745],{"class":693},[679,139296,139297],{"class":689},"\"Tell me an interesting fact about Anthropic\"",[679,139299,1339],{"class":693},[679,139301,139302,139304,139306],{"class":681,"line":993},[679,139303,73482],{"class":693},[679,139305,121783],{"class":880},[679,139307,17545],{"class":693},[679,139309,139310,139312,139314],{"class":681,"line":2129},[679,139311,73482],{"class":693},[679,139313,47833],{"class":880},[679,139315,9317],{"class":693},[679,139317,139318],{"class":681,"line":2140},[679,139319,985],{"class":693},[679,139321,139322],{"class":681,"line":2145},[679,139323,996],{"class":693},[4542,139325,93754],{"id":93753},[651,139327,139328],{},"With our controllers in place, we can now run the application and test our endpoints. Here's how you can make requests to each LLM:",[669,139330,139332],{"className":5851,"code":139331,"language":5853,"meta":674,"style":674},"# OpenAI endpoint\ncurl http://localhost:8080/openai\n\n# Anthropic endpoint\ncurl http://localhost:8080/claude\n",[676,139333,139334,139339,139346,139350,139355],{"__ignoreMap":674},[679,139335,139336],{"class":681,"line":682},[679,139337,139338],{"class":1400},"# OpenAI endpoint\n",[679,139340,139341,139343],{"class":681,"line":790},[679,139342,41991],{"class":880},[679,139344,139345],{"class":689}," http://localhost:8080/openai\n",[679,139347,139348],{"class":681,"line":892},[679,139349,889],{"emptyLinePlaceholder":797},[679,139351,139352],{"class":681,"line":901},[679,139353,139354],{"class":1400},"# Anthropic endpoint\n",[679,139356,139357,139359],{"class":681,"line":909},[679,139358,41991],{"class":880},[679,139360,139361],{"class":689}," http://localhost:8080/claude\n",[651,139363,139364],{},"Each request will return an interesting fact about the respective company, demonstrating that we're successfully communicating with two different LLMs in the same application.",[4542,139366,9042],{"id":9041},[651,139368,139369,139370,139372],{},"Integrating multiple LLMs into a single Spring Boot application is remarkably straightforward with Spring AI. By disabling auto-configuration and manually defining our ",[676,139371,122385],{}," beans, we gain the flexibility to work with multiple AI providers simultaneously. This approach opens up a world of possibilities for creating more robust, versatile AI-powered applications.",[651,139374,139375],{},"Some potential use cases for this setup include:",[5316,139377,139378,139381,139384],{},[5332,139379,139380],{},"Creating a chatbot that can switch between models based on the complexity of the query",[5332,139382,139383],{},"Implementing a content generation system that leverages different models for various types of content",[5332,139385,139386],{},"Building a code assistant that uses specialized models for different programming languages",[651,139388,139389],{},"As you explore the possibilities of multiple LLMs in your Spring applications, remember to consider factors like rate limiting, error handling, and response caching to create a production-ready system.",[651,139391,139392],{},"Have you experimented with multiple LLMs in your projects? What challenges or benefits have you encountered? Share your experiences in the comments below!",[651,139394,139395],{},"Happy coding, and may your AI adventures be ever fruitful!",[786,139397,139398],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":139400},[139401,139402,139403,139404,139405,139406],{"id":138681,"depth":790,"text":138682},{"id":94280,"depth":790,"text":94281},{"id":138846,"depth":790,"text":138847},{"id":139008,"depth":790,"text":139009},{"id":93753,"depth":790,"text":93754},{"id":9041,"depth":790,"text":9042},"Learn how to integrate and call multiple Large Language Models (LLMs) like OpenAI's GPT-4 and Anthropic's Claude in a single Spring Boot application using Spring AI. This tutorial covers project setup, configuration, and implementation of separate controllers for each LLM.",{"slug":139409,"date":139410,"published":797,"author":798,"tags":139411,"video":139412,"keywords":139413},"spring-ai-multiple-llms","2024-10-14T09:00:00.000Z",[7077,36422,123549],"https://www.youtube.com/embed/bK1MTlEDQvk","Spring AI, multiple LLMs, OpenAI, Anthropic, ChatClient, Spring Framework, Spring Boot, Java, GPT-4, Claude",{"title":54,"description":139407},"blog/2024/10/14/spring-ai-multiple-llms","rIVmpi1NAyP2H_BZWXwVxGdsCDfQasQ6t3XUU880ofA",{"id":139418,"title":51,"body":139419,"description":140052,"extension":793,"meta":140053,"navigation":797,"path":52,"seo":140059,"stem":140060,"__hash__":140061},"content/blog/2024/10/15/ai-java-developers.md",{"type":648,"value":139420,"toc":140045},[139421,139424,139427,139431,139440,139443,139522,139525,139529,139532,139746,139749,139753,139756,139759,139776,139782,139836,139842,139984,139987,139991,139994,140029,140031,140034,140037,140040,140042],[651,139422,139423],{},"As the world of artificial intelligence continues to evolve, Java developers are finding themselves at an exciting crossroads. While Python has long been the go-to language for AI and machine learning, the landscape is shifting. Today, we're not just talking about building and training models – we're focusing on how to consume and integrate powerful AI capabilities into our Java applications.",[651,139425,139426],{},"In this post, we'll explore how Java developers can leverage generative AI, specifically large language models like OpenAI's GPT-4, in their applications. We'll start with simple scripts and progress to more robust Spring Boot applications, addressing real-world challenges along the way.",[4542,139428,139430],{"id":139429},"getting-started-with-openais-api","Getting Started with OpenAI's API",[651,139432,139433,139434,139439],{},"Before we dive into code, you'll need an OpenAI API key. If you haven't already, head over to the ",[812,139435,139438],{"href":139436,"rel":139437},"https://openai.com/",[816],"OpenAI website"," to sign up and obtain your key.",[651,139441,139442],{},"Let's start with a simple curl command to test the API:",[669,139444,139446],{"className":5851,"code":139445,"language":5853,"meta":674,"style":674},"#!/bin/zsh\necho \"Calling OpenAI...\"\nPROMPT=\"Tell me something interesting about Java\"\n\ncurl https://api.openai.com/v1/chat/completions \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n-d '{ \"model\": \"gpt-4o\", \"messages\": [{\"role\":\"user\", \"content\": \"'\"${PROMPT}\"'\"}] }'\n",[676,139447,139448,139453,139461,139471,139475,139485,139495,139509],{"__ignoreMap":674},[679,139449,139450],{"class":681,"line":682},[679,139451,139452],{"class":1400},"#!/bin/zsh\n",[679,139454,139455,139458],{"class":681,"line":790},[679,139456,139457],{"class":931},"echo",[679,139459,139460],{"class":689}," \"Calling OpenAI...\"\n",[679,139462,139463,139466,139468],{"class":681,"line":892},[679,139464,139465],{"class":693},"PROMPT",[679,139467,686],{"class":685},[679,139469,139470],{"class":689},"\"Tell me something interesting about Java\"\n",[679,139472,139473],{"class":681,"line":901},[679,139474,889],{"emptyLinePlaceholder":797},[679,139476,139477,139479,139482],{"class":681,"line":909},[679,139478,41991],{"class":880},[679,139480,139481],{"class":689}," https://api.openai.com/v1/chat/completions",[679,139483,139484],{"class":931}," \\\n",[679,139486,139487,139490,139493],{"class":681,"line":918},[679,139488,139489],{"class":693},"-H ",[679,139491,139492],{"class":689},"\"Content-Type: application/json\"",[679,139494,139484],{"class":931},[679,139496,139497,139499,139502,139505,139507],{"class":681,"line":935},[679,139498,139489],{"class":693},[679,139500,139501],{"class":689},"\"Authorization: Bearer ",[679,139503,139504],{"class":693},"$OPENAI_API_KEY",[679,139506,4857],{"class":689},[679,139508,139484],{"class":931},[679,139510,139511,139514,139517,139519],{"class":681,"line":944},[679,139512,139513],{"class":693},"-d ",[679,139515,139516],{"class":689},"'{ \"model\": \"gpt-4o\", \"messages\": [{\"role\":\"user\", \"content\": \"'\"${",[679,139518,139465],{"class":693},[679,139520,139521],{"class":689},"}\"'\"}] }'\n",[651,139523,139524],{},"This script sends a POST request to OpenAI's chat completions endpoint, passing our prompt as the message content. The response will contain the AI-generated text along with some metadata.",[4542,139526,139528],{"id":139527},"making-ai-calls-in-a-java-application","Making AI Calls in a Java Application",[651,139530,139531],{},"While curl commands are great for quick tests, let's see how we can integrate this into a Java application. We'll use Java's built-in HTTP client to make the request:",[669,139533,139535],{"className":4107,"code":139534,"language":4109,"meta":674,"style":674},"var apiKey = System.getenv(\"OPENAI_API_KEY\");\nvar body = \"\"\"\n {\n \"model\": \"gpt-4o\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Tell me an interesting fact about the Spring Framework\"\n }\n ]\n }\"\"\";\n\nvar request = HttpRequest.newBuilder()\n .uri(URI.create(\"https://api.openai.com/v1/chat/completions\"))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(body))\n .build();\n\nvar client = HttpClient.newHttpClient();\nvar response = client.send(request, HttpResponse.BodyHandlers.ofString());\nSystem.out.println(response.body());\n",[676,139536,139537,139555,139565,139569,139574,139579,139583,139588,139593,139597,139602,139609,139613,139628,139644,139660,139678,139690,139698,139702,139716,139734],{"__ignoreMap":674},[679,139538,139539,139541,139543,139545,139547,139549,139551,139553],{"class":681,"line":682},[679,139540,73698],{"class":685},[679,139542,123731],{"class":693},[679,139544,686],{"class":685},[679,139546,108725],{"class":693},[679,139548,123663],{"class":880},[679,139550,745],{"class":693},[679,139552,123668],{"class":689},[679,139554,1208],{"class":693},[679,139556,139557,139559,139561,139563],{"class":681,"line":790},[679,139558,73698],{"class":685},[679,139560,123806],{"class":693},[679,139562,686],{"class":685},[679,139564,105376],{"class":689},[679,139566,139567],{"class":681,"line":892},[679,139568,101453],{"class":689},[679,139570,139571],{"class":681,"line":901},[679,139572,139573],{"class":689}," \"model\": \"gpt-4o\",\n",[679,139575,139576],{"class":681,"line":909},[679,139577,139578],{"class":689}," \"messages\": [\n",[679,139580,139581],{"class":681,"line":918},[679,139582,124247],{"class":689},[679,139584,139585],{"class":681,"line":935},[679,139586,139587],{"class":689}," \"role\": \"user\",\n",[679,139589,139590],{"class":681,"line":944},[679,139591,139592],{"class":689}," \"content\": \"Tell me an interesting fact about the Spring Framework\"\n",[679,139594,139595],{"class":681,"line":959},[679,139596,106697],{"class":689},[679,139598,139599],{"class":681,"line":964},[679,139600,139601],{"class":689}," ]\n",[679,139603,139604,139607],{"class":681,"line":977},[679,139605,139606],{"class":689}," }\"\"\"",[679,139608,1186],{"class":693},[679,139610,139611],{"class":681,"line":982},[679,139612,889],{"emptyLinePlaceholder":797},[679,139614,139615,139617,139620,139622,139624,139626],{"class":681,"line":988},[679,139616,73698],{"class":685},[679,139618,139619],{"class":693}," request ",[679,139621,686],{"class":685},[679,139623,123879],{"class":693},[679,139625,123882],{"class":880},[679,139627,17545],{"class":693},[679,139629,139630,139632,139634,139636,139638,139640,139642],{"class":681,"line":993},[679,139631,21331],{"class":693},[679,139633,4836],{"class":880},[679,139635,123893],{"class":693},[679,139637,5697],{"class":880},[679,139639,745],{"class":693},[679,139641,123900],{"class":689},[679,139643,40143],{"class":693},[679,139645,139646,139648,139650,139652,139654,139656,139658],{"class":681,"line":2129},[679,139647,21331],{"class":693},[679,139649,91684],{"class":880},[679,139651,745],{"class":693},[679,139653,123913],{"class":689},[679,139655,2797],{"class":693},[679,139657,79927],{"class":689},[679,139659,1339],{"class":693},[679,139661,139662,139664,139666,139668,139670,139672,139674,139676],{"class":681,"line":2140},[679,139663,21331],{"class":693},[679,139665,91684],{"class":880},[679,139667,745],{"class":693},[679,139669,91689],{"class":689},[679,139671,2797],{"class":693},[679,139673,91694],{"class":689},[679,139675,3059],{"class":685},[679,139677,123938],{"class":693},[679,139679,139680,139682,139684,139686,139688],{"class":681,"line":2145},[679,139681,21331],{"class":693},[679,139683,93764],{"class":880},[679,139685,123947],{"class":693},[679,139687,123950],{"class":880},[679,139689,123953],{"class":693},[679,139691,139692,139694,139696],{"class":681,"line":2154},[679,139693,21331],{"class":693},[679,139695,23612],{"class":880},[679,139697,9317],{"class":693},[679,139699,139700],{"class":681,"line":2159},[679,139701,889],{"emptyLinePlaceholder":797},[679,139703,139704,139706,139708,139710,139712,139714],{"class":681,"line":2164},[679,139705,73698],{"class":685},[679,139707,123983],{"class":693},[679,139709,686],{"class":685},[679,139711,123988],{"class":693},[679,139713,123991],{"class":880},[679,139715,9317],{"class":693},[679,139717,139718,139720,139722,139724,139726,139728,139730,139732],{"class":681,"line":3134},[679,139719,73698],{"class":685},[679,139721,124000],{"class":693},[679,139723,686],{"class":685},[679,139725,124005],{"class":693},[679,139727,9717],{"class":880},[679,139729,124010],{"class":693},[679,139731,123950],{"class":880},[679,139733,9431],{"class":693},[679,139735,139736,139738,139740,139742,139744],{"class":681,"line":3139},[679,139737,78405],{"class":693},[679,139739,1729],{"class":880},[679,139741,124023],{"class":693},[679,139743,3006],{"class":880},[679,139745,9431],{"class":693},[651,139747,139748],{},"This approach gives us more control over the request and allows us to process the response programmatically. However, as we start building more complex applications, we'll face challenges like parsing structured data, handling errors, and managing API quotas.",[4542,139750,139752],{"id":139751},"leveraging-spring-ai-for-robust-applications","Leveraging Spring AI for Robust Applications",[651,139754,139755],{},"Enter Spring AI, a powerful framework that simplifies the process of integrating AI capabilities into your Spring applications. Let's build a book recommendation system using Spring AI and Spring Boot.",[651,139757,139758],{},"First, we'll set up our application properties:",[669,139760,139762],{"className":76589,"code":139761,"language":35538,"meta":674,"style":674},"spring.ai.openai.api-key=${OPENAI_API_KEY}\nspring.ai.openai.chat.options.model=gpt-4o\n",[676,139763,139764,139770],{"__ignoreMap":674},[679,139765,139766,139768],{"class":681,"line":682},[679,139767,122493],{"class":685},[679,139769,122496],{"class":693},[679,139771,139772,139774],{"class":681,"line":790},[679,139773,122501],{"class":685},[679,139775,122504],{"class":693},[651,139777,105195,139778,139781],{},[676,139779,139780],{},"BookRecommendation"," record to structure our AI's output:",[669,139783,139785],{"className":4107,"code":139784,"language":4109,"meta":674,"style":674},"public record BookRecommendation(\n String title,\n String author,\n int publicationYear,\n String genre,\n int pageCount,\n String summary\n) {}\n",[676,139786,139787,139798,139803,139808,139815,139820,139827,139832],{"__ignoreMap":674},[679,139788,139789,139791,139793,139796],{"class":681,"line":682},[679,139790,6073],{"class":685},[679,139792,86928],{"class":685},[679,139794,139795],{"class":880}," BookRecommendation",[679,139797,21337],{"class":693},[679,139799,139800],{"class":681,"line":790},[679,139801,139802],{"class":693}," String title,\n",[679,139804,139805],{"class":681,"line":892},[679,139806,139807],{"class":693}," String author,\n",[679,139809,139810,139812],{"class":681,"line":901},[679,139811,36111],{"class":685},[679,139813,139814],{"class":693}," publicationYear,\n",[679,139816,139817],{"class":681,"line":909},[679,139818,139819],{"class":693}," String genre,\n",[679,139821,139822,139824],{"class":681,"line":918},[679,139823,36111],{"class":685},[679,139825,139826],{"class":693}," pageCount,\n",[679,139828,139829],{"class":681,"line":935},[679,139830,139831],{"class":693}," String summary\n",[679,139833,139834],{"class":681,"line":944},[679,139835,25357],{"class":693},[651,139837,139838,139839,139841],{},"Now, we can create a controller that uses Spring AI's ",[676,139840,122385],{}," to generate book recommendations:",[669,139843,139845],{"className":4107,"code":139844,"language":4109,"meta":674,"style":674},"@RestController\npublic class BookController {\n\n private final ChatClient chatClient;\n\n public BookController(ChatClient.Builder builder) {\n this.chatClient = builder.build();\n }\n\n @GetMapping(\"/\")\n public BookRecommendation home() {\n return chatClient.prompt()\n .user(\"Generate a book recommendation for a book on AI and coding. Please limit the summary to 100 words.\")\n .call()\n .entity(BookRecommendation.class);\n }\n}\n",[676,139846,139847,139853,139863,139867,139875,139879,139891,139905,139909,139913,139925,139936,139946,139959,139967,139976,139980],{"__ignoreMap":674},[679,139848,139849,139851],{"class":681,"line":682},[679,139850,4116],{"class":693},[679,139852,9212],{"class":685},[679,139854,139855,139857,139859,139861],{"class":681,"line":790},[679,139856,6073],{"class":685},[679,139858,4512],{"class":685},[679,139860,88106],{"class":880},[679,139862,884],{"class":693},[679,139864,139865],{"class":681,"line":892},[679,139866,889],{"emptyLinePlaceholder":797},[679,139868,139869,139871,139873],{"class":681,"line":901},[679,139870,9232],{"class":685},[679,139872,12768],{"class":685},[679,139874,121650],{"class":693},[679,139876,139877],{"class":681,"line":909},[679,139878,889],{"emptyLinePlaceholder":797},[679,139880,139881,139883,139885,139887,139889],{"class":681,"line":918},[679,139882,6089],{"class":685},[679,139884,88106],{"class":880},[679,139886,121663],{"class":693},[679,139888,90934],{"class":2099},[679,139890,4390],{"class":693},[679,139892,139893,139895,139897,139899,139901,139903],{"class":681,"line":935},[679,139894,7862],{"class":931},[679,139896,121674],{"class":693},[679,139898,686],{"class":685},[679,139900,136298],{"class":693},[679,139902,23612],{"class":880},[679,139904,9317],{"class":693},[679,139906,139907],{"class":681,"line":944},[679,139908,985],{"class":693},[679,139910,139911],{"class":681,"line":959},[679,139912,889],{"emptyLinePlaceholder":797},[679,139914,139915,139917,139919,139921,139923],{"class":681,"line":964},[679,139916,6872],{"class":693},[679,139918,20852],{"class":685},[679,139920,745],{"class":693},[679,139922,10032],{"class":689},[679,139924,1339],{"class":693},[679,139926,139927,139929,139932,139934],{"class":681,"line":977},[679,139928,6089],{"class":685},[679,139930,139931],{"class":693}," BookRecommendation ",[679,139933,12642],{"class":880},[679,139935,2667],{"class":693},[679,139937,139938,139940,139942,139944],{"class":681,"line":982},[679,139939,9444],{"class":685},[679,139941,121763],{"class":693},[679,139943,55494],{"class":880},[679,139945,17545],{"class":693},[679,139947,139948,139950,139952,139954,139957],{"class":681,"line":988},[679,139949,73482],{"class":693},[679,139951,9575],{"class":880},[679,139953,745],{"class":693},[679,139955,139956],{"class":689},"\"Generate a book recommendation for a book on AI and coding. Please limit the summary to 100 words.\"",[679,139958,1339],{"class":693},[679,139960,139961,139963,139965],{"class":681,"line":993},[679,139962,73482],{"class":693},[679,139964,121783],{"class":880},[679,139966,17545],{"class":693},[679,139968,139969,139971,139973],{"class":681,"line":2129},[679,139970,73482],{"class":693},[679,139972,122930],{"class":880},[679,139974,139975],{"class":693},"(BookRecommendation.class);\n",[679,139977,139978],{"class":681,"line":2140},[679,139979,985],{"class":693},[679,139981,139982],{"class":681,"line":2145},[679,139983,996],{"class":693},[651,139985,139986],{},"This code demonstrates one of Spring AI's most powerful features: the ability to automatically parse the AI's response into a structured Java object. We didn't need to manually parse JSON or map fields – Spring AI handles all of that for us.",[4542,139988,139990],{"id":139989},"best-practices-and-considerations","Best Practices and Considerations",[651,139992,139993],{},"As you build AI-powered applications, keep these points in mind:",[27665,139995,139996,140001,140007,140013,140018],{},[5332,139997,139998,140000],{},[2939,139999,124060],{},": AI services can sometimes be unavailable or return unexpected results. Implement robust error handling to ensure your application degrades gracefully.",[5332,140002,140003,140006],{},[2939,140004,140005],{},"Rate Limiting",": Most AI APIs have rate limits. Use techniques like exponential backoff to handle rate limiting errors.",[5332,140008,140009,140012],{},[2939,140010,140011],{},"Costs and Token Usage",": AI API calls can be expensive, especially at scale. Monitor your token usage and implement caching where appropriate.",[5332,140014,140015,140017],{},[2939,140016,126490],{},": Always keep your API keys secure. Use environment variables or secure vaults, never hardcode them in your application.",[5332,140019,140020,140023,140024,664],{},[2939,140021,140022],{},"Prompt Engineering",": The quality of your AI's output depends heavily on your input. Experiment with different prompts to get the best results. I have a guide on ",[812,140025,140028],{"href":140026,"rel":140027},"https://www.bytesizedai.dev/p/how-to-talk-to-robots",[816],"how to talk to robots",[4542,140030,9042],{"id":9041},[651,140032,140033],{},"We've journeyed from simple curl commands to sophisticated Spring Boot applications, demonstrating how Java developers can harness the power of generative AI. The ease with which we can now integrate these capabilities is truly remarkable, opening up a world of possibilities for enhancing our applications with AI-driven features.",[651,140035,140036],{},"Whether you're building a chatbot, a content generation tool, or a recommendation system, the combination of Java's robustness and the power of large language models can yield impressive results. As you continue to explore this exciting field, remember that the key to success lies not just in the technology, but in how creatively and responsibly you apply it to solve real-world problems.",[651,140038,140039],{},"I encourage you to experiment with these techniques in your own projects. Start small, iterate quickly, and don't be afraid to push the boundaries of what's possible. The future of Java development is here, and it's powered by AI.",[651,140041,78024],{},[786,140043,140044],{},"html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":140046},[140047,140048,140049,140050,140051],{"id":139429,"depth":790,"text":139430},{"id":139527,"depth":790,"text":139528},{"id":139751,"depth":790,"text":139752},{"id":139989,"depth":790,"text":139990},{"id":9041,"depth":790,"text":9042},"Explore how Java developers can leverage generative AI, from basic curl commands to robust Spring Boot applications. Learn to integrate OpenAI's GPT models, use Java's HTTP client, and harness the power of Spring AI for structured output and simplified AI integration.",{"slug":140054,"date":140055,"published":797,"author":798,"tags":140056,"video":140057,"keywords":140058},"ai-java-developers","2024-10-15T09:00:00.000Z",[36422,7077,126904],"https://www.youtube.com/embed/uoOwVWVl_eU","Java, Spring Boot, OpenAI, GPT, AI, generative AI, REST API, HTTP client, Spring AI, large language models, LLM, Spring Web, Spring MVC",{"title":51,"description":140052},"blog/2024/10/15/ai-java-developers","x7PiVGpV-4CUYGzxgEkcd4L0uI15AGAf8J5hbaWfo6Q",{"id":140063,"title":48,"body":140064,"description":140815,"extension":793,"meta":140816,"navigation":797,"path":49,"seo":140823,"stem":140824,"__hash__":140825},"content/blog/2024/10/22/getting-started-with-spring-ai-rag.md",{"type":648,"value":140065,"toc":140795},[140066,140069,140073,140076,140096,140099,140103,140106,140110,140113,140129,140131,140134,140145,140148,140152,140155,140159,140165,140180,140184,140187,140211,140214,140284,140288,140291,140498,140502,140510,140683,140690,140693,140696,140700,140711,140715,140726,140730,140741,140745,140748,140773,140782,140784,140787,140790,140792],[651,140067,140068],{},"If you're building intelligent applications with Spring Boot, you've likely encountered some common limitations when working with Large Language Models (LLMs). Whether it's dealing with outdated information, incorporating private data, or preventing hallucinations, these challenges can impact the effectiveness of your AI-powered features. In this guide, you'll learn how to use Retrieval Augmented Generation (RAG) with Spring AI to build more accurate and contextually aware applications.",[4542,140070,140072],{"id":140071},"why-rag-matters","Why RAG Matters",[651,140074,140075],{},"When working with LLMs, you'll encounter three main limitations:",[27665,140077,140078,140084,140090],{},[5332,140079,140080,140083],{},[2939,140081,140082],{},"Training Data Cutoff",": LLMs are trained on data up to a specific date, making them unreliable for current information.",[5332,140085,140086,140089],{},[2939,140087,140088],{},"Private Information",": Your organization's internal documents and knowledge aren't part of the LLM's training data.",[5332,140091,140092,140095],{},[2939,140093,140094],{},"Potential Hallucinations",": Without proper context, LLMs might generate inaccurate or fictional responses.",[651,140097,140098],{},"RAG addresses these challenges by combining document retrieval with LLM generation. This approach lets you leverage both the power of LLMs and your specific data sources.",[4542,140100,140102],{"id":140101},"understanding-tokens-and-context-windows","Understanding Tokens and Context Windows",[651,140104,140105],{},"Before diving into implementation, let's explore two critical concepts that affect your application's performance and cost.",[5909,140107,140109],{"id":140108},"the-currency-of-llms-tokens","The Currency of LLMs: Tokens",[651,140111,140112],{},"Tokens are the fundamental units of text processing in LLMs. Here's what you need to know:",[5316,140114,140115,140118],{},[5332,140116,140117],{},"100 tokens ≈ 75 words",[5332,140119,140120,140121],{},"Cost example for GPT-4:\n",[5316,140122,140123,140126],{},[5332,140124,140125],{},"Input tokens: $2.50 per 1M tokens",[5332,140127,140128],{},"Output tokens: $10.00 per 1M tokens",[5909,140130,138562],{"id":138561},[651,140132,140133],{},"Each LLM has a maximum context window size:",[5316,140135,140136,140139,140142],{},[5332,140137,140138],{},"GPT-4: 32,768 tokens",[5332,140140,140141],{},"Claude 3: 200,000 tokens",[5332,140143,140144],{},"Gemini Pro: 1,000,000 tokens",[651,140146,140147],{},"Understanding these limitations is crucial for building efficient RAG applications, as they determine how much context you can include with each request.",[4542,140149,140151],{"id":140150},"building-your-first-rag-application","Building Your First RAG Application",[651,140153,140154],{},"Let's create a Spring Boot application that demonstrates RAG using a financial market report as our document source.",[5909,140156,140158],{"id":140157},"step-1-project-setup","Step 1: Project Setup",[651,140160,107375,140161,140164],{},[812,140162,77478],{"href":51358,"rel":140163},[816]," and select the following dependencies:",[5316,140166,140167,140169,140171,140174,140177],{},[5332,140168,80827],{},[5332,140170,138029],{},[5332,140172,140173],{},"PDF Document Reader",[5332,140175,140176],{},"PGVector Store",[5332,140178,140179],{},"Docker Compose Support",[5909,140181,140183],{"id":140182},"step-2-configuration","Step 2: Configuration",[651,140185,140186],{},"Configure your application properties:",[669,140188,140190],{"className":76589,"code":140189,"language":35538,"meta":674,"style":674},"spring.ai.openai.api-key=${OPENAI_API_KEY}\nspring.ai.openai.chat.model=gpt-4\nspring.ai.vectorstore.pgvector.initialize-schema=true\n",[676,140191,140192,140198,140204],{"__ignoreMap":674},[679,140193,140194,140196],{"class":681,"line":682},[679,140195,122493],{"class":685},[679,140197,122496],{"class":693},[679,140199,140200,140202],{"class":681,"line":790},[679,140201,138821],{"class":685},[679,140203,138162],{"class":693},[679,140205,140206,140209],{"class":681,"line":892},[679,140207,140208],{"class":685},"spring.ai.vectorstore.pgvector.initialize-schema",[679,140210,93485],{"class":693},[651,140212,140213],{},"Set up your vector database using Docker Compose:",[669,140215,140217],{"className":67476,"code":140216,"language":56308,"meta":674,"style":674},"services:\n db:\n image: pgvector/pgvector:pg16\n environment:\n POSTGRES_DB: markets\n POSTGRES_USER: user\n POSTGRES_PASSWORD: password\n ports:\n - \"5432:5432\"\n",[676,140218,140219,140225,140231,140240,140246,140255,140264,140272,140278],{"__ignoreMap":674},[679,140220,140221,140223],{"class":681,"line":682},[679,140222,107180],{"class":4508},[679,140224,3327],{"class":693},[679,140226,140227,140229],{"class":681,"line":790},[679,140228,107187],{"class":4508},[679,140230,3327],{"class":693},[679,140232,140233,140235,140237],{"class":681,"line":892},[679,140234,107194],{"class":4508},[679,140236,4282],{"class":693},[679,140238,140239],{"class":689},"pgvector/pgvector:pg16\n",[679,140241,140242,140244],{"class":681,"line":901},[679,140243,107229],{"class":4508},[679,140245,3327],{"class":693},[679,140247,140248,140250,140252],{"class":681,"line":909},[679,140249,107255],{"class":4508},[679,140251,4282],{"class":693},[679,140253,140254],{"class":689},"markets\n",[679,140256,140257,140259,140261],{"class":681,"line":918},[679,140258,107236],{"class":4508},[679,140260,4282],{"class":693},[679,140262,140263],{"class":689},"user\n",[679,140265,140266,140268,140270],{"class":681,"line":935},[679,140267,107245],{"class":4508},[679,140269,4282],{"class":693},[679,140271,107250],{"class":689},[679,140273,140274,140276],{"class":681,"line":944},[679,140275,107214],{"class":4508},[679,140277,3327],{"class":693},[679,140279,140280,140282],{"class":681,"line":959},[679,140281,107221],{"class":693},[679,140283,107224],{"class":689},[5909,140285,140287],{"id":140286},"step-3-document-ingestion","Step 3: Document Ingestion",[651,140289,140290],{},"Create a service to handle document processing and storage. If you are following the example from my repo you should have a pdf in the docs directory. If you do not place one of your own documents there and update the\nreference for the marketPDF.",[669,140292,140294],{"className":4107,"code":140293,"language":4109,"meta":674,"style":674},"@Component\npublic class IngestionService implements CommandLineRunner {\n private final VectorStore vectorStore;\n private final Logger log = LoggerFactory.getLogger(IngestionService.class);\n \n @Value(\"classpath:/docs/market-report.pdf\")\n private Resource marketPDF;\n \n public IngestionService(VectorStore vectorStore) {\n this.vectorStore = vectorStore;\n }\n \n @Override\n public void run(String... args) {\n var pdfReader = new ParagraphPdfDocumentReader(marketPDF);\n TextSplitter textSplitter = new TokenTextSplitter();\n vectorStore.accept(textSplitter.apply(pdfReader.get()));\n log.info(\"Vector store loaded with data\");\n }\n}\n",[676,140295,140296,140302,140317,140326,140343,140347,140360,140367,140371,140385,140397,140401,140405,140411,140425,140442,140456,140477,140490,140494],{"__ignoreMap":674},[679,140297,140298,140300],{"class":681,"line":682},[679,140299,4116],{"class":693},[679,140301,13105],{"class":685},[679,140303,140304,140306,140308,140311,140313,140315],{"class":681,"line":790},[679,140305,6073],{"class":685},[679,140307,4512],{"class":685},[679,140309,140310],{"class":880}," IngestionService",[679,140312,4661],{"class":685},[679,140314,16444],{"class":880},[679,140316,884],{"class":693},[679,140318,140319,140321,140323],{"class":681,"line":892},[679,140320,9232],{"class":685},[679,140322,12768],{"class":685},[679,140324,140325],{"class":693}," VectorStore vectorStore;\n",[679,140327,140328,140330,140332,140334,140336,140338,140340],{"class":681,"line":901},[679,140329,9232],{"class":685},[679,140331,12768],{"class":685},[679,140333,111111],{"class":693},[679,140335,686],{"class":685},[679,140337,9240],{"class":693},[679,140339,9243],{"class":880},[679,140341,140342],{"class":693},"(IngestionService.class);\n",[679,140344,140345],{"class":681,"line":909},[679,140346,119642],{"class":693},[679,140348,140349,140351,140353,140355,140358],{"class":681,"line":918},[679,140350,6872],{"class":693},[679,140352,31784],{"class":685},[679,140354,745],{"class":693},[679,140356,140357],{"class":689},"\"classpath:/docs/market-report.pdf\"",[679,140359,1339],{"class":693},[679,140361,140362,140364],{"class":681,"line":935},[679,140363,9232],{"class":685},[679,140365,140366],{"class":693}," Resource marketPDF;\n",[679,140368,140369],{"class":681,"line":944},[679,140370,119642],{"class":693},[679,140372,140373,140375,140377,140380,140383],{"class":681,"line":959},[679,140374,6089],{"class":685},[679,140376,140310],{"class":880},[679,140378,140379],{"class":693},"(VectorStore ",[679,140381,140382],{"class":2099},"vectorStore",[679,140384,4390],{"class":693},[679,140386,140387,140389,140392,140394],{"class":681,"line":964},[679,140388,7862],{"class":931},[679,140390,140391],{"class":693},".vectorStore ",[679,140393,686],{"class":685},[679,140395,140396],{"class":693}," vectorStore;\n",[679,140398,140399],{"class":681,"line":977},[679,140400,985],{"class":693},[679,140402,140403],{"class":681,"line":982},[679,140404,119642],{"class":693},[679,140406,140407,140409],{"class":681,"line":988},[679,140408,6872],{"class":693},[679,140410,10723],{"class":685},[679,140412,140413,140415,140417,140419,140421,140423],{"class":681,"line":993},[679,140414,6089],{"class":685},[679,140416,6095],{"class":685},[679,140418,16486],{"class":880},[679,140420,16489],{"class":693},[679,140422,6108],{"class":2099},[679,140424,4390],{"class":693},[679,140426,140427,140429,140432,140434,140436,140439],{"class":681,"line":2129},[679,140428,94003],{"class":685},[679,140430,140431],{"class":693}," pdfReader ",[679,140433,686],{"class":685},[679,140435,2054],{"class":685},[679,140437,140438],{"class":880}," ParagraphPdfDocumentReader",[679,140440,140441],{"class":693},"(marketPDF);\n",[679,140443,140444,140447,140449,140451,140454],{"class":681,"line":2140},[679,140445,140446],{"class":693}," TextSplitter textSplitter ",[679,140448,686],{"class":685},[679,140450,2054],{"class":685},[679,140452,140453],{"class":880}," TokenTextSplitter",[679,140455,9317],{"class":693},[679,140457,140458,140461,140464,140467,140470,140473,140475],{"class":681,"line":2145},[679,140459,140460],{"class":693}," vectorStore.",[679,140462,140463],{"class":880},"accept",[679,140465,140466],{"class":693},"(textSplitter.",[679,140468,140469],{"class":880},"apply",[679,140471,140472],{"class":693},"(pdfReader.",[679,140474,7626],{"class":880},[679,140476,111968],{"class":693},[679,140478,140479,140481,140483,140485,140488],{"class":681,"line":2154},[679,140480,119944],{"class":693},[679,140482,9415],{"class":880},[679,140484,745],{"class":693},[679,140486,140487],{"class":689},"\"Vector store loaded with data\"",[679,140489,1208],{"class":693},[679,140491,140492],{"class":681,"line":2159},[679,140493,985],{"class":693},[679,140495,140496],{"class":681,"line":2164},[679,140497,996],{"class":693},[5909,140499,140501],{"id":140500},"step-4-query-processing","Step 4: Query Processing",[651,140503,140504,140505,140509],{},"Create a controller to handle RAG queries. This will create a GET mapping for the root which can be located at ",[812,140506,140507],{"href":140507,"rel":140508},"http://locahost:8080/",[816]," by default.",[669,140511,140513],{"className":4107,"code":140512,"language":4109,"meta":674,"style":674},"@RestController\npublic class ChatController {\n private final ChatClient chatClient;\n\n public ChatController(ChatClient.Builder builder, VectorStore vectorStore) {\n this.chatClient = builder\n .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))\n .build();\n }\n\n @GetMapping(\"/\")\n public String chat(@RequestParam(defaultValue = \"What's the current state of the market?\") String query) {\n return chatClient.prompt()\n .user(query)\n .call()\n .content();\n }\n}\n",[676,140514,140515,140521,140531,140539,140543,140560,140570,140585,140593,140597,140601,140613,140640,140650,140659,140667,140675,140679],{"__ignoreMap":674},[679,140516,140517,140519],{"class":681,"line":682},[679,140518,4116],{"class":693},[679,140520,9212],{"class":685},[679,140522,140523,140525,140527,140529],{"class":681,"line":790},[679,140524,6073],{"class":685},[679,140526,4512],{"class":685},[679,140528,121635],{"class":880},[679,140530,884],{"class":693},[679,140532,140533,140535,140537],{"class":681,"line":892},[679,140534,9232],{"class":685},[679,140536,12768],{"class":685},[679,140538,121650],{"class":693},[679,140540,140541],{"class":681,"line":901},[679,140542,889],{"emptyLinePlaceholder":797},[679,140544,140545,140547,140549,140551,140553,140556,140558],{"class":681,"line":909},[679,140546,6089],{"class":685},[679,140548,121635],{"class":880},[679,140550,121663],{"class":693},[679,140552,90934],{"class":2099},[679,140554,140555],{"class":693},", VectorStore ",[679,140557,140382],{"class":2099},[679,140559,4390],{"class":693},[679,140561,140562,140564,140566,140568],{"class":681,"line":918},[679,140563,7862],{"class":931},[679,140565,121674],{"class":693},[679,140567,686],{"class":685},[679,140569,119021],{"class":693},[679,140571,140572,140574,140576,140578,140580,140582],{"class":681,"line":935},[679,140573,73482],{"class":693},[679,140575,121917],{"class":880},[679,140577,745],{"class":693},[679,140579,8930],{"class":685},[679,140581,121850],{"class":880},[679,140583,140584],{"class":693},"(vectorStore))\n",[679,140586,140587,140589,140591],{"class":681,"line":944},[679,140588,73482],{"class":693},[679,140590,23612],{"class":880},[679,140592,9317],{"class":693},[679,140594,140595],{"class":681,"line":959},[679,140596,985],{"class":693},[679,140598,140599],{"class":681,"line":964},[679,140600,889],{"emptyLinePlaceholder":797},[679,140602,140603,140605,140607,140609,140611],{"class":681,"line":977},[679,140604,6872],{"class":693},[679,140606,20852],{"class":685},[679,140608,745],{"class":693},[679,140610,10032],{"class":689},[679,140612,1339],{"class":693},[679,140614,140615,140617,140619,140621,140623,140625,140627,140629,140631,140634,140636,140638],{"class":681,"line":982},[679,140616,6089],{"class":685},[679,140618,9289],{"class":693},[679,140620,129981],{"class":880},[679,140622,73246],{"class":693},[679,140624,73249],{"class":685},[679,140626,745],{"class":693},[679,140628,73254],{"class":931},[679,140630,6883],{"class":685},[679,140632,140633],{"class":689}," \"What's the current state of the market?\"",[679,140635,73262],{"class":693},[679,140637,115846],{"class":2099},[679,140639,4390],{"class":693},[679,140641,140642,140644,140646,140648],{"class":681,"line":988},[679,140643,9444],{"class":685},[679,140645,121763],{"class":693},[679,140647,55494],{"class":880},[679,140649,17545],{"class":693},[679,140651,140652,140654,140656],{"class":681,"line":993},[679,140653,73482],{"class":693},[679,140655,9575],{"class":880},[679,140657,140658],{"class":693},"(query)\n",[679,140660,140661,140663,140665],{"class":681,"line":2129},[679,140662,73482],{"class":693},[679,140664,121783],{"class":880},[679,140666,17545],{"class":693},[679,140668,140669,140671,140673],{"class":681,"line":2140},[679,140670,73482],{"class":693},[679,140672,47833],{"class":880},[679,140674,9317],{"class":693},[679,140676,140677],{"class":681,"line":2145},[679,140678,985],{"class":693},[679,140680,140681],{"class":681,"line":2154},[679,140682,996],{"class":693},[651,140684,140685,140686,140689],{},"If you run the application you can send a GET request to ",[812,140687,32924],{"href":32924,"rel":140688},[816]," and the chat method will be executed. If you provide your own documents just update the user query above.",[4542,140691,74030],{"id":140692},"best-practices",[651,140694,140695],{},"When implementing RAG with Spring AI, keep these best practices in mind:",[5909,140697,140699],{"id":140698},"document-processing","Document Processing",[5316,140701,140702,140705,140708],{},[5332,140703,140704],{},"Split documents into meaningful chunks",[5332,140706,140707],{},"Consider document update frequency",[5332,140709,140710],{},"Handle processing errors gracefully",[5909,140712,140714],{"id":140713},"resource-optimization","Resource Optimization",[5316,140716,140717,140720,140723],{},[5332,140718,140719],{},"Monitor token usage",[5332,140721,140722],{},"Implement caching where appropriate",[5332,140724,140725],{},"Use batch processing for large document sets",[5909,140727,140729],{"id":140728},"security-considerations","Security Considerations",[5316,140731,140732,140735,140738],{},[5332,140733,140734],{},"Protect sensitive document content",[5332,140736,140737],{},"Implement proper authentication",[5332,140739,140740],{},"Secure API endpoints",[4542,140742,140744],{"id":140743},"next-steps","Next Steps",[651,140746,140747],{},"Now that you understand the basics of RAG with Spring AI, consider these advanced topics:",[27665,140749,140750,140761,140767],{},[5332,140751,140752,140755,140756,140760],{},[2939,140753,140754],{},"Local LLM Integration",": Use ",[812,140757,126448],{"href":140758,"rel":140759},"https://ollama.ai",[816]," for processing sensitive data locally",[5332,140762,140763,140766],{},[2939,140764,140765],{},"Custom Document Readers",": Create readers for your specific document formats",[5332,140768,140769,140772],{},[2939,140770,140771],{},"Advanced Vector Search",": Implement filtering and hybrid search strategies",[651,140774,140775,140776,140781],{},"You can find more examples in the ",[812,140777,140780],{"href":140778,"rel":140779},"https://docs.spring.io/spring-ai/reference/",[816],"Spring AI documentation",", including advanced configuration options and additional vector store implementations.",[4542,140783,9042],{"id":9041},[651,140785,140786],{},"RAG with Spring AI provides a powerful way to enhance your applications with domain-specific knowledge while leveraging the capabilities of Large Language Models. By following the implementation patterns and best practices outlined in this guide, you can build intelligent applications that provide accurate, contextually relevant responses while maintaining control over costs and performance.",[651,140788,140789],{},"Remember that RAG isn't just about feeding documents to an LLM – it's about intelligently selecting and using relevant information to enhance your AI applications. As you build your RAG implementation, focus on optimizing both the ingestion and retrieval phases to create efficient, cost-effective solutions.",[651,140791,78024],{},[786,140793,140794],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":140796},[140797,140798,140802,140808,140813,140814],{"id":140071,"depth":790,"text":140072},{"id":140101,"depth":790,"text":140102,"children":140799},[140800,140801],{"id":140108,"depth":892,"text":140109},{"id":138561,"depth":892,"text":138562},{"id":140150,"depth":790,"text":140151,"children":140803},[140804,140805,140806,140807],{"id":140157,"depth":892,"text":140158},{"id":140182,"depth":892,"text":140183},{"id":140286,"depth":892,"text":140287},{"id":140500,"depth":892,"text":140501},{"id":140692,"depth":790,"text":74030,"children":140809},[140810,140811,140812],{"id":140698,"depth":892,"text":140699},{"id":140713,"depth":892,"text":140714},{"id":140728,"depth":892,"text":140729},{"id":140743,"depth":790,"text":140744},{"id":9041,"depth":790,"text":9042},"Learn how to build intelligent applications using Retrieval Augmented Generation (RAG) with Spring AI. This practical guide covers everything from understanding tokens and context windows to implementing a full RAG solution.",{"slug":140817,"date":140818,"published":797,"author":798,"tags":140819,"keywords":140821,"video":140822},"getting-started-with-spring-ai-rag","2024-10-22T09:00:00.000Z",[7055,140820,4109],"spring-ai",[123549,7077,36422],"https://www.youtube.com/embed/6Pgmr7xMjiY",{"title":48,"description":140815},"blog/2024/10/22/getting-started-with-spring-ai-rag","9sxTWoHKDJtOCpBVBCxfpIlqv_dcvpYjhIdnf1xlqX4",{"id":140827,"title":45,"body":140828,"description":142872,"extension":793,"meta":142873,"navigation":797,"path":46,"seo":142881,"stem":142882,"__hash__":142883},"content/blog/2024/10/24/spring-boot-oauth-demo.md",{"type":648,"value":140829,"toc":142857},[140830,140833,140835,140838,141036,141038,141043,141366,141370,141376,141382,141388,141505,141509,141515,142169,142173,142176,142423,142426,142430,142507,142511,142547,142551,142556,142689,142692,142745,142749,142752,142772,142776,142779,142815,142817,142820,142823,142834,142837,142851,142854],[651,140831,140832],{},"Spring Security is a powerful framework for adding authentication and authorization to your applications. When combined with JTE (Java Template Engine), you can create elegant, secure login forms while maintaining clean separation between your security logic and presentation layer. In this tutorial, we'll build a custom login form that supports both traditional username/password authentication and OAuth2 providers like Google and GitHub.",[4542,140834,94281],{"id":94280},[651,140836,140837],{},"Start by creating a new Spring Boot project with the following dependencies:",[669,140839,140841],{"className":9101,"code":140840,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-security\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-oauth2-client\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>gg.jte\u003C/groupId>\n \u003CartifactId>jte-spring-boot-starter\u003C/artifactId>\n \u003Cversion>3.1.9\u003C/version>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,140842,140843,140851,140859,140871,140883,140891,140899,140911,140924,140932,140940,140952,140965,140973,140981,140994,141007,141020,141028],{"__ignoreMap":674},[679,140844,140845,140847,140849],{"class":681,"line":682},[679,140846,4505],{"class":693},[679,140848,129682],{"class":4508},[679,140850,4519],{"class":693},[679,140852,140853,140855,140857],{"class":681,"line":790},[679,140854,4524],{"class":693},[679,140856,119838],{"class":4508},[679,140858,4519],{"class":693},[679,140860,140861,140863,140865,140867,140869],{"class":681,"line":892},[679,140862,4904],{"class":693},[679,140864,119847],{"class":4508},[679,140866,119850],{"class":693},[679,140868,119847],{"class":4508},[679,140870,4519],{"class":693},[679,140872,140873,140875,140877,140879,140881],{"class":681,"line":901},[679,140874,4904],{"class":693},[679,140876,119861],{"class":4508},[679,140878,138077],{"class":693},[679,140880,119861],{"class":4508},[679,140882,4519],{"class":693},[679,140884,140885,140887,140889],{"class":681,"line":909},[679,140886,4577],{"class":693},[679,140888,119838],{"class":4508},[679,140890,4519],{"class":693},[679,140892,140893,140895,140897],{"class":681,"line":918},[679,140894,4524],{"class":693},[679,140896,119838],{"class":4508},[679,140898,4519],{"class":693},[679,140900,140901,140903,140905,140907,140909],{"class":681,"line":935},[679,140902,4904],{"class":693},[679,140904,119847],{"class":4508},[679,140906,119850],{"class":693},[679,140908,119847],{"class":4508},[679,140910,4519],{"class":693},[679,140912,140913,140915,140917,140920,140922],{"class":681,"line":944},[679,140914,4904],{"class":693},[679,140916,119861],{"class":4508},[679,140918,140919],{"class":693},">spring-boot-starter-security\u003C/",[679,140921,119861],{"class":4508},[679,140923,4519],{"class":693},[679,140925,140926,140928,140930],{"class":681,"line":959},[679,140927,4577],{"class":693},[679,140929,119838],{"class":4508},[679,140931,4519],{"class":693},[679,140933,140934,140936,140938],{"class":681,"line":964},[679,140935,4524],{"class":693},[679,140937,119838],{"class":4508},[679,140939,4519],{"class":693},[679,140941,140942,140944,140946,140948,140950],{"class":681,"line":977},[679,140943,4904],{"class":693},[679,140945,119847],{"class":4508},[679,140947,119850],{"class":693},[679,140949,119847],{"class":4508},[679,140951,4519],{"class":693},[679,140953,140954,140956,140958,140961,140963],{"class":681,"line":982},[679,140955,4904],{"class":693},[679,140957,119861],{"class":4508},[679,140959,140960],{"class":693},">spring-boot-starter-oauth2-client\u003C/",[679,140962,119861],{"class":4508},[679,140964,4519],{"class":693},[679,140966,140967,140969,140971],{"class":681,"line":988},[679,140968,4577],{"class":693},[679,140970,119838],{"class":4508},[679,140972,4519],{"class":693},[679,140974,140975,140977,140979],{"class":681,"line":993},[679,140976,4524],{"class":693},[679,140978,119838],{"class":4508},[679,140980,4519],{"class":693},[679,140982,140983,140985,140987,140990,140992],{"class":681,"line":2129},[679,140984,4904],{"class":693},[679,140986,119847],{"class":4508},[679,140988,140989],{"class":693},">gg.jte\u003C/",[679,140991,119847],{"class":4508},[679,140993,4519],{"class":693},[679,140995,140996,140998,141000,141003,141005],{"class":681,"line":2140},[679,140997,4904],{"class":693},[679,140999,119861],{"class":4508},[679,141001,141002],{"class":693},">jte-spring-boot-starter\u003C/",[679,141004,119861],{"class":4508},[679,141006,4519],{"class":693},[679,141008,141009,141011,141013,141016,141018],{"class":681,"line":2145},[679,141010,4904],{"class":693},[679,141012,107166],{"class":4508},[679,141014,141015],{"class":693},">3.1.9\u003C/",[679,141017,107166],{"class":4508},[679,141019,4519],{"class":693},[679,141021,141022,141024,141026],{"class":681,"line":2154},[679,141023,4577],{"class":693},[679,141025,119838],{"class":4508},[679,141027,4519],{"class":693},[679,141029,141030,141032,141034],{"class":681,"line":2159},[679,141031,4586],{"class":693},[679,141033,129682],{"class":4508},[679,141035,4519],{"class":693},[4542,141037,106181],{"id":106180},[651,141039,77744,141040,141042],{},[676,141041,89455],{}," class to configure Spring Security with support for both form-based and OAuth2 authentication:",[669,141044,141046],{"className":4107,"code":141045,"language":4109,"meta":674,"style":674},"@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n @Bean\n public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http\n .authorizeHttpRequests(auth -> auth\n .requestMatchers(\"/login\", \"/error\").permitAll()\n .anyRequest().authenticated())\n .formLogin(form -> form\n .loginPage(\"/login\")\n .defaultSuccessUrl(\"/dashboard\")\n .permitAll())\n .oauth2Login(oauth -> oauth\n .loginPage(\"/login\")\n .defaultSuccessUrl(\"/dashboard\"))\n .logout(logout -> logout\n .logoutSuccessUrl(\"/\"))\n .build();\n }\n\n @Bean\n public InMemoryUserDetailsManager userDetailsManager() {\n UserDetails user = User.withUsername(\"admin\")\n .password(\"{noop}admin123\")\n .roles(\"ADMIN\")\n .build();\n return new InMemoryUserDetailsManager(user);\n }\n}\n",[676,141047,141048,141054,141060,141070,141074,141080,141098,141104,141116,141137,141149,141163,141176,141190,141198,141212,141224,141236,141251,141264,141272,141276,141280,141286,141297,141314,141327,141340,141348,141358,141362],{"__ignoreMap":674},[679,141049,141050,141052],{"class":681,"line":682},[679,141051,4116],{"class":693},[679,141053,6212],{"class":685},[679,141055,141056,141058],{"class":681,"line":790},[679,141057,4116],{"class":693},[679,141059,89474],{"class":685},[679,141061,141062,141064,141066,141068],{"class":681,"line":892},[679,141063,6073],{"class":685},[679,141065,4512],{"class":685},[679,141067,89483],{"class":880},[679,141069,884],{"class":693},[679,141071,141072],{"class":681,"line":901},[679,141073,889],{"emptyLinePlaceholder":797},[679,141075,141076,141078],{"class":681,"line":909},[679,141077,6872],{"class":693},[679,141079,16929],{"class":685},[679,141081,141082,141084,141086,141088,141090,141092,141094,141096],{"class":681,"line":918},[679,141083,6089],{"class":685},[679,141085,89502],{"class":693},[679,141087,89505],{"class":880},[679,141089,89508],{"class":693},[679,141091,89511],{"class":2099},[679,141093,2378],{"class":693},[679,141095,9580],{"class":685},[679,141097,10466],{"class":693},[679,141099,141100,141102],{"class":681,"line":935},[679,141101,9444],{"class":685},[679,141103,89524],{"class":693},[679,141105,141106,141108,141110,141112,141114],{"class":681,"line":944},[679,141107,40148],{"class":693},[679,141109,95392],{"class":880},[679,141111,95465],{"class":693},[679,141113,16955],{"class":685},[679,141115,89561],{"class":693},[679,141117,141118,141120,141122,141124,141127,141129,141131,141133,141135],{"class":681,"line":959},[679,141119,73482],{"class":693},[679,141121,40124],{"class":880},[679,141123,745],{"class":693},[679,141125,141126],{"class":689},"\"/login\"",[679,141128,2797],{"class":693},[679,141130,106674],{"class":689},[679,141132,47213],{"class":693},[679,141134,40151],{"class":880},[679,141136,17545],{"class":693},[679,141138,141139,141141,141143,141145,141147],{"class":681,"line":964},[679,141140,73482],{"class":693},[679,141142,89569],{"class":880},[679,141144,10541],{"class":693},[679,141146,89574],{"class":880},[679,141148,40172],{"class":693},[679,141150,141151,141153,141155,141158,141160],{"class":681,"line":977},[679,141152,40148],{"class":693},[679,141154,95511],{"class":880},[679,141156,141157],{"class":693},"(form ",[679,141159,16955],{"class":685},[679,141161,141162],{"class":693}," form\n",[679,141164,141165,141167,141170,141172,141174],{"class":681,"line":982},[679,141166,73482],{"class":693},[679,141168,141169],{"class":880},"loginPage",[679,141171,745],{"class":693},[679,141173,141126],{"class":689},[679,141175,1339],{"class":693},[679,141177,141178,141180,141183,141185,141188],{"class":681,"line":988},[679,141179,73482],{"class":693},[679,141181,141182],{"class":880},"defaultSuccessUrl",[679,141184,745],{"class":693},[679,141186,141187],{"class":689},"\"/dashboard\"",[679,141189,1339],{"class":693},[679,141191,141192,141194,141196],{"class":681,"line":993},[679,141193,73482],{"class":693},[679,141195,40151],{"class":880},[679,141197,40172],{"class":693},[679,141199,141200,141202,141204,141207,141209],{"class":681,"line":2129},[679,141201,40148],{"class":693},[679,141203,107671],{"class":880},[679,141205,141206],{"class":693},"(oauth ",[679,141208,16955],{"class":685},[679,141210,141211],{"class":693}," oauth\n",[679,141213,141214,141216,141218,141220,141222],{"class":681,"line":2140},[679,141215,73482],{"class":693},[679,141217,141169],{"class":880},[679,141219,745],{"class":693},[679,141221,141126],{"class":689},[679,141223,1339],{"class":693},[679,141225,141226,141228,141230,141232,141234],{"class":681,"line":2145},[679,141227,73482],{"class":693},[679,141229,141182],{"class":880},[679,141231,745],{"class":693},[679,141233,141187],{"class":689},[679,141235,40143],{"class":693},[679,141237,141238,141240,141243,141246,141248],{"class":681,"line":2154},[679,141239,40148],{"class":693},[679,141241,141242],{"class":880},"logout",[679,141244,141245],{"class":693},"(logout ",[679,141247,16955],{"class":685},[679,141249,141250],{"class":693}," logout\n",[679,141252,141253,141255,141258,141260,141262],{"class":681,"line":2159},[679,141254,73482],{"class":693},[679,141256,141257],{"class":880},"logoutSuccessUrl",[679,141259,745],{"class":693},[679,141261,10032],{"class":689},[679,141263,40143],{"class":693},[679,141265,141266,141268,141270],{"class":681,"line":2164},[679,141267,40148],{"class":693},[679,141269,23612],{"class":880},[679,141271,9317],{"class":693},[679,141273,141274],{"class":681,"line":3134},[679,141275,985],{"class":693},[679,141277,141278],{"class":681,"line":3139},[679,141279,889],{"emptyLinePlaceholder":797},[679,141281,141282,141284],{"class":681,"line":3144},[679,141283,6872],{"class":693},[679,141285,16929],{"class":685},[679,141287,141288,141290,141292,141295],{"class":681,"line":3149},[679,141289,6089],{"class":685},[679,141291,89695],{"class":693},[679,141293,141294],{"class":880},"userDetailsManager",[679,141296,2667],{"class":693},[679,141298,141299,141302,141304,141306,141308,141310,141312],{"class":681,"line":3169},[679,141300,141301],{"class":693}," UserDetails user ",[679,141303,686],{"class":685},[679,141305,1331],{"class":693},[679,141307,89718],{"class":880},[679,141309,745],{"class":693},[679,141311,63553],{"class":689},[679,141313,1339],{"class":693},[679,141315,141316,141318,141320,141322,141325],{"class":681,"line":3185},[679,141317,40148],{"class":693},[679,141319,24067],{"class":880},[679,141321,745],{"class":693},[679,141323,141324],{"class":689},"\"{noop}admin123\"",[679,141326,1339],{"class":693},[679,141328,141329,141331,141334,141336,141338],{"class":681,"line":3194},[679,141330,40148],{"class":693},[679,141332,141333],{"class":880},"roles",[679,141335,745],{"class":693},[679,141337,102371],{"class":689},[679,141339,1339],{"class":693},[679,141341,141342,141344,141346],{"class":681,"line":3199},[679,141343,40148],{"class":693},[679,141345,23612],{"class":880},[679,141347,9317],{"class":693},[679,141349,141350,141352,141354,141356],{"class":681,"line":3212},[679,141351,9444],{"class":685},[679,141353,2054],{"class":685},[679,141355,89708],{"class":880},[679,141357,9386],{"class":693},[679,141359,141360],{"class":681,"line":3217},[679,141361,985],{"class":693},[679,141363,141364],{"class":681,"line":3222},[679,141365,996],{"class":693},[4542,141367,141369],{"id":141368},"building-the-template-structure","Building the Template Structure",[651,141371,141372,141373,2391],{},"JTE templates are organized in a clear hierarchy under ",[676,141374,141375],{},"/src/main/jte",[669,141377,141380],{"className":141378,"code":141379,"language":11464},[16247],"src/\n main/\n jte/\n layout/\n default.jte # Base layout template\n pages/\n login.jte # Login form\n dashboard.jte # Protected dashboard\n home.jte # Public home page\n",[676,141381,141379],{"__ignoreMap":674},[651,141383,141384,141385,100725],{},"Create a base layout template (",[676,141386,141387],{},"default.jte",[669,141389,141391],{"className":4496,"code":141390,"language":4498,"meta":674,"style":674},"@import org.springframework.security.web.csrf.CsrfToken\n@param CsrfToken csrf = null\n@param Content content\n\n\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n \u003Ctitle>Spring Security with JTE\u003C/title>\n \u003Cscript src=\"https://cdn.tailwindcss.com\">\u003C/script>\n\u003C/head>\n\u003Cbody>\n ${content}\n\u003C/body>\n\u003C/html>\n",[676,141392,141393,141398,141403,141407,141411,141421,141429,141437,141450,141468,141476,141484,141489,141497],{"__ignoreMap":674},[679,141394,141395],{"class":681,"line":682},[679,141396,141397],{"class":693},"@import org.springframework.security.web.csrf.CsrfToken\n",[679,141399,141400],{"class":681,"line":790},[679,141401,141402],{"class":693},"@param CsrfToken csrf = null\n",[679,141404,141405],{"class":681,"line":892},[679,141406,132846],{"class":693},[679,141408,141409],{"class":681,"line":901},[679,141410,889],{"emptyLinePlaceholder":797},[679,141412,141413,141415,141417,141419],{"class":681,"line":909},[679,141414,11904],{"class":693},[679,141416,11907],{"class":4508},[679,141418,11910],{"class":880},[679,141420,4519],{"class":693},[679,141422,141423,141425,141427],{"class":681,"line":918},[679,141424,4505],{"class":693},[679,141426,4498],{"class":4508},[679,141428,4519],{"class":693},[679,141430,141431,141433,141435],{"class":681,"line":935},[679,141432,4505],{"class":693},[679,141434,11741],{"class":4508},[679,141436,4519],{"class":693},[679,141438,141439,141441,141443,141446,141448],{"class":681,"line":944},[679,141440,4524],{"class":693},[679,141442,11750],{"class":4508},[679,141444,141445],{"class":693},">Spring Security with JTE\u003C/",[679,141447,11750],{"class":4508},[679,141449,4519],{"class":693},[679,141451,141452,141454,141456,141458,141460,141462,141464,141466],{"class":681,"line":959},[679,141453,4524],{"class":693},[679,141455,47668],{"class":4508},[679,141457,5361],{"class":880},[679,141459,686],{"class":693},[679,141461,124580],{"class":689},[679,141463,4563],{"class":693},[679,141465,47668],{"class":4508},[679,141467,4519],{"class":693},[679,141469,141470,141472,141474],{"class":681,"line":964},[679,141471,4586],{"class":693},[679,141473,11741],{"class":4508},[679,141475,4519],{"class":693},[679,141477,141478,141480,141482],{"class":681,"line":977},[679,141479,4505],{"class":693},[679,141481,3006],{"class":4508},[679,141483,4519],{"class":693},[679,141485,141486],{"class":681,"line":982},[679,141487,141488],{"class":693}," ${content}\n",[679,141490,141491,141493,141495],{"class":681,"line":988},[679,141492,4586],{"class":693},[679,141494,3006],{"class":4508},[679,141496,4519],{"class":693},[679,141498,141499,141501,141503],{"class":681,"line":993},[679,141500,4586],{"class":693},[679,141502,4498],{"class":4508},[679,141504,4519],{"class":693},[4542,141506,141508],{"id":141507},"implementing-the-login-form","Implementing the Login Form",[651,141510,141511,141512,100725],{},"Create the login form template (",[676,141513,141514],{},"login.jte",[669,141516,141518],{"className":4496,"code":141517,"language":4498,"meta":674,"style":674},"@param String error = null\n@param String logout = null\n@param CsrfToken csrf\n\n@layout.default(csrf = csrf)\n \u003Cdiv class=\"min-h-screen flex items-center justify-center\">\n \u003Cdiv class=\"max-w-md w-full space-y-8\">\n \u003Cdiv>\n \u003Ch2 class=\"text-center text-3xl font-bold\">Sign in to your account\u003C/h2>\n \u003C/div>\n\n @if(error != null)\n \u003Cdiv class=\"bg-red-100 p-4 rounded-md\">\n Invalid username or password\n \u003C/div>\n @endif\n\n \u003Cform class=\"mt-8 space-y-6\" action=\"/login\" method=\"POST\">\n @if(csrf != null)\n \u003Cinput type=\"hidden\" name=\"${csrf.parameterName}\" value=\"${csrf.token}\">\n @endif\n\n \u003Cdiv>\n \u003Cinput type=\"text\" name=\"username\" required \n class=\"appearance-none rounded-md relative block w-full px-3 py-2 border\"\n placeholder=\"Username\">\n \u003C/div>\n\n \u003Cdiv>\n \u003Cinput type=\"password\" name=\"password\" required \n class=\"appearance-none rounded-md relative block w-full px-3 py-2 border\"\n placeholder=\"Password\">\n \u003C/div>\n\n \u003Cdiv>\n \u003Cbutton type=\"submit\" \n class=\"group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700\">\n Sign in\n \u003C/button>\n \u003C/div>\n \u003C/form>\n\n \u003Cdiv class=\"mt-6\">\n \u003Cdiv class=\"relative\">\n \u003Cdiv class=\"absolute inset-0 flex items-center\">\n \u003Cdiv class=\"w-full border-t border-gray-300\">\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"relative flex justify-center text-sm\">\n \u003Cspan class=\"px-2 bg-white text-gray-500\">Or continue with\u003C/span>\n \u003C/div>\n \u003C/div>\n\n \u003Cdiv class=\"mt-6 grid grid-cols-2 gap-3\">\n \u003Ca href=\"/oauth2/authorization/google\" \n class=\"w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50\">\n Google\n \u003C/a>\n \u003Ca href=\"/oauth2/authorization/github\"\n class=\"w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50\">\n GitHub\n \u003C/a>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n",[676,141519,141520,141525,141530,141535,141539,141544,141559,141574,141582,141602,141610,141614,141619,141634,141639,141647,141652,141656,141683,141688,141717,141722,141726,141734,141757,141767,141779,141787,141791,141799,141821,141829,141840,141848,141852,141860,141874,141886,141891,141899,141907,141915,141919,141934,141949,141964,141983,141991,142006,142026,142034,142042,142046,142061,142076,142088,142093,142101,142114,142124,142129,142137,142145,142153,142161],{"__ignoreMap":674},[679,141521,141522],{"class":681,"line":682},[679,141523,141524],{"class":693},"@param String error = null\n",[679,141526,141527],{"class":681,"line":790},[679,141528,141529],{"class":693},"@param String logout = null\n",[679,141531,141532],{"class":681,"line":892},[679,141533,141534],{"class":693},"@param CsrfToken csrf\n",[679,141536,141537],{"class":681,"line":901},[679,141538,889],{"emptyLinePlaceholder":797},[679,141540,141541],{"class":681,"line":909},[679,141542,141543],{"class":693},"@layout.default(csrf = csrf)\n",[679,141545,141546,141548,141550,141552,141554,141557],{"class":681,"line":918},[679,141547,4524],{"class":693},[679,141549,4509],{"class":4508},[679,141551,4512],{"class":880},[679,141553,686],{"class":693},[679,141555,141556],{"class":689},"\"min-h-screen flex items-center justify-center\"",[679,141558,4519],{"class":693},[679,141560,141561,141563,141565,141567,141569,141572],{"class":681,"line":935},[679,141562,4904],{"class":693},[679,141564,4509],{"class":4508},[679,141566,4512],{"class":880},[679,141568,686],{"class":693},[679,141570,141571],{"class":689},"\"max-w-md w-full space-y-8\"",[679,141573,4519],{"class":693},[679,141575,141576,141578,141580],{"class":681,"line":944},[679,141577,5392],{"class":693},[679,141579,4509],{"class":4508},[679,141581,4519],{"class":693},[679,141583,141584,141586,141588,141590,141592,141595,141598,141600],{"class":681,"line":959},[679,141585,125341],{"class":693},[679,141587,4542],{"class":4508},[679,141589,4512],{"class":880},[679,141591,686],{"class":693},[679,141593,141594],{"class":689},"\"text-center text-3xl font-bold\"",[679,141596,141597],{"class":693},">Sign in to your account\u003C/",[679,141599,4542],{"class":4508},[679,141601,4519],{"class":693},[679,141603,141604,141606,141608],{"class":681,"line":964},[679,141605,125356],{"class":693},[679,141607,4509],{"class":4508},[679,141609,4519],{"class":693},[679,141611,141612],{"class":681,"line":977},[679,141613,889],{"emptyLinePlaceholder":797},[679,141615,141616],{"class":681,"line":982},[679,141617,141618],{"class":693}," @if(error != null)\n",[679,141620,141621,141623,141625,141627,141629,141632],{"class":681,"line":988},[679,141622,125341],{"class":693},[679,141624,4509],{"class":4508},[679,141626,4512],{"class":880},[679,141628,686],{"class":693},[679,141630,141631],{"class":689},"\"bg-red-100 p-4 rounded-md\"",[679,141633,4519],{"class":693},[679,141635,141636],{"class":681,"line":993},[679,141637,141638],{"class":693}," Invalid username or password\n",[679,141640,141641,141643,141645],{"class":681,"line":2129},[679,141642,134932],{"class":693},[679,141644,4509],{"class":4508},[679,141646,4519],{"class":693},[679,141648,141649],{"class":681,"line":2140},[679,141650,141651],{"class":693}," @endif\n",[679,141653,141654],{"class":681,"line":2145},[679,141655,889],{"emptyLinePlaceholder":797},[679,141657,141658,141660,141662,141664,141666,141669,141671,141673,141675,141677,141679,141681],{"class":681,"line":2154},[679,141659,5392],{"class":693},[679,141661,134744],{"class":4508},[679,141663,4512],{"class":880},[679,141665,686],{"class":693},[679,141667,141668],{"class":689},"\"mt-8 space-y-6\"",[679,141670,135734],{"class":880},[679,141672,686],{"class":693},[679,141674,141126],{"class":689},[679,141676,90100],{"class":880},[679,141678,686],{"class":693},[679,141680,92599],{"class":689},[679,141682,4519],{"class":693},[679,141684,141685],{"class":681,"line":2159},[679,141686,141687],{"class":693}," @if(csrf != null)\n",[679,141689,141690,141692,141694,141696,141698,141701,141703,141705,141708,141710,141712,141715],{"class":681,"line":2164},[679,141691,134881],{"class":693},[679,141693,27722],{"class":4508},[679,141695,27725],{"class":880},[679,141697,686],{"class":693},[679,141699,141700],{"class":689},"\"hidden\"",[679,141702,5283],{"class":880},[679,141704,686],{"class":693},[679,141706,141707],{"class":689},"\"${csrf.parameterName}\"",[679,141709,65142],{"class":880},[679,141711,686],{"class":693},[679,141713,141714],{"class":689},"\"${csrf.token}\"",[679,141716,4519],{"class":693},[679,141718,141719],{"class":681,"line":3134},[679,141720,141721],{"class":693}," @endif\n",[679,141723,141724],{"class":681,"line":3139},[679,141725,889],{"emptyLinePlaceholder":797},[679,141727,141728,141730,141732],{"class":681,"line":3144},[679,141729,125341],{"class":693},[679,141731,4509],{"class":4508},[679,141733,4519],{"class":693},[679,141735,141736,141738,141740,141742,141744,141746,141748,141750,141752,141755],{"class":681,"line":3149},[679,141737,134881],{"class":693},[679,141739,27722],{"class":4508},[679,141741,27725],{"class":880},[679,141743,686],{"class":693},[679,141745,54445],{"class":689},[679,141747,5283],{"class":880},[679,141749,686],{"class":693},[679,141751,133284],{"class":689},[679,141753,141754],{"class":880}," required",[679,141756,138414],{"class":693},[679,141758,141759,141762,141764],{"class":681,"line":3169},[679,141760,141761],{"class":880}," class",[679,141763,686],{"class":693},[679,141765,141766],{"class":689},"\"appearance-none rounded-md relative block w-full px-3 py-2 border\"\n",[679,141768,141769,141772,141774,141777],{"class":681,"line":3185},[679,141770,141771],{"class":880}," placeholder",[679,141773,686],{"class":693},[679,141775,141776],{"class":689},"\"Username\"",[679,141778,4519],{"class":693},[679,141780,141781,141783,141785],{"class":681,"line":3194},[679,141782,134932],{"class":693},[679,141784,4509],{"class":4508},[679,141786,4519],{"class":693},[679,141788,141789],{"class":681,"line":3199},[679,141790,889],{"emptyLinePlaceholder":797},[679,141792,141793,141795,141797],{"class":681,"line":3212},[679,141794,125341],{"class":693},[679,141796,4509],{"class":4508},[679,141798,4519],{"class":693},[679,141800,141801,141803,141805,141807,141809,141811,141813,141815,141817,141819],{"class":681,"line":3217},[679,141802,134881],{"class":693},[679,141804,27722],{"class":4508},[679,141806,27725],{"class":880},[679,141808,686],{"class":693},[679,141810,91604],{"class":689},[679,141812,5283],{"class":880},[679,141814,686],{"class":693},[679,141816,91604],{"class":689},[679,141818,141754],{"class":880},[679,141820,138414],{"class":693},[679,141822,141823,141825,141827],{"class":681,"line":3222},[679,141824,141761],{"class":880},[679,141826,686],{"class":693},[679,141828,141766],{"class":689},[679,141830,141831,141833,141835,141838],{"class":681,"line":3227},[679,141832,141771],{"class":880},[679,141834,686],{"class":693},[679,141836,141837],{"class":689},"\"Password\"",[679,141839,4519],{"class":693},[679,141841,141842,141844,141846],{"class":681,"line":3232},[679,141843,134932],{"class":693},[679,141845,4509],{"class":4508},[679,141847,4519],{"class":693},[679,141849,141850],{"class":681,"line":3499},[679,141851,889],{"emptyLinePlaceholder":797},[679,141853,141854,141856,141858],{"class":681,"line":3509},[679,141855,125341],{"class":693},[679,141857,4509],{"class":4508},[679,141859,4519],{"class":693},[679,141861,141862,141864,141866,141868,141870,141872],{"class":681,"line":3516},[679,141863,134881],{"class":693},[679,141865,54258],{"class":4508},[679,141867,27725],{"class":880},[679,141869,686],{"class":693},[679,141871,134831],{"class":689},[679,141873,138414],{"class":693},[679,141875,141876,141879,141881,141884],{"class":681,"line":3531},[679,141877,141878],{"class":880}," class",[679,141880,686],{"class":693},[679,141882,141883],{"class":689},"\"group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700\"",[679,141885,4519],{"class":693},[679,141887,141888],{"class":681,"line":3536},[679,141889,141890],{"class":693}," Sign in\n",[679,141892,141893,141895,141897],{"class":681,"line":3541},[679,141894,134923],{"class":693},[679,141896,54258],{"class":4508},[679,141898,4519],{"class":693},[679,141900,141901,141903,141905],{"class":681,"line":3546},[679,141902,134932],{"class":693},[679,141904,4509],{"class":4508},[679,141906,4519],{"class":693},[679,141908,141909,141911,141913],{"class":681,"line":3551},[679,141910,125356],{"class":693},[679,141912,134744],{"class":4508},[679,141914,4519],{"class":693},[679,141916,141917],{"class":681,"line":3557},[679,141918,889],{"emptyLinePlaceholder":797},[679,141920,141921,141923,141925,141927,141929,141932],{"class":681,"line":3567},[679,141922,5392],{"class":693},[679,141924,4509],{"class":4508},[679,141926,4512],{"class":880},[679,141928,686],{"class":693},[679,141930,141931],{"class":689},"\"mt-6\"",[679,141933,4519],{"class":693},[679,141935,141936,141938,141940,141942,141944,141947],{"class":681,"line":3574},[679,141937,125341],{"class":693},[679,141939,4509],{"class":4508},[679,141941,4512],{"class":880},[679,141943,686],{"class":693},[679,141945,141946],{"class":689},"\"relative\"",[679,141948,4519],{"class":693},[679,141950,141951,141953,141955,141957,141959,141962],{"class":681,"line":3589},[679,141952,134881],{"class":693},[679,141954,4509],{"class":4508},[679,141956,4512],{"class":880},[679,141958,686],{"class":693},[679,141960,141961],{"class":689},"\"absolute inset-0 flex items-center\"",[679,141963,4519],{"class":693},[679,141965,141966,141968,141970,141972,141974,141977,141979,141981],{"class":681,"line":3594},[679,141967,134890],{"class":693},[679,141969,4509],{"class":4508},[679,141971,4512],{"class":880},[679,141973,686],{"class":693},[679,141975,141976],{"class":689},"\"w-full border-t border-gray-300\"",[679,141978,4563],{"class":693},[679,141980,4509],{"class":4508},[679,141982,4519],{"class":693},[679,141984,141985,141987,141989],{"class":681,"line":3602},[679,141986,134923],{"class":693},[679,141988,4509],{"class":4508},[679,141990,4519],{"class":693},[679,141992,141993,141995,141997,141999,142001,142004],{"class":681,"line":3608},[679,141994,134881],{"class":693},[679,141996,4509],{"class":4508},[679,141998,4512],{"class":880},[679,142000,686],{"class":693},[679,142002,142003],{"class":689},"\"relative flex justify-center text-sm\"",[679,142005,4519],{"class":693},[679,142007,142008,142010,142012,142014,142016,142019,142022,142024],{"class":681,"line":3619},[679,142009,134890],{"class":693},[679,142011,679],{"class":4508},[679,142013,4512],{"class":880},[679,142015,686],{"class":693},[679,142017,142018],{"class":689},"\"px-2 bg-white text-gray-500\"",[679,142020,142021],{"class":693},">Or continue with\u003C/",[679,142023,679],{"class":4508},[679,142025,4519],{"class":693},[679,142027,142028,142030,142032],{"class":681,"line":3624},[679,142029,134923],{"class":693},[679,142031,4509],{"class":4508},[679,142033,4519],{"class":693},[679,142035,142036,142038,142040],{"class":681,"line":3629},[679,142037,134932],{"class":693},[679,142039,4509],{"class":4508},[679,142041,4519],{"class":693},[679,142043,142044],{"class":681,"line":3639},[679,142045,889],{"emptyLinePlaceholder":797},[679,142047,142048,142050,142052,142054,142056,142059],{"class":681,"line":3644},[679,142049,125341],{"class":693},[679,142051,4509],{"class":4508},[679,142053,4512],{"class":880},[679,142055,686],{"class":693},[679,142057,142058],{"class":689},"\"mt-6 grid grid-cols-2 gap-3\"",[679,142060,4519],{"class":693},[679,142062,142063,142065,142067,142069,142071,142074],{"class":681,"line":3649},[679,142064,134881],{"class":693},[679,142066,812],{"class":4508},[679,142068,4550],{"class":880},[679,142070,686],{"class":693},[679,142072,142073],{"class":689},"\"/oauth2/authorization/google\"",[679,142075,138414],{"class":693},[679,142077,142078,142081,142083,142086],{"class":681,"line":3659},[679,142079,142080],{"class":880}," class",[679,142082,686],{"class":693},[679,142084,142085],{"class":689},"\"w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50\"",[679,142087,4519],{"class":693},[679,142089,142090],{"class":681,"line":3664},[679,142091,142092],{"class":693}," Google\n",[679,142094,142095,142097,142099],{"class":681,"line":3669},[679,142096,134923],{"class":693},[679,142098,812],{"class":4508},[679,142100,4519],{"class":693},[679,142102,142103,142105,142107,142109,142111],{"class":681,"line":3679},[679,142104,134881],{"class":693},[679,142106,812],{"class":4508},[679,142108,4550],{"class":880},[679,142110,686],{"class":693},[679,142112,142113],{"class":689},"\"/oauth2/authorization/github\"\n",[679,142115,142116,142118,142120,142122],{"class":681,"line":3684},[679,142117,142080],{"class":880},[679,142119,686],{"class":693},[679,142121,142085],{"class":689},[679,142123,4519],{"class":693},[679,142125,142126],{"class":681,"line":3689},[679,142127,142128],{"class":693}," GitHub\n",[679,142130,142131,142133,142135],{"class":681,"line":3699},[679,142132,134923],{"class":693},[679,142134,812],{"class":4508},[679,142136,4519],{"class":693},[679,142138,142139,142141,142143],{"class":681,"line":3704},[679,142140,134932],{"class":693},[679,142142,4509],{"class":4508},[679,142144,4519],{"class":693},[679,142146,142147,142149,142151],{"class":681,"line":3709},[679,142148,125356],{"class":693},[679,142150,4509],{"class":4508},[679,142152,4519],{"class":693},[679,142154,142155,142157,142159],{"class":681,"line":3719},[679,142156,5480],{"class":693},[679,142158,4509],{"class":4508},[679,142160,4519],{"class":693},[679,142162,142163,142165,142167],{"class":681,"line":3724},[679,142164,4577],{"class":693},[679,142166,4509],{"class":4508},[679,142168,4519],{"class":693},[4542,142170,142172],{"id":142171},"handling-login-logic","Handling Login Logic",[651,142174,142175],{},"Create a controller to handle the login process:",[669,142177,142179],{"className":4107,"code":142178,"language":4109,"meta":674,"style":674},"@Controller\npublic class LoginController {\n\n @GetMapping(\"/login\")\n public String login(HttpServletRequest request, Model model) {\n String error = request.getParameter(\"error\");\n String logout = request.getParameter(\"logout\");\n \n if (error != null) {\n model.addAttribute(\"error\", \"Invalid username or password\");\n }\n \n if (logout != null) {\n model.addAttribute(\"logout\", \"You have been logged out\");\n }\n \n CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());\n if (csrf != null) {\n model.addAttribute(\"csrf\", csrf);\n }\n \n return \"pages/login\";\n }\n}\n",[676,142180,142181,142187,142198,142202,142214,142233,142252,142270,142275,142288,142306,142310,142314,142327,142344,142348,142352,142371,142384,142398,142402,142406,142415,142419],{"__ignoreMap":674},[679,142182,142183,142185],{"class":681,"line":682},[679,142184,4116],{"class":693},[679,142186,9942],{"class":685},[679,142188,142189,142191,142193,142196],{"class":681,"line":790},[679,142190,6073],{"class":685},[679,142192,4512],{"class":685},[679,142194,142195],{"class":880}," LoginController",[679,142197,884],{"class":693},[679,142199,142200],{"class":681,"line":892},[679,142201,889],{"emptyLinePlaceholder":797},[679,142203,142204,142206,142208,142210,142212],{"class":681,"line":901},[679,142205,6872],{"class":693},[679,142207,20852],{"class":685},[679,142209,745],{"class":693},[679,142211,141126],{"class":689},[679,142213,1339],{"class":693},[679,142215,142216,142218,142220,142223,142225,142227,142229,142231],{"class":681,"line":909},[679,142217,6089],{"class":685},[679,142219,9289],{"class":693},[679,142221,142222],{"class":880},"login",[679,142224,10439],{"class":693},[679,142226,10054],{"class":2099},[679,142228,134358],{"class":693},[679,142230,10048],{"class":2099},[679,142232,4390],{"class":693},[679,142234,142235,142238,142240,142243,142246,142248,142250],{"class":681,"line":918},[679,142236,142237],{"class":693}," String error ",[679,142239,686],{"class":685},[679,142241,142242],{"class":693}," request.",[679,142244,142245],{"class":880},"getParameter",[679,142247,745],{"class":693},[679,142249,101853],{"class":689},[679,142251,1208],{"class":693},[679,142253,142254,142257,142259,142261,142263,142265,142268],{"class":681,"line":935},[679,142255,142256],{"class":693}," String logout ",[679,142258,686],{"class":685},[679,142260,142242],{"class":693},[679,142262,142245],{"class":880},[679,142264,745],{"class":693},[679,142266,142267],{"class":689},"\"logout\"",[679,142269,1208],{"class":693},[679,142271,142272],{"class":681,"line":944},[679,142273,142274],{"class":693}," \n",[679,142276,142277,142279,142282,142284,142286],{"class":681,"line":959},[679,142278,1249],{"class":685},[679,142280,142281],{"class":693}," (error ",[679,142283,1587],{"class":685},[679,142285,2307],{"class":931},[679,142287,4390],{"class":693},[679,142289,142290,142293,142295,142297,142299,142301,142304],{"class":681,"line":964},[679,142291,142292],{"class":693}," model.",[679,142294,10064],{"class":880},[679,142296,745],{"class":693},[679,142298,101853],{"class":689},[679,142300,2797],{"class":693},[679,142302,142303],{"class":689},"\"Invalid username or password\"",[679,142305,1208],{"class":693},[679,142307,142308],{"class":681,"line":977},[679,142309,1290],{"class":693},[679,142311,142312],{"class":681,"line":982},[679,142313,142274],{"class":693},[679,142315,142316,142318,142321,142323,142325],{"class":681,"line":988},[679,142317,1249],{"class":685},[679,142319,142320],{"class":693}," (logout ",[679,142322,1587],{"class":685},[679,142324,2307],{"class":931},[679,142326,4390],{"class":693},[679,142328,142329,142331,142333,142335,142337,142339,142342],{"class":681,"line":993},[679,142330,142292],{"class":693},[679,142332,10064],{"class":880},[679,142334,745],{"class":693},[679,142336,142267],{"class":689},[679,142338,2797],{"class":693},[679,142340,142341],{"class":689},"\"You have been logged out\"",[679,142343,1208],{"class":693},[679,142345,142346],{"class":681,"line":2129},[679,142347,1290],{"class":693},[679,142349,142350],{"class":681,"line":2140},[679,142351,142274],{"class":693},[679,142353,142354,142357,142359,142362,142364,142367,142369],{"class":681,"line":2145},[679,142355,142356],{"class":693}," CsrfToken csrf ",[679,142358,686],{"class":685},[679,142360,142361],{"class":693}," (CsrfToken) request.",[679,142363,73950],{"class":880},[679,142365,142366],{"class":693},"(CsrfToken.class.",[679,142368,10577],{"class":880},[679,142370,9431],{"class":693},[679,142372,142373,142375,142378,142380,142382],{"class":681,"line":2154},[679,142374,1249],{"class":685},[679,142376,142377],{"class":693}," (csrf ",[679,142379,1587],{"class":685},[679,142381,2307],{"class":931},[679,142383,4390],{"class":693},[679,142385,142386,142388,142390,142392,142395],{"class":681,"line":2159},[679,142387,142292],{"class":693},[679,142389,10064],{"class":880},[679,142391,745],{"class":693},[679,142393,142394],{"class":689},"\"csrf\"",[679,142396,142397],{"class":693},", csrf);\n",[679,142399,142400],{"class":681,"line":2164},[679,142401,1290],{"class":693},[679,142403,142404],{"class":681,"line":3134},[679,142405,142274],{"class":693},[679,142407,142408,142410,142413],{"class":681,"line":3139},[679,142409,9444],{"class":685},[679,142411,142412],{"class":689}," \"pages/login\"",[679,142414,1186],{"class":693},[679,142416,142417],{"class":681,"line":3144},[679,142418,985],{"class":693},[679,142420,142421],{"class":681,"line":3149},[679,142422,996],{"class":693},[4542,142424,142425],{"id":107718},"Setting Up OAuth2 Providers",[5909,142427,142429],{"id":142428},"google-oauth2-setup","Google OAuth2 Setup",[27665,142431,142432,142439,142442,142473],{},[5332,142433,142434,142435],{},"Go to the ",[812,142436,142438],{"href":107849,"rel":142437},[816],"Google Cloud Console",[5332,142440,142441],{},"Create a new project or select an existing one",[5332,142443,142444,142445],{},"Configure the OAuth consent screen:",[5316,142446,142447,142450,142453,142467,142470],{},[5332,142448,142449],{},"Navigate to \"APIs & Services\" > \"OAuth consent screen\"",[5332,142451,142452],{},"Choose \"External\" user type",[5332,142454,142455,142456],{},"Fill in the required information:\n",[5316,142457,142458,142461,142464],{},[5332,142459,142460],{},"App name",[5332,142462,142463],{},"User support email",[5332,142465,142466],{},"Developer contact information",[5332,142468,142469],{},"Add scopes: email, profile, openid",[5332,142471,142472],{},"Add test users if using external user type",[5332,142474,142475,142476],{},"Create OAuth2 credentials:",[5316,142477,142478,142481,142484,142487,142504],{},[5332,142479,142480],{},"Go to \"APIs & Services\" > \"Credentials\"",[5332,142482,142483],{},"Click \"Create Credentials\" > \"OAuth client ID\"",[5332,142485,142486],{},"Choose \"Web application\"",[5332,142488,142489,142490],{},"Add these URLs:\n",[5316,142491,142492,142498],{},[5332,142493,142494,142495],{},"Authorized JavaScript origins: ",[812,142496,11697],{"href":11697,"rel":142497},[816],[5332,142499,142500,142501],{},"Authorized redirect URIs: ",[812,142502,107866],{"href":107866,"rel":142503},[816],[5332,142505,142506],{},"Save your client ID and client secret",[5909,142508,142510],{"id":142509},"github-oauth-setup","GitHub OAuth Setup",[27665,142512,142513,142520,142523,142541,142544],{},[5332,142514,104720,142515],{},[812,142516,142519],{"href":142517,"rel":142518},"https://github.com/settings/developers",[816],"GitHub Developer Settings",[5332,142521,142522],{},"Click \"New OAuth App\"",[5332,142524,142525,142526],{},"Fill in the application details:\n",[5316,142527,142528,142531,142536],{},[5332,142529,142530],{},"Application name: Your App Name",[5332,142532,142533,142534],{},"Homepage URL: ",[676,142535,11697],{},[5332,142537,142538,142539],{},"Authorization callback URL: ",[676,142540,107827],{},[5332,142542,142543],{},"Register the application",[5332,142545,142546],{},"Note your client ID and generate a client secret",[5909,142548,142550],{"id":142549},"configuring-oauth2-in-your-application","Configuring OAuth2 in Your Application",[651,142552,142553,142554,2391],{},"Add the following configuration to your ",[676,142555,40799],{},[669,142557,142559],{"className":67476,"code":142558,"language":56308,"meta":674,"style":674},"spring:\n security:\n oauth2:\n client:\n registration:\n google:\n client-id: ${GOOGLE_CLIENT_ID}\n client-secret: ${GOOGLE_CLIENT_SECRET}\n scope:\n - email\n - profile\n github:\n client-id: ${GITHUB_CLIENT_ID}\n client-secret: ${GITHUB_CLIENT_SECRET}\n scope:\n - user:email\n - read:user\n",[676,142560,142561,142567,142574,142581,142588,142595,142602,142612,142622,142629,142637,142644,142651,142660,142669,142675,142682],{"__ignoreMap":674},[679,142562,142563,142565],{"class":681,"line":682},[679,142564,7055],{"class":4508},[679,142566,3327],{"class":693},[679,142568,142569,142572],{"class":681,"line":790},[679,142570,142571],{"class":4508}," security",[679,142573,3327],{"class":693},[679,142575,142576,142579],{"class":681,"line":892},[679,142577,142578],{"class":4508}," oauth2",[679,142580,3327],{"class":693},[679,142582,142583,142586],{"class":681,"line":901},[679,142584,142585],{"class":4508}," client",[679,142587,3327],{"class":693},[679,142589,142590,142593],{"class":681,"line":909},[679,142591,142592],{"class":4508}," registration",[679,142594,3327],{"class":693},[679,142596,142597,142600],{"class":681,"line":918},[679,142598,142599],{"class":4508}," google",[679,142601,3327],{"class":693},[679,142603,142604,142607,142609],{"class":681,"line":935},[679,142605,142606],{"class":4508}," client-id",[679,142608,4282],{"class":693},[679,142610,142611],{"class":689},"${GOOGLE_CLIENT_ID}\n",[679,142613,142614,142617,142619],{"class":681,"line":944},[679,142615,142616],{"class":4508}," client-secret",[679,142618,4282],{"class":693},[679,142620,142621],{"class":689},"${GOOGLE_CLIENT_SECRET}\n",[679,142623,142624,142627],{"class":681,"line":959},[679,142625,142626],{"class":4508}," scope",[679,142628,3327],{"class":693},[679,142630,142631,142634],{"class":681,"line":964},[679,142632,142633],{"class":693}," - ",[679,142635,142636],{"class":689},"email\n",[679,142638,142639,142641],{"class":681,"line":977},[679,142640,142633],{"class":693},[679,142642,142643],{"class":689},"profile\n",[679,142645,142646,142649],{"class":681,"line":982},[679,142647,142648],{"class":4508}," github",[679,142650,3327],{"class":693},[679,142652,142653,142655,142657],{"class":681,"line":988},[679,142654,142606],{"class":4508},[679,142656,4282],{"class":693},[679,142658,142659],{"class":689},"${GITHUB_CLIENT_ID}\n",[679,142661,142662,142664,142666],{"class":681,"line":993},[679,142663,142616],{"class":4508},[679,142665,4282],{"class":693},[679,142667,142668],{"class":689},"${GITHUB_CLIENT_SECRET}\n",[679,142670,142671,142673],{"class":681,"line":2129},[679,142672,142626],{"class":4508},[679,142674,3327],{"class":693},[679,142676,142677,142679],{"class":681,"line":2140},[679,142678,142633],{"class":693},[679,142680,142681],{"class":689},"user:email\n",[679,142683,142684,142686],{"class":681,"line":2145},[679,142685,142633],{"class":693},[679,142687,142688],{"class":689},"read:user\n",[651,142690,142691],{},"For local development, you can set these environment variables in your IDE's run configuration or export them in your terminal:",[669,142693,142695],{"className":5851,"code":142694,"language":5853,"meta":674,"style":674},"export GOOGLE_CLIENT_ID=your_google_client_id\nexport GOOGLE_CLIENT_SECRET=your_google_client_secret\nexport GITHUB_CLIENT_ID=your_github_client_id\nexport GITHUB_CLIENT_SECRET=your_github_client_secret\n",[676,142696,142697,142709,142721,142733],{"__ignoreMap":674},[679,142698,142699,142701,142704,142706],{"class":681,"line":682},[679,142700,29245],{"class":685},[679,142702,142703],{"class":693}," GOOGLE_CLIENT_ID",[679,142705,686],{"class":685},[679,142707,142708],{"class":693},"your_google_client_id\n",[679,142710,142711,142713,142716,142718],{"class":681,"line":790},[679,142712,29245],{"class":685},[679,142714,142715],{"class":693}," GOOGLE_CLIENT_SECRET",[679,142717,686],{"class":685},[679,142719,142720],{"class":693},"your_google_client_secret\n",[679,142722,142723,142725,142728,142730],{"class":681,"line":892},[679,142724,29245],{"class":685},[679,142726,142727],{"class":693}," GITHUB_CLIENT_ID",[679,142729,686],{"class":685},[679,142731,142732],{"class":693},"your_github_client_id\n",[679,142734,142735,142737,142740,142742],{"class":681,"line":901},[679,142736,29245],{"class":685},[679,142738,142739],{"class":693}," GITHUB_CLIENT_SECRET",[679,142741,686],{"class":685},[679,142743,142744],{"class":693},"your_github_client_secret\n",[5909,142746,142748],{"id":142747},"security-best-practices","Security Best Practices",[651,142750,142751],{},"When working with OAuth2 credentials:",[27665,142753,142754,142757,142760,142763,142766,142769],{},[5332,142755,142756],{},"Never commit credentials to version control",[5332,142758,142759],{},"Use environment variables or a secure configuration management system",[5332,142761,142762],{},"Restrict OAuth scopes to only what's necessary",[5332,142764,142765],{},"Always use HTTPS in production",[5332,142767,142768],{},"Regularly rotate client secrets",[5332,142770,142771],{},"Monitor OAuth usage for suspicious activity",[5909,142773,142775],{"id":142774},"troubleshooting-oauth2-integration","Troubleshooting OAuth2 Integration",[651,142777,142778],{},"Common issues and solutions:",[27665,142780,142781,142798],{},[5332,142782,142783,142784],{},"Redirect URI Mismatch:",[5316,142785,142786,142789,142792,142795],{},[5332,142787,142788],{},"Double-check the exact URIs in your OAuth provider settings",[5332,142790,142791],{},"Ensure no trailing slashes",[5332,142793,142794],{},"Verify the correct protocol (http/https)",[5332,142796,142797],{},"Confirm the port number matches",[5332,142799,142800,142801],{},"Authentication Errors:",[5316,142802,142803,142806,142809,142812],{},[5332,142804,142805],{},"Clear browser cookies and cache",[5332,142807,142808],{},"Verify environment variables are set correctly",[5332,142810,142811],{},"Check application logs for detailed error messages",[5332,142813,142814],{},"Confirm OAuth provider console settings",[4542,142816,9042],{"id":9041},[651,142818,142819],{},"By combining Spring Security with JTE, you can create a secure, maintainable authentication system with a clean separation of concerns. The template engine makes it easy to create professional-looking login forms while Spring Security handles all the security concerns behind the scenes.",[651,142821,142822],{},"Some key takeaways:",[5316,142824,142825,142828,142831],{},[5332,142826,142827],{},"JTE provides a clean way to structure your templates and maintain a clear separation between logic and presentation",[5332,142829,142830],{},"Spring Security's configuration can be customized to support both traditional and OAuth2 authentication",[5332,142832,142833],{},"CSRF protection and error handling can be cleanly integrated into your templates",[651,142835,142836],{},"To extend this further, consider adding:",[5316,142838,142839,142842,142845,142848],{},[5332,142840,142841],{},"Remember-me functionality",[5332,142843,142844],{},"Custom success/failure handlers",[5332,142846,142847],{},"Additional OAuth2 providers",[5332,142849,142850],{},"User registration flow",[651,142852,142853],{},"The complete source code for this tutorial is available on GitHub, where you can find additional configuration options and examples.",[786,142855,142856],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":142858},[142859,142860,142861,142862,142863,142864,142871],{"id":94280,"depth":790,"text":94281},{"id":106180,"depth":790,"text":106181},{"id":141368,"depth":790,"text":141369},{"id":141507,"depth":790,"text":141508},{"id":142171,"depth":790,"text":142172},{"id":107718,"depth":790,"text":142425,"children":142865},[142866,142867,142868,142869,142870],{"id":142428,"depth":892,"text":142429},{"id":142509,"depth":892,"text":142510},{"id":142549,"depth":892,"text":142550},{"id":142747,"depth":892,"text":142748},{"id":142774,"depth":892,"text":142775},{"id":9041,"depth":790,"text":9042},"Learn how to create a secure login form using Spring Security and JTE (Java Template Engine) with support for both traditional authentication and OAuth2 providers.",{"slug":142874,"date":142875,"published":797,"author":798,"tags":142876,"cover":142877,"video":142878,"github":142879,"keywords":142880},"spring-boot-oauth-demo","2024-10-24T17:00:00.000Z",[7077,23988],"./spring-security-jte-login.png","https://www.youtube.com/embed/f1h4GkhxMp8","https://github.com/danvega/spring-security-jte-demo","spring security, jte, java template engine, oauth2, login form, authentication, spring boot, java",{"title":45,"description":142872},"blog/2024/10/24/spring-boot-oauth-demo","95MAf28LA2R4iBs_Y2MXkW62dR0945w9m7tqJScASDk",{"id":142885,"title":42,"body":142886,"description":143534,"extension":793,"meta":143535,"navigation":797,"path":43,"seo":143541,"stem":143542,"__hash__":143543},"content/blog/2024/10/29/spring-boot-environment-variables.md",{"type":648,"value":142887,"toc":143520},[142888,142891,142895,142898,142901,142948,142952,142955,142959,142965,143075,143079,143082,143107,143111,143114,143119,143136,143141,143162,143167,143185,143189,143192,143296,143300,143330,143371,143387,143391,143394,143405,143408,143429,143433,143436,143486,143488,143491,143494,143511,143514,143517],[651,142889,142890],{},"Managing sensitive information and configuration across different environments is a crucial aspect of modern application development. In this guide, we'll explore how to effectively use environment variables in Spring Boot applications to handle configuration and secure sensitive data.",[4542,142892,142894],{"id":142893},"why-environment-variables-matter","Why Environment Variables Matter",[651,142896,142897],{},"When building Spring Boot applications, especially those that integrate with external services or databases, you'll often need to manage configuration values like API keys, database credentials, and service URLs. Hardcoding these values directly into your application code or even in configuration files can lead to security risks and deployment challenges.",[651,142899,142900],{},"Consider this problematic example:",[669,142902,142904],{"className":4107,"code":142903,"language":4109,"meta":674,"style":674},"@Service\npublic class ApiClient {\n private static final String API_KEY = \"sk-1234567890abcdef\"; // Don't do this!\n}\n",[676,142905,142906,142912,142923,142944],{"__ignoreMap":674},[679,142907,142908,142910],{"class":681,"line":682},[679,142909,4116],{"class":693},[679,142911,9486],{"class":685},[679,142913,142914,142916,142918,142921],{"class":681,"line":790},[679,142915,6073],{"class":685},[679,142917,4512],{"class":685},[679,142919,142920],{"class":880}," ApiClient",[679,142922,884],{"class":693},[679,142924,142925,142927,142929,142931,142934,142936,142939,142941],{"class":681,"line":892},[679,142926,9232],{"class":685},[679,142928,6092],{"class":685},[679,142930,12768],{"class":685},[679,142932,142933],{"class":693}," String API_KEY ",[679,142935,686],{"class":685},[679,142937,142938],{"class":689}," \"sk-1234567890abcdef\"",[679,142940,75640],{"class":693},[679,142942,142943],{"class":1400},"// Don't do this!\n",[679,142945,142946],{"class":681,"line":901},[679,142947,996],{"class":693},[4542,142949,142951],{"id":142950},"understanding-spring-boots-environment-variable-support","Understanding Spring Boot's Environment Variable Support",[651,142953,142954],{},"Spring Boot provides robust support for environment variables through various property sources. Let's explore how to properly handle configuration using environment variables.",[5909,142956,142958],{"id":142957},"using-the-value-annotation","Using the @Value Annotation",[651,142960,142961,142962,142964],{},"The most straightforward way to access environment variables is using the ",[676,142963,40349],{}," annotation:",[669,142966,142968],{"className":4107,"code":142967,"language":4109,"meta":674,"style":674},"@RestController\npublic class ApiController {\n @Value(\"${api.key}\")\n private String apiKey;\n \n @GetMapping(\"/status\")\n public String getStatus() {\n return \"API Configured with key: \" + apiKey.substring(0, 5) + \"...\";\n }\n}\n",[676,142969,142970,142976,142987,143000,143007,143011,143024,143035,143067,143071],{"__ignoreMap":674},[679,142971,142972,142974],{"class":681,"line":682},[679,142973,4116],{"class":693},[679,142975,9212],{"class":685},[679,142977,142978,142980,142982,142985],{"class":681,"line":790},[679,142979,6073],{"class":685},[679,142981,4512],{"class":685},[679,142983,142984],{"class":880}," ApiController",[679,142986,884],{"class":693},[679,142988,142989,142991,142993,142995,142998],{"class":681,"line":892},[679,142990,6872],{"class":693},[679,142992,31784],{"class":685},[679,142994,745],{"class":693},[679,142996,142997],{"class":689},"\"${api.key}\"",[679,142999,1339],{"class":693},[679,143001,143002,143004],{"class":681,"line":901},[679,143003,9232],{"class":685},[679,143005,143006],{"class":693}," String apiKey;\n",[679,143008,143009],{"class":681,"line":909},[679,143010,119642],{"class":693},[679,143012,143013,143015,143017,143019,143022],{"class":681,"line":918},[679,143014,6872],{"class":693},[679,143016,20852],{"class":685},[679,143018,745],{"class":693},[679,143020,143021],{"class":689},"\"/status\"",[679,143023,1339],{"class":693},[679,143025,143026,143028,143030,143033],{"class":681,"line":935},[679,143027,6089],{"class":685},[679,143029,9289],{"class":693},[679,143031,143032],{"class":880},"getStatus",[679,143034,2667],{"class":693},[679,143036,143037,143039,143042,143044,143047,143050,143052,143054,143056,143058,143060,143062,143065],{"class":681,"line":944},[679,143038,9444],{"class":685},[679,143040,143041],{"class":689}," \"API Configured with key: \"",[679,143043,3059],{"class":685},[679,143045,143046],{"class":693}," apiKey.",[679,143048,143049],{"class":880},"substring",[679,143051,745],{"class":693},[679,143053,1060],{"class":931},[679,143055,2797],{"class":693},[679,143057,66775],{"class":931},[679,143059,2378],{"class":693},[679,143061,3065],{"class":685},[679,143063,143064],{"class":689}," \"...\"",[679,143066,1186],{"class":693},[679,143068,143069],{"class":681,"line":959},[679,143070,985],{"class":693},[679,143072,143073],{"class":681,"line":964},[679,143074,996],{"class":693},[5909,143076,143078],{"id":143077},"setting-default-values","Setting Default Values",[651,143080,143081],{},"You can provide default values for cases where the environment variable might not be set:",[669,143083,143085],{"className":4107,"code":143084,"language":4109,"meta":674,"style":674},"@Value(\"${database.url:jdbc:postgresql://localhost:5432/mydb}\")\nprivate String databaseUrl;\n",[676,143086,143087,143100],{"__ignoreMap":674},[679,143088,143089,143091,143093,143095,143098],{"class":681,"line":682},[679,143090,4116],{"class":693},[679,143092,31784],{"class":685},[679,143094,745],{"class":693},[679,143096,143097],{"class":689},"\"${database.url:jdbc:postgresql://localhost:5432/mydb}\"",[679,143099,1339],{"class":693},[679,143101,143102,143104],{"class":681,"line":790},[679,143103,21301],{"class":685},[679,143105,143106],{"class":693}," String databaseUrl;\n",[4542,143108,143110],{"id":143109},"working-with-environment-variables","Working with Environment Variables",[651,143112,143113],{},"There are several ways to provide environment variables to your Spring Boot application:",[27665,143115,143116],{},[5332,143117,143118],{},"System Environment Variables:",[669,143120,143122],{"className":5851,"code":143121,"language":5853,"meta":674,"style":674},"export API_KEY=your-secret-key\n",[676,143123,143124],{"__ignoreMap":674},[679,143125,143126,143128,143131,143133],{"class":681,"line":682},[679,143127,29245],{"class":685},[679,143129,143130],{"class":693}," API_KEY",[679,143132,686],{"class":685},[679,143134,143135],{"class":693},"your-secret-key\n",[27665,143137,143138],{"start":790},[5332,143139,143140],{},"Application Properties:",[669,143142,143144],{"className":76589,"code":143143,"language":35538,"meta":674,"style":674},"api.key=${API_KEY}\ndatabase.url=${DB_URL:jdbc:postgresql://localhost:5432/mydb}\n",[676,143145,143146,143154],{"__ignoreMap":674},[679,143147,143148,143151],{"class":681,"line":682},[679,143149,143150],{"class":685},"api.key",[679,143152,143153],{"class":693},"=${API_KEY}\n",[679,143155,143156,143159],{"class":681,"line":790},[679,143157,143158],{"class":685},"database.url",[679,143160,143161],{"class":693},"=${DB_URL:jdbc:postgresql://localhost:5432/mydb}\n",[27665,143163,143164],{"start":892},[5332,143165,143166],{},"Command Line Arguments:",[669,143168,143170],{"className":5851,"code":143169,"language":5853,"meta":674,"style":674},"java -jar myapp.jar --api.key=your-secret-key\n",[676,143171,143172],{"__ignoreMap":674},[679,143173,143174,143176,143179,143182],{"class":681,"line":682},[679,143175,4109],{"class":880},[679,143177,143178],{"class":931}," -jar",[679,143180,143181],{"class":689}," myapp.jar",[679,143183,143184],{"class":931}," --api.key=your-secret-key\n",[5909,143186,143188],{"id":143187},"using-the-environment-interface","Using the Environment Interface",[651,143190,143191],{},"For more dynamic access to environment variables, you can use Spring's Environment interface:",[669,143193,143195],{"className":4107,"code":143194,"language":4109,"meta":674,"style":674},"@Service\npublic class ConfigService {\n private final Environment environment;\n \n public ConfigService(Environment environment) {\n this.environment = environment;\n }\n \n public String getConfigValue(String key) {\n return environment.getProperty(key);\n }\n}\n",[676,143196,143197,143203,143214,143223,143227,143241,143253,143257,143261,143276,143288,143292],{"__ignoreMap":674},[679,143198,143199,143201],{"class":681,"line":682},[679,143200,4116],{"class":693},[679,143202,9486],{"class":685},[679,143204,143205,143207,143209,143212],{"class":681,"line":790},[679,143206,6073],{"class":685},[679,143208,4512],{"class":685},[679,143210,143211],{"class":880}," ConfigService",[679,143213,884],{"class":693},[679,143215,143216,143218,143220],{"class":681,"line":892},[679,143217,9232],{"class":685},[679,143219,12768],{"class":685},[679,143221,143222],{"class":693}," Environment environment;\n",[679,143224,143225],{"class":681,"line":901},[679,143226,119642],{"class":693},[679,143228,143229,143231,143233,143236,143239],{"class":681,"line":909},[679,143230,6089],{"class":685},[679,143232,143211],{"class":880},[679,143234,143235],{"class":693},"(Environment ",[679,143237,143238],{"class":2099},"environment",[679,143240,4390],{"class":693},[679,143242,143243,143245,143248,143250],{"class":681,"line":918},[679,143244,7862],{"class":931},[679,143246,143247],{"class":693},".environment ",[679,143249,686],{"class":685},[679,143251,143252],{"class":693}," environment;\n",[679,143254,143255],{"class":681,"line":935},[679,143256,985],{"class":693},[679,143258,143259],{"class":681,"line":944},[679,143260,119642],{"class":693},[679,143262,143263,143265,143267,143270,143272,143274],{"class":681,"line":959},[679,143264,6089],{"class":685},[679,143266,9289],{"class":693},[679,143268,143269],{"class":880},"getConfigValue",[679,143271,11400],{"class":693},[679,143273,56128],{"class":2099},[679,143275,4390],{"class":693},[679,143277,143278,143280,143283,143285],{"class":681,"line":964},[679,143279,9444],{"class":685},[679,143281,143282],{"class":693}," environment.",[679,143284,108728],{"class":880},[679,143286,143287],{"class":693},"(key);\n",[679,143289,143290],{"class":681,"line":977},[679,143291,985],{"class":693},[679,143293,143294],{"class":681,"line":982},[679,143295,996],{"class":693},[4542,143297,143299],{"id":143298},"best-practices-and-security-considerations","Best Practices and Security Considerations",[27665,143301,143302,143316,143327],{},[5332,143303,143304,143305],{},"Never commit sensitive values to version control:",[5316,143306,143307,143313],{},[5332,143308,135899,143309,143312],{},[676,143310,143311],{},".gitignore"," to exclude local configuration files",[5332,143314,143315],{},"Keep production credentials separate from development",[5332,143317,143318,143319],{},"Use meaningful variable names:",[5316,143320,143321,143324],{},[5332,143322,143323],{},"Follow a consistent naming convention",[5332,143325,143326],{},"Use prefixes to group related variables",[5332,143328,143329],{},"Validate environment variables on startup:",[669,143331,143333],{"className":4107,"code":143332,"language":4109,"meta":674,"style":674},"@PostConstruct\npublic void validateConfig() {\n Assert.notNull(apiKey, \"API key must be configured!\");\n}\n",[676,143334,143335,143341,143352,143367],{"__ignoreMap":674},[679,143336,143337,143339],{"class":681,"line":682},[679,143338,4116],{"class":693},[679,143340,87391],{"class":685},[679,143342,143343,143345,143347,143350],{"class":681,"line":790},[679,143344,6073],{"class":685},[679,143346,6095],{"class":685},[679,143348,143349],{"class":880}," validateConfig",[679,143351,2667],{"class":693},[679,143353,143354,143356,143359,143362,143365],{"class":681,"line":892},[679,143355,79289],{"class":693},[679,143357,143358],{"class":880},"notNull",[679,143360,143361],{"class":693},"(apiKey, ",[679,143363,143364],{"class":689},"\"API key must be configured!\"",[679,143366,1208],{"class":693},[679,143368,143369],{"class":681,"line":901},[679,143370,996],{"class":693},[27665,143372,143373],{"start":901},[5332,143374,143375,143376],{},"Consider using encrypted properties for sensitive data:\n",[5316,143377,143378,143381,143384],{},[5332,143379,143380],{},"Spring Cloud Config Server",[5332,143382,143383],{},"Vault",[5332,143385,143386],{},"AWS Secrets Manager",[4542,143388,143390],{"id":143389},"development-workflow-integration","Development Workflow Integration",[651,143392,143393],{},"When developing locally, you can set environment variables in your IDE. For IntelliJ IDEA:",[27665,143395,143396,143399,143402],{},[5332,143397,143398],{},"Edit Run Configuration",[5332,143400,143401],{},"Add Environment Variables",[5332,143403,143404],{},"Apply and run",[651,143406,143407],{},"Example configuration:",[669,143409,143411],{"className":76589,"code":143410,"language":35538,"meta":674,"style":674},"API_KEY=dev-key-123\nDB_URL=jdbc:postgresql://localhost:5432/devdb\n",[676,143412,143413,143421],{"__ignoreMap":674},[679,143414,143415,143418],{"class":681,"line":682},[679,143416,143417],{"class":685},"API_KEY",[679,143419,143420],{"class":693},"=dev-key-123\n",[679,143422,143423,143426],{"class":681,"line":790},[679,143424,143425],{"class":685},"DB_URL",[679,143427,143428],{"class":693},"=jdbc:postgresql://localhost:5432/devdb\n",[4542,143430,143432],{"id":143431},"working-with-multiple-environments","Working with Multiple Environments",[651,143434,143435],{},"Spring Boot makes it easy to manage different configurations for various environments:",[669,143437,143439],{"className":76589,"code":143438,"language":35538,"meta":674,"style":674},"# application.properties\nspring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}\n\n# application-dev.properties\napi.url=http://localhost:8080\n\n# application-prod.properties\napi.url=${API_URL}\n",[676,143440,143441,143446,143453,143457,143462,143470,143474,143479],{"__ignoreMap":674},[679,143442,143443],{"class":681,"line":682},[679,143444,143445],{"class":1400},"# application.properties\n",[679,143447,143448,143450],{"class":681,"line":790},[679,143449,80474],{"class":685},[679,143451,143452],{"class":693},"=${SPRING_PROFILES_ACTIVE:dev}\n",[679,143454,143455],{"class":681,"line":892},[679,143456,889],{"emptyLinePlaceholder":797},[679,143458,143459],{"class":681,"line":901},[679,143460,143461],{"class":1400},"# application-dev.properties\n",[679,143463,143464,143467],{"class":681,"line":909},[679,143465,143466],{"class":685},"api.url",[679,143468,143469],{"class":693},"=http://localhost:8080\n",[679,143471,143472],{"class":681,"line":918},[679,143473,889],{"emptyLinePlaceholder":797},[679,143475,143476],{"class":681,"line":935},[679,143477,143478],{"class":1400},"# application-prod.properties\n",[679,143480,143481,143483],{"class":681,"line":944},[679,143482,143466],{"class":685},[679,143484,143485],{"class":693},"=${API_URL}\n",[4542,143487,9042],{"id":9041},[651,143489,143490],{},"Environment variables are a crucial tool for managing configuration and securing sensitive data in Spring Boot applications. By following the practices outlined in this guide, you can build more secure and maintainable applications that can easily be deployed across different environments.",[651,143492,143493],{},"Remember:",[5316,143495,143496,143499,143502,143505,143508],{},[5332,143497,143498],{},"Never hardcode sensitive information",[5332,143500,143501],{},"Use environment variables for configuration that changes between environments",[5332,143503,143504],{},"Provide clear documentation for required environment variables",[5332,143506,143507],{},"Validate configuration at startup",[5332,143509,143510],{},"Use meaningful variable names and follow consistent patterns",[651,143512,143513],{},"Next time you're tempted to hardcode that API key, remember that environment variables are your friend in keeping your application secure and configurable.",[651,143515,143516],{},"Happy coding! 🚀",[786,143518,143519],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":143521},[143522,143523,143527,143530,143531,143532,143533],{"id":142893,"depth":790,"text":142894},{"id":142950,"depth":790,"text":142951,"children":143524},[143525,143526],{"id":142957,"depth":892,"text":142958},{"id":143077,"depth":892,"text":143078},{"id":143109,"depth":790,"text":143110,"children":143528},[143529],{"id":143187,"depth":892,"text":143188},{"id":143298,"depth":790,"text":143299},{"id":143389,"depth":790,"text":143390},{"id":143431,"depth":790,"text":143432},{"id":9041,"depth":790,"text":9042},"Learn how to effectively manage configuration and secure sensitive data using environment variables in Spring Boot applications. This comprehensive guide covers best practices, security considerations, and practical examples.",{"slug":143536,"date":143537,"published":797,"author":798,"tags":143538,"video":143539,"keywords":143540},"spring-boot-environment-variables","2024-10-29T17:00:00.000Z",[7077],"https://www.youtube.com/embed/rQV76dufxz4","spring boot, spring framework, environment variables, configuration, security, api keys, spring framework, java, development, best practices",{"title":42,"description":143534},"blog/2024/10/29/spring-boot-environment-variables","HDkOgyKYlGo26Wd8NB_GZbCeNQA_zNZGz41wFUgThUA",{"id":143545,"title":39,"body":143546,"description":144985,"extension":793,"meta":144986,"navigation":797,"path":40,"seo":144994,"stem":144995,"__hash__":144996},"content/blog/2024/10/31/spring-boot-rest-client-logging.md",{"type":648,"value":143547,"toc":144974},[143548,143551,143555,143558,143561,143565,143568,143572,143575,143971,143975,143978,144353,144357,144360,144577,144579,144582,144599,144626,144634,144708,144712,144715,144958,144960,144963,144966,144969,144971],[651,143549,143550],{},"If you're working with Spring Boot's new Rest Client, you might find yourself needing to log HTTP requests and responses for debugging, monitoring, or audit purposes. Today, we'll explore how to implement clean and efficient logging for your Rest Client calls.",[4542,143552,143554],{"id":143553},"why-log-rest-client-calls","Why Log Rest Client Calls?",[651,143556,143557],{},"When building applications that communicate with external services, having visibility into your HTTP interactions is crucial. Whether you're debugging integration issues, monitoring API performance, or maintaining an audit trail, proper request and response logging is essential.",[651,143559,143560],{},"Spring Boot's Rest Client, introduced in version 3.2, provides a modern and fluent API for making HTTP requests. While it's great at handling the communication, out-of-the-box logging can be verbose and not always suited to your needs.",[4542,143562,143564],{"id":143563},"two-approaches-to-logging","Two Approaches to Logging",[651,143566,143567],{},"Let's explore two different ways to implement logging with Spring Boot's Rest Client.",[5909,143569,143571],{"id":143570},"_1-inline-logging-with-restclientbuilder","1. Inline Logging with RestClient.Builder",[651,143573,143574],{},"The simplest approach is to add logging directly when building your RestClient:",[669,143576,143578],{"className":4107,"code":143577,"language":4109,"meta":674,"style":674},"@Service\npublic class PostClient {\n private static final Logger log = LoggerFactory.getLogger(PostClient.class);\n private final RestClient restClient;\n\n public PostClient(RestClient.Builder builder) {\n this.restClient = builder\n .baseUrl(\"https://api.example.com\")\n .requestInterceptor((request, body, execution) -> {\n logRequest(request, body);\n var response = execution.execute(request, body);\n logResponse(request, response);\n return response;\n })\n .build();\n }\n\n private void logRequest(HttpRequest request, byte[] body) {\n log.info(\"Request: {} {}\", request.getMethod(), request.getURI());\n logHeaders(request.getHeaders());\n if (body != null && body.length > 0) {\n log.info(\"Request body: {}\", new String(body, StandardCharsets.UTF_8));\n }\n }\n\n private void logResponse(HttpRequest request, ClientHttpResponse response) throws IOException {\n log.info(\"Response status: {}\", response.getStatusCode());\n logHeaders(response.getHeaders());\n byte[] responseBody = response.getBody().readAllBytes();\n if (responseBody.length > 0) {\n log.info(\"Response body: {}\", new String(responseBody, StandardCharsets.UTF_8));\n }\n }\n}\n",[676,143579,143580,143586,143597,143616,143624,143628,143640,143650,143663,143677,143685,143700,143708,143715,143719,143727,143731,143735,143759,143783,143796,143819,143839,143843,143847,143851,143876,143894,143904,143926,143939,143959,143963,143967],{"__ignoreMap":674},[679,143581,143582,143584],{"class":681,"line":682},[679,143583,4116],{"class":693},[679,143585,9486],{"class":685},[679,143587,143588,143590,143592,143595],{"class":681,"line":790},[679,143589,6073],{"class":685},[679,143591,4512],{"class":685},[679,143593,143594],{"class":880}," PostClient",[679,143596,884],{"class":693},[679,143598,143599,143601,143603,143605,143607,143609,143611,143613],{"class":681,"line":892},[679,143600,9232],{"class":685},[679,143602,6092],{"class":685},[679,143604,12768],{"class":685},[679,143606,111111],{"class":693},[679,143608,686],{"class":685},[679,143610,9240],{"class":693},[679,143612,9243],{"class":880},[679,143614,143615],{"class":693},"(PostClient.class);\n",[679,143617,143618,143620,143622],{"class":681,"line":901},[679,143619,9232],{"class":685},[679,143621,12768],{"class":685},[679,143623,113669],{"class":693},[679,143625,143626],{"class":681,"line":909},[679,143627,889],{"emptyLinePlaceholder":797},[679,143629,143630,143632,143634,143636,143638],{"class":681,"line":918},[679,143631,6089],{"class":685},[679,143633,143594],{"class":880},[679,143635,117853],{"class":693},[679,143637,90934],{"class":2099},[679,143639,4390],{"class":693},[679,143641,143642,143644,143646,143648],{"class":681,"line":935},[679,143643,7862],{"class":931},[679,143645,119016],{"class":693},[679,143647,686],{"class":685},[679,143649,119021],{"class":693},[679,143651,143652,143654,143656,143658,143661],{"class":681,"line":944},[679,143653,40148],{"class":693},[679,143655,100528],{"class":880},[679,143657,745],{"class":693},[679,143659,143660],{"class":689},"\"https://api.example.com\"",[679,143662,1339],{"class":693},[679,143664,143665,143667,143670,143673,143675],{"class":681,"line":959},[679,143666,40148],{"class":693},[679,143668,143669],{"class":880},"requestInterceptor",[679,143671,143672],{"class":693},"((request, body, execution) ",[679,143674,16955],{"class":685},[679,143676,884],{"class":693},[679,143678,143679,143682],{"class":681,"line":964},[679,143680,143681],{"class":880}," logRequest",[679,143683,143684],{"class":693},"(request, body);\n",[679,143686,143687,143689,143691,143693,143695,143698],{"class":681,"line":977},[679,143688,112622],{"class":685},[679,143690,124000],{"class":693},[679,143692,686],{"class":685},[679,143694,9474],{"class":693},[679,143696,143697],{"class":880},"execute",[679,143699,143684],{"class":693},[679,143701,143702,143705],{"class":681,"line":982},[679,143703,143704],{"class":880}," logResponse",[679,143706,143707],{"class":693},"(request, response);\n",[679,143709,143710,143713],{"class":681,"line":988},[679,143711,143712],{"class":685}," return",[679,143714,137817],{"class":693},[679,143716,143717],{"class":681,"line":993},[679,143718,106357],{"class":693},[679,143720,143721,143723,143725],{"class":681,"line":2129},[679,143722,40148],{"class":693},[679,143724,23612],{"class":880},[679,143726,9317],{"class":693},[679,143728,143729],{"class":681,"line":2140},[679,143730,985],{"class":693},[679,143732,143733],{"class":681,"line":2145},[679,143734,889],{"emptyLinePlaceholder":797},[679,143736,143737,143739,143741,143744,143747,143749,143751,143753,143755,143757],{"class":681,"line":2154},[679,143738,9232],{"class":685},[679,143740,6095],{"class":685},[679,143742,143743],{"class":880}," logRequest",[679,143745,143746],{"class":693},"(HttpRequest ",[679,143748,10054],{"class":2099},[679,143750,2797],{"class":693},[679,143752,1057],{"class":685},[679,143754,16901],{"class":693},[679,143756,3006],{"class":2099},[679,143758,4390],{"class":693},[679,143760,143761,143763,143765,143767,143770,143773,143775,143778,143781],{"class":681,"line":2159},[679,143762,119944],{"class":693},[679,143764,9415],{"class":880},[679,143766,745],{"class":693},[679,143768,143769],{"class":689},"\"Request: {} {}\"",[679,143771,143772],{"class":693},", request.",[679,143774,10572],{"class":880},[679,143776,143777],{"class":693},"(), request.",[679,143779,143780],{"class":880},"getURI",[679,143782,9431],{"class":693},[679,143784,143785,143788,143791,143794],{"class":681,"line":2164},[679,143786,143787],{"class":880}," logHeaders",[679,143789,143790],{"class":693},"(request.",[679,143792,143793],{"class":880},"getHeaders",[679,143795,9431],{"class":693},[679,143797,143798,143800,143803,143805,143807,143810,143813,143815,143817],{"class":681,"line":3134},[679,143799,1249],{"class":685},[679,143801,143802],{"class":693}," (body ",[679,143804,1587],{"class":685},[679,143806,2307],{"class":931},[679,143808,143809],{"class":685}," &&",[679,143811,143812],{"class":693}," body.length ",[679,143814,5860],{"class":685},[679,143816,14987],{"class":931},[679,143818,4390],{"class":693},[679,143820,143821,143823,143825,143827,143830,143832,143834,143836],{"class":681,"line":3139},[679,143822,104907],{"class":693},[679,143824,9415],{"class":880},[679,143826,745],{"class":693},[679,143828,143829],{"class":689},"\"Request body: {}\"",[679,143831,2797],{"class":693},[679,143833,8930],{"class":685},[679,143835,130055],{"class":880},[679,143837,143838],{"class":693},"(body, StandardCharsets.UTF_8));\n",[679,143840,143841],{"class":681,"line":3144},[679,143842,1290],{"class":693},[679,143844,143845],{"class":681,"line":3149},[679,143846,985],{"class":693},[679,143848,143849],{"class":681,"line":3169},[679,143850,889],{"emptyLinePlaceholder":797},[679,143852,143853,143855,143857,143860,143862,143864,143867,143869,143871,143873],{"class":681,"line":3185},[679,143854,9232],{"class":685},[679,143856,6095],{"class":685},[679,143858,143859],{"class":880}," logResponse",[679,143861,143746],{"class":693},[679,143863,10054],{"class":2099},[679,143865,143866],{"class":693},", ClientHttpResponse ",[679,143868,10447],{"class":2099},[679,143870,2378],{"class":693},[679,143872,9580],{"class":685},[679,143874,143875],{"class":693}," IOException {\n",[679,143877,143878,143880,143882,143884,143887,143890,143892],{"class":681,"line":3194},[679,143879,119944],{"class":693},[679,143881,9415],{"class":880},[679,143883,745],{"class":693},[679,143885,143886],{"class":689},"\"Response status: {}\"",[679,143888,143889],{"class":693},", response.",[679,143891,118004],{"class":880},[679,143893,9431],{"class":693},[679,143895,143896,143898,143900,143902],{"class":681,"line":3199},[679,143897,143787],{"class":880},[679,143899,124023],{"class":693},[679,143901,143793],{"class":880},[679,143903,9431],{"class":693},[679,143905,143906,143909,143912,143914,143916,143919,143921,143924],{"class":681,"line":3212},[679,143907,143908],{"class":685}," byte",[679,143910,143911],{"class":693},"[] responseBody ",[679,143913,686],{"class":685},[679,143915,46743],{"class":693},[679,143917,143918],{"class":880},"getBody",[679,143920,10541],{"class":693},[679,143922,143923],{"class":880},"readAllBytes",[679,143925,9317],{"class":693},[679,143927,143928,143930,143933,143935,143937],{"class":681,"line":3217},[679,143929,1249],{"class":685},[679,143931,143932],{"class":693}," (responseBody.length ",[679,143934,5860],{"class":685},[679,143936,14987],{"class":931},[679,143938,4390],{"class":693},[679,143940,143941,143943,143945,143947,143950,143952,143954,143956],{"class":681,"line":3222},[679,143942,104907],{"class":693},[679,143944,9415],{"class":880},[679,143946,745],{"class":693},[679,143948,143949],{"class":689},"\"Response body: {}\"",[679,143951,2797],{"class":693},[679,143953,8930],{"class":685},[679,143955,130055],{"class":880},[679,143957,143958],{"class":693},"(responseBody, StandardCharsets.UTF_8));\n",[679,143960,143961],{"class":681,"line":3227},[679,143962,1290],{"class":693},[679,143964,143965],{"class":681,"line":3232},[679,143966,985],{"class":693},[679,143968,143969],{"class":681,"line":3499},[679,143970,996],{"class":693},[5909,143972,143974],{"id":143973},"_2-dedicated-interceptor-class","2. Dedicated Interceptor Class",[651,143976,143977],{},"For a more reusable solution, create a separate interceptor class:",[669,143979,143981],{"className":4107,"code":143980,"language":4109,"meta":674,"style":674},"@Component\npublic class ClientLoggerRequestInterceptor implements ClientHttpRequestInterceptor {\n private static final Logger log = LoggerFactory.getLogger(ClientLoggerRequestInterceptor.class);\n\n @Override\n public ClientHttpResponse intercept(HttpRequest request, byte[] body, \n ClientHttpRequestExecution execution) throws IOException {\n logRequest(request, body);\n var response = execution.execute(request, body);\n return logResponse(request, response);\n }\n\n private void logRequest(HttpRequest request, byte[] body) {\n log.info(\"Request: {} {}\", request.getMethod(), request.getURI());\n logHeaders(request.getHeaders());\n if (body != null && body.length > 0) {\n log.info(\"Request body: {}\", new String(body, StandardCharsets.UTF_8));\n }\n }\n\n private ClientHttpResponse logResponse(HttpRequest request, \n ClientHttpResponse response) throws IOException {\n log.info(\"Response status: {}\", response.getStatusCode());\n logHeaders(response.getHeaders());\n \n byte[] responseBody = response.getBody().readAllBytes();\n if (responseBody.length > 0) {\n log.info(\"Response body: {}\", \n new String(responseBody, StandardCharsets.UTF_8));\n }\n \n // Return wrapped response to allow reading the body again\n return new BufferingClientHttpResponseWrapper(response, responseBody);\n }\n}\n",[676,143982,143983,143989,144005,144024,144028,144034,144059,144072,144079,144093,144101,144105,144109,144131,144151,144161,144181,144199,144203,144207,144211,144226,144239,144255,144265,144269,144287,144299,144311,144320,144324,144328,144333,144345,144349],{"__ignoreMap":674},[679,143984,143985,143987],{"class":681,"line":682},[679,143986,4116],{"class":693},[679,143988,13105],{"class":685},[679,143990,143991,143993,143995,143998,144000,144003],{"class":681,"line":790},[679,143992,6073],{"class":685},[679,143994,4512],{"class":685},[679,143996,143997],{"class":880}," ClientLoggerRequestInterceptor",[679,143999,4661],{"class":685},[679,144001,144002],{"class":880}," ClientHttpRequestInterceptor",[679,144004,884],{"class":693},[679,144006,144007,144009,144011,144013,144015,144017,144019,144021],{"class":681,"line":892},[679,144008,9232],{"class":685},[679,144010,6092],{"class":685},[679,144012,12768],{"class":685},[679,144014,111111],{"class":693},[679,144016,686],{"class":685},[679,144018,9240],{"class":693},[679,144020,9243],{"class":880},[679,144022,144023],{"class":693},"(ClientLoggerRequestInterceptor.class);\n",[679,144025,144026],{"class":681,"line":901},[679,144027,889],{"emptyLinePlaceholder":797},[679,144029,144030,144032],{"class":681,"line":909},[679,144031,6872],{"class":693},[679,144033,10723],{"class":685},[679,144035,144036,144038,144041,144044,144046,144048,144050,144052,144054,144056],{"class":681,"line":918},[679,144037,6089],{"class":685},[679,144039,144040],{"class":693}," ClientHttpResponse ",[679,144042,144043],{"class":880},"intercept",[679,144045,143746],{"class":693},[679,144047,10054],{"class":2099},[679,144049,2797],{"class":693},[679,144051,1057],{"class":685},[679,144053,16901],{"class":693},[679,144055,3006],{"class":2099},[679,144057,144058],{"class":693},", \n",[679,144060,144061,144064,144066,144068,144070],{"class":681,"line":935},[679,144062,144063],{"class":693}," ClientHttpRequestExecution ",[679,144065,125313],{"class":2099},[679,144067,2378],{"class":693},[679,144069,9580],{"class":685},[679,144071,143875],{"class":693},[679,144073,144074,144077],{"class":681,"line":944},[679,144075,144076],{"class":880}," logRequest",[679,144078,143684],{"class":693},[679,144080,144081,144083,144085,144087,144089,144091],{"class":681,"line":959},[679,144082,94003],{"class":685},[679,144084,124000],{"class":693},[679,144086,686],{"class":685},[679,144088,9474],{"class":693},[679,144090,143697],{"class":880},[679,144092,143684],{"class":693},[679,144094,144095,144097,144099],{"class":681,"line":964},[679,144096,9444],{"class":685},[679,144098,143859],{"class":880},[679,144100,143707],{"class":693},[679,144102,144103],{"class":681,"line":977},[679,144104,985],{"class":693},[679,144106,144107],{"class":681,"line":982},[679,144108,889],{"emptyLinePlaceholder":797},[679,144110,144111,144113,144115,144117,144119,144121,144123,144125,144127,144129],{"class":681,"line":988},[679,144112,9232],{"class":685},[679,144114,6095],{"class":685},[679,144116,143743],{"class":880},[679,144118,143746],{"class":693},[679,144120,10054],{"class":2099},[679,144122,2797],{"class":693},[679,144124,1057],{"class":685},[679,144126,16901],{"class":693},[679,144128,3006],{"class":2099},[679,144130,4390],{"class":693},[679,144132,144133,144135,144137,144139,144141,144143,144145,144147,144149],{"class":681,"line":993},[679,144134,119944],{"class":693},[679,144136,9415],{"class":880},[679,144138,745],{"class":693},[679,144140,143769],{"class":689},[679,144142,143772],{"class":693},[679,144144,10572],{"class":880},[679,144146,143777],{"class":693},[679,144148,143780],{"class":880},[679,144150,9431],{"class":693},[679,144152,144153,144155,144157,144159],{"class":681,"line":2129},[679,144154,143787],{"class":880},[679,144156,143790],{"class":693},[679,144158,143793],{"class":880},[679,144160,9431],{"class":693},[679,144162,144163,144165,144167,144169,144171,144173,144175,144177,144179],{"class":681,"line":2140},[679,144164,1249],{"class":685},[679,144166,143802],{"class":693},[679,144168,1587],{"class":685},[679,144170,2307],{"class":931},[679,144172,143809],{"class":685},[679,144174,143812],{"class":693},[679,144176,5860],{"class":685},[679,144178,14987],{"class":931},[679,144180,4390],{"class":693},[679,144182,144183,144185,144187,144189,144191,144193,144195,144197],{"class":681,"line":2145},[679,144184,104907],{"class":693},[679,144186,9415],{"class":880},[679,144188,745],{"class":693},[679,144190,143829],{"class":689},[679,144192,2797],{"class":693},[679,144194,8930],{"class":685},[679,144196,130055],{"class":880},[679,144198,143838],{"class":693},[679,144200,144201],{"class":681,"line":2154},[679,144202,1290],{"class":693},[679,144204,144205],{"class":681,"line":2159},[679,144206,985],{"class":693},[679,144208,144209],{"class":681,"line":2164},[679,144210,889],{"emptyLinePlaceholder":797},[679,144212,144213,144215,144217,144220,144222,144224],{"class":681,"line":3134},[679,144214,9232],{"class":685},[679,144216,144040],{"class":693},[679,144218,144219],{"class":880},"logResponse",[679,144221,143746],{"class":693},[679,144223,10054],{"class":2099},[679,144225,144058],{"class":693},[679,144227,144228,144231,144233,144235,144237],{"class":681,"line":3139},[679,144229,144230],{"class":693}," ClientHttpResponse ",[679,144232,10447],{"class":2099},[679,144234,2378],{"class":693},[679,144236,9580],{"class":685},[679,144238,143875],{"class":693},[679,144240,144241,144243,144245,144247,144249,144251,144253],{"class":681,"line":3144},[679,144242,119944],{"class":693},[679,144244,9415],{"class":880},[679,144246,745],{"class":693},[679,144248,143886],{"class":689},[679,144250,143889],{"class":693},[679,144252,118004],{"class":880},[679,144254,9431],{"class":693},[679,144256,144257,144259,144261,144263],{"class":681,"line":3149},[679,144258,143787],{"class":880},[679,144260,124023],{"class":693},[679,144262,143793],{"class":880},[679,144264,9431],{"class":693},[679,144266,144267],{"class":681,"line":3169},[679,144268,142274],{"class":693},[679,144270,144271,144273,144275,144277,144279,144281,144283,144285],{"class":681,"line":3185},[679,144272,143908],{"class":685},[679,144274,143911],{"class":693},[679,144276,686],{"class":685},[679,144278,46743],{"class":693},[679,144280,143918],{"class":880},[679,144282,10541],{"class":693},[679,144284,143923],{"class":880},[679,144286,9317],{"class":693},[679,144288,144289,144291,144293,144295,144297],{"class":681,"line":3194},[679,144290,1249],{"class":685},[679,144292,143932],{"class":693},[679,144294,5860],{"class":685},[679,144296,14987],{"class":931},[679,144298,4390],{"class":693},[679,144300,144301,144303,144305,144307,144309],{"class":681,"line":3199},[679,144302,104907],{"class":693},[679,144304,9415],{"class":880},[679,144306,745],{"class":693},[679,144308,143949],{"class":689},[679,144310,144058],{"class":693},[679,144312,144313,144316,144318],{"class":681,"line":3212},[679,144314,144315],{"class":685}," new",[679,144317,130055],{"class":880},[679,144319,143958],{"class":693},[679,144321,144322],{"class":681,"line":3217},[679,144323,1290],{"class":693},[679,144325,144326],{"class":681,"line":3222},[679,144327,142274],{"class":693},[679,144329,144330],{"class":681,"line":3227},[679,144331,144332],{"class":1400}," // Return wrapped response to allow reading the body again\n",[679,144334,144335,144337,144339,144342],{"class":681,"line":3232},[679,144336,9444],{"class":685},[679,144338,2054],{"class":685},[679,144340,144341],{"class":880}," BufferingClientHttpResponseWrapper",[679,144343,144344],{"class":693},"(response, responseBody);\n",[679,144346,144347],{"class":681,"line":3499},[679,144348,985],{"class":693},[679,144350,144351],{"class":681,"line":3509},[679,144352,996],{"class":693},[4542,144354,144356],{"id":144355},"handling-response-bodies","Handling Response Bodies",[651,144358,144359],{},"One important consideration when logging responses is that the response body can only be read once. To handle this, we need to cache the response body and provide it back to the client. Here's a wrapper class to accomplish this:",[669,144361,144363],{"className":4107,"code":144362,"language":4109,"meta":674,"style":674},"private static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {\n private final ClientHttpResponse response;\n private final byte[] body;\n\n public BufferingClientHttpResponseWrapper(ClientHttpResponse response, \n byte[] body) {\n this.response = response;\n this.body = body;\n }\n\n @Override\n public InputStream getBody() {\n return new ByteArrayInputStream(body);\n }\n\n // Delegate other methods to wrapped response\n @Override\n public HttpStatusCode getStatusCode() throws IOException {\n return response.getStatusCode();\n }\n\n @Override\n public HttpHeaders getHeaders() {\n return response.getHeaders();\n }\n}\n",[676,144364,144365,144382,144391,144403,144407,144420,144431,144442,144454,144458,144462,144468,144479,144490,144494,144498,144503,144509,144524,144534,144538,144542,144548,144559,144569,144573],{"__ignoreMap":674},[679,144366,144367,144369,144371,144373,144375,144377,144380],{"class":681,"line":682},[679,144368,21301],{"class":685},[679,144370,6092],{"class":685},[679,144372,4512],{"class":685},[679,144374,144341],{"class":880},[679,144376,4661],{"class":685},[679,144378,144379],{"class":880}," ClientHttpResponse",[679,144381,884],{"class":693},[679,144383,144384,144386,144388],{"class":681,"line":790},[679,144385,9232],{"class":685},[679,144387,12768],{"class":685},[679,144389,144390],{"class":693}," ClientHttpResponse response;\n",[679,144392,144393,144395,144397,144400],{"class":681,"line":892},[679,144394,9232],{"class":685},[679,144396,12768],{"class":685},[679,144398,144399],{"class":685}," byte",[679,144401,144402],{"class":693},"[] body;\n",[679,144404,144405],{"class":681,"line":901},[679,144406,889],{"emptyLinePlaceholder":797},[679,144408,144409,144411,144413,144416,144418],{"class":681,"line":909},[679,144410,6089],{"class":685},[679,144412,144341],{"class":880},[679,144414,144415],{"class":693},"(ClientHttpResponse ",[679,144417,10447],{"class":2099},[679,144419,144058],{"class":693},[679,144421,144422,144425,144427,144429],{"class":681,"line":918},[679,144423,144424],{"class":685}," byte",[679,144426,16901],{"class":693},[679,144428,3006],{"class":2099},[679,144430,4390],{"class":693},[679,144432,144433,144435,144438,144440],{"class":681,"line":935},[679,144434,7862],{"class":931},[679,144436,144437],{"class":693},".response ",[679,144439,686],{"class":685},[679,144441,137817],{"class":693},[679,144443,144444,144446,144449,144451],{"class":681,"line":944},[679,144445,7862],{"class":931},[679,144447,144448],{"class":693},".body ",[679,144450,686],{"class":685},[679,144452,144453],{"class":693}," body;\n",[679,144455,144456],{"class":681,"line":959},[679,144457,985],{"class":693},[679,144459,144460],{"class":681,"line":964},[679,144461,889],{"emptyLinePlaceholder":797},[679,144463,144464,144466],{"class":681,"line":977},[679,144465,6872],{"class":693},[679,144467,10723],{"class":685},[679,144469,144470,144472,144475,144477],{"class":681,"line":982},[679,144471,6089],{"class":685},[679,144473,144474],{"class":693}," InputStream ",[679,144476,143918],{"class":880},[679,144478,2667],{"class":693},[679,144480,144481,144483,144485,144487],{"class":681,"line":988},[679,144482,9444],{"class":685},[679,144484,2054],{"class":685},[679,144486,78901],{"class":880},[679,144488,144489],{"class":693},"(body);\n",[679,144491,144492],{"class":681,"line":993},[679,144493,985],{"class":693},[679,144495,144496],{"class":681,"line":2129},[679,144497,889],{"emptyLinePlaceholder":797},[679,144499,144500],{"class":681,"line":2140},[679,144501,144502],{"class":1400}," // Delegate other methods to wrapped response\n",[679,144504,144505,144507],{"class":681,"line":2145},[679,144506,6872],{"class":693},[679,144508,10723],{"class":685},[679,144510,144511,144513,144516,144518,144520,144522],{"class":681,"line":2154},[679,144512,6089],{"class":685},[679,144514,144515],{"class":693}," HttpStatusCode ",[679,144517,118004],{"class":880},[679,144519,6700],{"class":693},[679,144521,9580],{"class":685},[679,144523,143875],{"class":693},[679,144525,144526,144528,144530,144532],{"class":681,"line":2159},[679,144527,9444],{"class":685},[679,144529,46743],{"class":693},[679,144531,118004],{"class":880},[679,144533,9317],{"class":693},[679,144535,144536],{"class":681,"line":2164},[679,144537,985],{"class":693},[679,144539,144540],{"class":681,"line":3134},[679,144541,889],{"emptyLinePlaceholder":797},[679,144543,144544,144546],{"class":681,"line":3139},[679,144545,6872],{"class":693},[679,144547,10723],{"class":685},[679,144549,144550,144552,144555,144557],{"class":681,"line":3144},[679,144551,6089],{"class":685},[679,144553,144554],{"class":693}," HttpHeaders ",[679,144556,143793],{"class":880},[679,144558,2667],{"class":693},[679,144560,144561,144563,144565,144567],{"class":681,"line":3149},[679,144562,9444],{"class":685},[679,144564,46743],{"class":693},[679,144566,143793],{"class":880},[679,144568,9317],{"class":693},[679,144570,144571],{"class":681,"line":3169},[679,144572,985],{"class":693},[679,144574,144575],{"class":681,"line":3185},[679,144576,996],{"class":693},[4542,144578,139990],{"id":139989},[651,144580,144581],{},"When implementing Rest Client logging, keep these points in mind:",[27665,144583,144584,144589,144594],{},[5332,144585,144586,144588],{},[2939,144587,126490],{},": Be careful not to log sensitive information like authentication tokens or personal data.",[5332,144590,144591,144593],{},[2939,144592,74024],{},": Consider using appropriate log levels and conditional logging to minimize overhead.",[5332,144595,144596,144598],{},[2939,144597,6478],{},": Make logging behavior configurable through application properties:",[669,144600,144602],{"className":76589,"code":144601,"language":35538,"meta":674,"style":674},"logging.level.your.client.package=DEBUG\nclient.logging.enabled=true\nclient.logging.include-headers=false\n",[676,144603,144604,144612,144619],{"__ignoreMap":674},[679,144605,144606,144609],{"class":681,"line":682},[679,144607,144608],{"class":685},"logging.level.your.client.package",[679,144610,144611],{"class":693},"=DEBUG\n",[679,144613,144614,144617],{"class":681,"line":790},[679,144615,144616],{"class":685},"client.logging.enabled",[679,144618,93485],{"class":693},[679,144620,144621,144624],{"class":681,"line":892},[679,144622,144623],{"class":685},"client.logging.include-headers",[679,144625,103241],{"class":693},[27665,144627,144628],{"start":901},[5332,144629,144630,144633],{},[2939,144631,144632],{},"Large Payloads",": Consider truncating large request/response bodies to prevent log bloat:",[669,144635,144637],{"className":4107,"code":144636,"language":4109,"meta":674,"style":674},"private String truncateIfNeeded(String content, int maxLength) {\n if (content.length() \u003C= maxLength) {\n return content;\n }\n return content.substring(0, maxLength) + \"... (truncated)\";\n}\n",[676,144638,144639,144656,144671,144677,144681,144704],{"__ignoreMap":674},[679,144640,144641,144643,144645,144648,144651,144653],{"class":681,"line":682},[679,144642,21301],{"class":685},[679,144644,9289],{"class":693},[679,144646,144647],{"class":880},"truncateIfNeeded",[679,144649,144650],{"class":693},"(String content, ",[679,144652,1078],{"class":685},[679,144654,144655],{"class":693}," maxLength) {\n",[679,144657,144658,144660,144663,144665,144667,144669],{"class":681,"line":790},[679,144659,1231],{"class":685},[679,144661,144662],{"class":693}," (content.",[679,144664,51959],{"class":880},[679,144666,6700],{"class":693},[679,144668,1563],{"class":685},[679,144670,144655],{"class":693},[679,144672,144673,144675],{"class":681,"line":892},[679,144674,9444],{"class":685},[679,144676,77642],{"class":693},[679,144678,144679],{"class":681,"line":901},[679,144680,985],{"class":693},[679,144682,144683,144685,144688,144690,144692,144694,144697,144699,144702],{"class":681,"line":909},[679,144684,21478],{"class":685},[679,144686,144687],{"class":693}," content.",[679,144689,143049],{"class":880},[679,144691,745],{"class":693},[679,144693,1060],{"class":931},[679,144695,144696],{"class":693},", maxLength) ",[679,144698,3065],{"class":685},[679,144700,144701],{"class":689}," \"... (truncated)\"",[679,144703,1186],{"class":693},[679,144705,144706],{"class":681,"line":918},[679,144707,996],{"class":693},[4542,144709,144711],{"id":144710},"testing-your-logging-implementation","Testing Your Logging Implementation",[651,144713,144714],{},"Here's a simple test to verify your logging implementation:",[669,144716,144718],{"className":4107,"code":144717,"language":4109,"meta":674,"style":674},"@SpringBootTest\nclass ClientLoggerRequestInterceptorTest {\n\n @Autowired\n private ClientLoggerRequestInterceptor interceptor;\n\n @Test\n void shouldLogRequestAndResponse() throws IOException {\n // Create test request\n MockClientHttpRequest request = new MockClientHttpRequest();\n request.setMethod(HttpMethod.GET);\n request.setURI(URI.create(\"http://test.com/api\"));\n\n // Execute with interceptor\n ClientHttpResponse response = interceptor.intercept(\n request, \n \"test body\".getBytes(), \n (req, body) -> new MockClientHttpResponse(\n \"response\".getBytes(), \n HttpStatus.OK\n )\n );\n\n // Verify response can still be read\n String responseBody = new String(response.getBody().readAllBytes());\n assertThat(responseBody).isEqualTo(\"response\");\n }\n}\n",[676,144719,144720,144727,144736,144740,144746,144753,144757,144763,144776,144781,144795,144806,144824,144828,144833,144847,144852,144864,144878,144889,144894,144898,144902,144906,144911,144932,144950,144954],{"__ignoreMap":674},[679,144721,144722,144724],{"class":681,"line":682},[679,144723,4116],{"class":693},[679,144725,144726],{"class":685},"SpringBootTest\n",[679,144728,144729,144731,144734],{"class":681,"line":790},[679,144730,877],{"class":685},[679,144732,144733],{"class":880}," ClientLoggerRequestInterceptorTest",[679,144735,884],{"class":693},[679,144737,144738],{"class":681,"line":892},[679,144739,889],{"emptyLinePlaceholder":797},[679,144741,144742,144744],{"class":681,"line":901},[679,144743,6872],{"class":693},[679,144745,9257],{"class":685},[679,144747,144748,144750],{"class":681,"line":909},[679,144749,9232],{"class":685},[679,144751,144752],{"class":693}," ClientLoggerRequestInterceptor interceptor;\n",[679,144754,144755],{"class":681,"line":918},[679,144756,889],{"emptyLinePlaceholder":797},[679,144758,144759,144761],{"class":681,"line":935},[679,144760,6872],{"class":693},[679,144762,73087],{"class":685},[679,144764,144765,144767,144770,144772,144774],{"class":681,"line":944},[679,144766,3314],{"class":685},[679,144768,144769],{"class":880}," shouldLogRequestAndResponse",[679,144771,6700],{"class":693},[679,144773,9580],{"class":685},[679,144775,143875],{"class":693},[679,144777,144778],{"class":681,"line":959},[679,144779,144780],{"class":1400}," // Create test request\n",[679,144782,144783,144786,144788,144790,144793],{"class":681,"line":964},[679,144784,144785],{"class":693}," MockClientHttpRequest request ",[679,144787,686],{"class":685},[679,144789,2054],{"class":685},[679,144791,144792],{"class":880}," MockClientHttpRequest",[679,144794,9317],{"class":693},[679,144796,144797,144800,144803],{"class":681,"line":977},[679,144798,144799],{"class":693}," request.",[679,144801,144802],{"class":880},"setMethod",[679,144804,144805],{"class":693},"(HttpMethod.GET);\n",[679,144807,144808,144810,144813,144815,144817,144819,144822],{"class":681,"line":982},[679,144809,144799],{"class":693},[679,144811,144812],{"class":880},"setURI",[679,144814,123893],{"class":693},[679,144816,5697],{"class":880},[679,144818,745],{"class":693},[679,144820,144821],{"class":689},"\"http://test.com/api\"",[679,144823,1669],{"class":693},[679,144825,144826],{"class":681,"line":988},[679,144827,889],{"emptyLinePlaceholder":797},[679,144829,144830],{"class":681,"line":993},[679,144831,144832],{"class":1400}," // Execute with interceptor\n",[679,144834,144835,144838,144840,144843,144845],{"class":681,"line":2129},[679,144836,144837],{"class":693}," ClientHttpResponse response ",[679,144839,686],{"class":685},[679,144841,144842],{"class":693}," interceptor.",[679,144844,144043],{"class":880},[679,144846,21337],{"class":693},[679,144848,144849],{"class":681,"line":2140},[679,144850,144851],{"class":693}," request, \n",[679,144853,144854,144857,144859,144861],{"class":681,"line":2145},[679,144855,144856],{"class":689}," \"test body\"",[679,144858,664],{"class":693},[679,144860,78907],{"class":880},[679,144862,144863],{"class":693},"(), \n",[679,144865,144866,144869,144871,144873,144876],{"class":681,"line":2154},[679,144867,144868],{"class":693}," (req, body) ",[679,144870,16955],{"class":685},[679,144872,2054],{"class":685},[679,144874,144875],{"class":880}," MockClientHttpResponse",[679,144877,21337],{"class":693},[679,144879,144880,144883,144885,144887],{"class":681,"line":2159},[679,144881,144882],{"class":689}," \"response\"",[679,144884,664],{"class":693},[679,144886,78907],{"class":880},[679,144888,144863],{"class":693},[679,144890,144891],{"class":681,"line":2164},[679,144892,144893],{"class":693}," HttpStatus.OK\n",[679,144895,144896],{"class":681,"line":3134},[679,144897,89994],{"class":693},[679,144899,144900],{"class":681,"line":3139},[679,144901,21400],{"class":693},[679,144903,144904],{"class":681,"line":3144},[679,144905,889],{"emptyLinePlaceholder":797},[679,144907,144908],{"class":681,"line":3149},[679,144909,144910],{"class":1400}," // Verify response can still be read\n",[679,144912,144913,144916,144918,144920,144922,144924,144926,144928,144930],{"class":681,"line":3169},[679,144914,144915],{"class":693}," String responseBody ",[679,144917,686],{"class":685},[679,144919,2054],{"class":685},[679,144921,130055],{"class":880},[679,144923,124023],{"class":693},[679,144925,143918],{"class":880},[679,144927,10541],{"class":693},[679,144929,143923],{"class":880},[679,144931,9431],{"class":693},[679,144933,144934,144937,144940,144943,144945,144948],{"class":681,"line":3185},[679,144935,144936],{"class":880}," assertThat",[679,144938,144939],{"class":693},"(responseBody).",[679,144941,144942],{"class":880},"isEqualTo",[679,144944,745],{"class":693},[679,144946,144947],{"class":689},"\"response\"",[679,144949,1208],{"class":693},[679,144951,144952],{"class":681,"line":3194},[679,144953,985],{"class":693},[679,144955,144956],{"class":681,"line":3199},[679,144957,996],{"class":693},[4542,144959,9042],{"id":9041},[651,144961,144962],{},"Proper logging of HTTP interactions is essential for maintaining and troubleshooting applications. Spring Boot's Rest Client provides flexible options for implementing logging, whether through inline configuration or a dedicated interceptor.",[651,144964,144965],{},"Choose the approach that best fits your needs - inline logging for simplicity, or a dedicated interceptor for reusability across multiple clients. Remember to consider security, performance, and maintenance when implementing your logging solution.",[651,144967,144968],{},"Have you implemented logging for your Rest Client? What challenges did you face? Share your experiences in the comments below!",[651,144970,143516],{},[786,144972,144973],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":144975},[144976,144977,144981,144982,144983,144984],{"id":143553,"depth":790,"text":143554},{"id":143563,"depth":790,"text":143564,"children":144978},[144979,144980],{"id":143570,"depth":892,"text":143571},{"id":143973,"depth":892,"text":143974},{"id":144355,"depth":790,"text":144356},{"id":139989,"depth":790,"text":139990},{"id":144710,"depth":790,"text":144711},{"id":9041,"depth":790,"text":9042},"Learn how to implement clean and efficient request/response logging for Spring Boot's Rest Client, including both inline and interceptor-based approaches.",{"slug":144987,"date":144988,"published":797,"author":798,"tags":144989,"video":144991,"github":144992,"keywords":144993},"spring-boot-rest-client-logging","2024-10-31T09:00:00.000Z",[7077,144990],"Rest Client","https://www.youtube.com/embed/l35P5GylXN8","https://github.com/danvega/rc-logging","spring boot, rest client, http logging, request logging, response logging, spring framework, java, api logging, http interceptor",{"title":39,"description":144985},"blog/2024/10/31/spring-boot-rest-client-logging","FybY2fHXdYEOU6bHHmiDqhCEKOVHi-XZQJmNHrrZqQo",{"id":144998,"title":36,"body":144999,"description":146151,"extension":793,"meta":146152,"navigation":797,"path":37,"seo":146158,"stem":146159,"__hash__":146160},"content/blog/2024/11/01/spring-ai-tokens.md",{"type":648,"value":145000,"toc":146143},[145001,145004,145008,145011,145014,145025,145028,145036,145045,145050,145053,145156,145161,145164,145168,145171,145230,145232,145250,145253,145510,145514,145517,145680,145684,145687,146032,146035,146099,146103,146129,146131,146134,146137,146140],[651,145002,145003],{},"When working with Large Language Models (LLMs), every interaction involves tokens – the fundamental units that determine both the cost and capabilities of your AI integrations. In this tutorial, you'll learn how to track and manage token usage in your Spring AI applications, ensuring you can build cost-effective and efficient AI-powered features.",[4542,145005,145007],{"id":145006},"what-are-tokens-and-why-do-they-matter","What are Tokens and Why Do They Matter?",[651,145009,145010],{},"Tokens are the building blocks of text processing in LLMs. They're not exactly words – typically, a token represents about 3/4 of a word in English. For example, the word \"springboot\" might be split into \"spring\" and \"boot\" as separate tokens, while common words like \"the\" or \"and\" are usually single tokens.",[651,145012,145013],{},"Understanding token usage is crucial because:",[5316,145015,145016,145019,145022],{},[5332,145017,145018],{},"Tokens determine the cost of API calls to LLM providers",[5332,145020,145021],{},"They affect the context window size (maximum input length)",[5332,145023,145024],{},"Different models have different token limits and pricing structures",[651,145026,145027],{},"For instance, with OpenAI's GPT-4, you might pay:",[5316,145029,145030,145033],{},[5332,145031,145032],{},"$0.01 per 1K tokens for input (prompts)",[5332,145034,145035],{},"$0.03 per 1K tokens for output (completions)",[651,145037,145038,145039,145044],{},"For a better understanding of how tokens are calculated, OpenAI offers a ",[812,145040,145043],{"href":145041,"rel":145042},"https://platform.openai.com/tokenizer",[816],"Tokenizer"," tool that visualizes token breakdown.",[651,145046,145047],{},[660,145048],{"alt":145043,"src":145049},"/images/blog/2024/11/01/tokenizer.png",[651,145051,145052],{},"Once you understand what tokens are it's also important to understand that each Large Language Model (LLMs) has a maximum number of tokens that can be sent in or out. Here's an updated table detailing the context window sizes of major LLMs:",[1031,145054,145055,145066],{},[1034,145056,145057],{},[1037,145058,145059,145061,145064],{},[1040,145060,29450],{},[1040,145062,145063],{},"Context Window Size",[1040,145065,12285],{},[1050,145067,145068,145079,145090,145101,145112,145123,145134,145145],{},[1037,145069,145070,145073,145076],{},[1055,145071,145072],{},"GPT-4 (32K)",[1055,145074,145075],{},"32,768 tokens",[1055,145077,145078],{},"For handling larger, complex tasks.",[1037,145080,145081,145084,145087],{},[1055,145082,145083],{},"GPT-3.5 Turbo",[1055,145085,145086],{},"4,096 tokens",[1055,145088,145089],{},"Streamlined, fast chat model from OpenAI.",[1037,145091,145092,145095,145098],{},[1055,145093,145094],{},"Claude 2",[1055,145096,145097],{},"100,000 tokens",[1055,145099,145100],{},"Ideal for long documents and chat memory.",[1037,145102,145103,145106,145109],{},[1055,145104,145105],{},"Claude 3.5",[1055,145107,145108],{},"200,000 tokens",[1055,145110,145111],{},"Twice the capacity of Claude 2, useful for extended workflows.",[1037,145113,145114,145117,145120],{},[1055,145115,145116],{},"Gemini 1.5 Flash",[1055,145118,145119],{},"1 million tokens",[1055,145121,145122],{},"Designed for in-depth, multimodal analysis.",[1037,145124,145125,145128,145131],{},[1055,145126,145127],{},"Gemini 1.5 Pro",[1055,145129,145130],{},"2 million tokens",[1055,145132,145133],{},"The largest context window, suitable for vast input like entire codebases or hours of media.",[1037,145135,145136,145139,145142],{},[1055,145137,145138],{},"LLaMA 2",[1055,145140,145141],{},"4,096 - 8,192 tokens",[1055,145143,145144],{},"Open-source model with multiple versions.",[1037,145146,145147,145150,145153],{},[1055,145148,145149],{},"Falcon LLM",[1055,145151,145152],{},"2,048 - 4,096 tokens",[1055,145154,145155],{},"Lightweight, efficient for various applications.",[651,145157,145158],{},[7300,145159,145160],{},"Note: The context window size refers to the maximum number of tokens (words or characters) the model can process in a single input. Larger context windows allow models to consider more extensive input data, enhancing their ability to generate coherent and contextually relevant responses.",[651,145162,145163],{},"For instance, GPT-4 Turbo has a context window of 8,192 tokens, while Claude 2.1 and Gemini 1.5 offer significantly larger windows of 200,000 and 1,000,000 tokens, respectively.",[4542,145165,145167],{"id":145166},"setting-up-token-tracking-in-spring-ai","Setting Up Token Tracking in Spring AI",[651,145169,145170],{},"Let's create a Spring Boot application that demonstrates token tracking. First, create a new project with the required dependencies:",[669,145172,145174],{"className":9101,"code":145173,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.ai\u003C/groupId>\n \u003CartifactId>spring-ai-starter-anthropic\u003C/artifactId>\n \u003Cversion>0.8.0\u003C/version>\n\u003C/dependency>\n",[676,145175,145176,145184,145196,145209,145222],{"__ignoreMap":674},[679,145177,145178,145180,145182],{"class":681,"line":682},[679,145179,4505],{"class":693},[679,145181,119838],{"class":4508},[679,145183,4519],{"class":693},[679,145185,145186,145188,145190,145192,145194],{"class":681,"line":790},[679,145187,4524],{"class":693},[679,145189,119847],{"class":4508},[679,145191,129701],{"class":693},[679,145193,119847],{"class":4508},[679,145195,4519],{"class":693},[679,145197,145198,145200,145202,145205,145207],{"class":681,"line":892},[679,145199,4524],{"class":693},[679,145201,119861],{"class":4508},[679,145203,145204],{"class":693},">spring-ai-starter-anthropic\u003C/",[679,145206,119861],{"class":4508},[679,145208,4519],{"class":693},[679,145210,145211,145213,145215,145218,145220],{"class":681,"line":901},[679,145212,4524],{"class":693},[679,145214,107166],{"class":4508},[679,145216,145217],{"class":693},">0.8.0\u003C/",[679,145219,107166],{"class":4508},[679,145221,4519],{"class":693},[679,145223,145224,145226,145228],{"class":681,"line":909},[679,145225,4586],{"class":693},[679,145227,119838],{"class":4508},[679,145229,4519],{"class":693},[651,145231,140186],{},[669,145233,145235],{"className":76589,"code":145234,"language":35538,"meta":674,"style":674},"spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}\nspring.ai.anthropic.model=claude-3-sonnet-20240229\n",[676,145236,145237,145243],{"__ignoreMap":674},[679,145238,145239,145241],{"class":681,"line":682},[679,145240,129765],{"class":685},[679,145242,129768],{"class":693},[679,145244,145245,145248],{"class":681,"line":790},[679,145246,145247],{"class":685},"spring.ai.anthropic.model",[679,145249,138837],{"class":693},[651,145251,145252],{},"Create a basic controller to interact with the LLM:",[669,145254,145256],{"className":4107,"code":145255,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/tokens\")\npublic class TokenController {\n \n private final ChatClient chatClient;\n private static final Logger log = LoggerFactory.getLogger(TokenController.class);\n\n public TokenController(ChatClient.Builder builder) {\n this.chatClient = builder.build();\n }\n\n @GetMapping(\"/basic\")\n public String getBasicResponse() {\n return chatClient.prompt()\n .withContent(\"Tell me a fun fact about Java\")\n .call()\n .getResult()\n .getOutput()\n .getContent();\n }\n\n @GetMapping(\"/detailed\")\n public ChatResponse getDetailedResponse() {\n return chatClient.prompt()\n .withContent(\"Tell me a fun fact about Java\")\n .call();\n }\n}\n",[676,145257,145258,145264,145277,145288,145292,145300,145319,145323,145335,145349,145353,145357,145370,145381,145391,145405,145413,145422,145431,145439,145443,145447,145460,145472,145482,145494,145502,145506],{"__ignoreMap":674},[679,145259,145260,145262],{"class":681,"line":682},[679,145261,4116],{"class":693},[679,145263,9212],{"class":685},[679,145265,145266,145268,145270,145272,145275],{"class":681,"line":790},[679,145267,4116],{"class":693},[679,145269,9275],{"class":685},[679,145271,745],{"class":693},[679,145273,145274],{"class":689},"\"/api/tokens\"",[679,145276,1339],{"class":693},[679,145278,145279,145281,145283,145286],{"class":681,"line":892},[679,145280,6073],{"class":685},[679,145282,4512],{"class":685},[679,145284,145285],{"class":880}," TokenController",[679,145287,884],{"class":693},[679,145289,145290],{"class":681,"line":901},[679,145291,119642],{"class":693},[679,145293,145294,145296,145298],{"class":681,"line":909},[679,145295,9232],{"class":685},[679,145297,12768],{"class":685},[679,145299,121650],{"class":693},[679,145301,145302,145304,145306,145308,145310,145312,145314,145316],{"class":681,"line":918},[679,145303,9232],{"class":685},[679,145305,6092],{"class":685},[679,145307,12768],{"class":685},[679,145309,111111],{"class":693},[679,145311,686],{"class":685},[679,145313,9240],{"class":693},[679,145315,9243],{"class":880},[679,145317,145318],{"class":693},"(TokenController.class);\n",[679,145320,145321],{"class":681,"line":935},[679,145322,889],{"emptyLinePlaceholder":797},[679,145324,145325,145327,145329,145331,145333],{"class":681,"line":944},[679,145326,6089],{"class":685},[679,145328,145285],{"class":880},[679,145330,121663],{"class":693},[679,145332,90934],{"class":2099},[679,145334,4390],{"class":693},[679,145336,145337,145339,145341,145343,145345,145347],{"class":681,"line":959},[679,145338,7862],{"class":931},[679,145340,121674],{"class":693},[679,145342,686],{"class":685},[679,145344,136298],{"class":693},[679,145346,23612],{"class":880},[679,145348,9317],{"class":693},[679,145350,145351],{"class":681,"line":964},[679,145352,985],{"class":693},[679,145354,145355],{"class":681,"line":977},[679,145356,889],{"emptyLinePlaceholder":797},[679,145358,145359,145361,145363,145365,145368],{"class":681,"line":982},[679,145360,6872],{"class":693},[679,145362,20852],{"class":685},[679,145364,745],{"class":693},[679,145366,145367],{"class":689},"\"/basic\"",[679,145369,1339],{"class":693},[679,145371,145372,145374,145376,145379],{"class":681,"line":988},[679,145373,6089],{"class":685},[679,145375,9289],{"class":693},[679,145377,145378],{"class":880},"getBasicResponse",[679,145380,2667],{"class":693},[679,145382,145383,145385,145387,145389],{"class":681,"line":993},[679,145384,9444],{"class":685},[679,145386,121763],{"class":693},[679,145388,55494],{"class":880},[679,145390,17545],{"class":693},[679,145392,145393,145395,145398,145400,145403],{"class":681,"line":2129},[679,145394,73482],{"class":693},[679,145396,145397],{"class":880},"withContent",[679,145399,745],{"class":693},[679,145401,145402],{"class":689},"\"Tell me a fun fact about Java\"",[679,145404,1339],{"class":693},[679,145406,145407,145409,145411],{"class":681,"line":2140},[679,145408,73482],{"class":693},[679,145410,121783],{"class":880},[679,145412,17545],{"class":693},[679,145414,145415,145417,145420],{"class":681,"line":2145},[679,145416,73482],{"class":693},[679,145418,145419],{"class":880},"getResult",[679,145421,17545],{"class":693},[679,145423,145424,145426,145429],{"class":681,"line":2154},[679,145425,73482],{"class":693},[679,145427,145428],{"class":880},"getOutput",[679,145430,17545],{"class":693},[679,145432,145433,145435,145437],{"class":681,"line":2159},[679,145434,73482],{"class":693},[679,145436,77684],{"class":880},[679,145438,9317],{"class":693},[679,145440,145441],{"class":681,"line":2164},[679,145442,985],{"class":693},[679,145444,145445],{"class":681,"line":3134},[679,145446,889],{"emptyLinePlaceholder":797},[679,145448,145449,145451,145453,145455,145458],{"class":681,"line":3139},[679,145450,6872],{"class":693},[679,145452,20852],{"class":685},[679,145454,745],{"class":693},[679,145456,145457],{"class":689},"\"/detailed\"",[679,145459,1339],{"class":693},[679,145461,145462,145464,145467,145470],{"class":681,"line":3144},[679,145463,6089],{"class":685},[679,145465,145466],{"class":693}," ChatResponse ",[679,145468,145469],{"class":880},"getDetailedResponse",[679,145471,2667],{"class":693},[679,145473,145474,145476,145478,145480],{"class":681,"line":3149},[679,145475,9444],{"class":685},[679,145477,121763],{"class":693},[679,145479,55494],{"class":880},[679,145481,17545],{"class":693},[679,145483,145484,145486,145488,145490,145492],{"class":681,"line":3169},[679,145485,73482],{"class":693},[679,145487,145397],{"class":880},[679,145489,745],{"class":693},[679,145491,145402],{"class":689},[679,145493,1339],{"class":693},[679,145495,145496,145498,145500],{"class":681,"line":3185},[679,145497,73482],{"class":693},[679,145499,121783],{"class":880},[679,145501,9317],{"class":693},[679,145503,145504],{"class":681,"line":3194},[679,145505,985],{"class":693},[679,145507,145508],{"class":681,"line":3199},[679,145509,996],{"class":693},[4542,145511,145513],{"id":145512},"creating-a-custom-response-type","Creating a Custom Response Type",[651,145515,145516],{},"To make token tracking more intuitive, let's create a custom response type:",[669,145518,145520],{"className":4107,"code":145519,"language":4109,"meta":674,"style":674},"public record CustomResponse(String content, Usage usage) {\n public record Usage(int promptTokens, int completionTokens, int totalTokens) {}\n}\n\n@GetMapping(\"/custom\")\npublic CustomResponse getCustomResponse() {\n ChatResponse response = chatClient.prompt()\n .withContent(\"Tell me a fun fact about Java\")\n .call();\n \n return new CustomResponse(\n response.getResult().getOutput().getContent(),\n response.getMetadata().getUsage()\n );\n}\n",[676,145521,145522,145534,145560,145564,145568,145581,145593,145606,145618,145626,145631,145641,145658,145672,145676],{"__ignoreMap":674},[679,145523,145524,145526,145528,145531],{"class":681,"line":682},[679,145525,6073],{"class":685},[679,145527,86928],{"class":685},[679,145529,145530],{"class":880}," CustomResponse",[679,145532,145533],{"class":693},"(String content, Usage usage) {\n",[679,145535,145536,145538,145540,145543,145545,145547,145550,145552,145555,145557],{"class":681,"line":790},[679,145537,6089],{"class":685},[679,145539,86928],{"class":685},[679,145541,145542],{"class":880}," Usage",[679,145544,745],{"class":693},[679,145546,1078],{"class":685},[679,145548,145549],{"class":693}," promptTokens, ",[679,145551,1078],{"class":685},[679,145553,145554],{"class":693}," completionTokens, ",[679,145556,1078],{"class":685},[679,145558,145559],{"class":693}," totalTokens) {}\n",[679,145561,145562],{"class":681,"line":892},[679,145563,996],{"class":693},[679,145565,145566],{"class":681,"line":901},[679,145567,889],{"emptyLinePlaceholder":797},[679,145569,145570,145572,145574,145576,145579],{"class":681,"line":909},[679,145571,4116],{"class":693},[679,145573,20852],{"class":685},[679,145575,745],{"class":693},[679,145577,145578],{"class":689},"\"/custom\"",[679,145580,1339],{"class":693},[679,145582,145583,145585,145588,145591],{"class":681,"line":918},[679,145584,6073],{"class":685},[679,145586,145587],{"class":693}," CustomResponse ",[679,145589,145590],{"class":880},"getCustomResponse",[679,145592,2667],{"class":693},[679,145594,145595,145598,145600,145602,145604],{"class":681,"line":935},[679,145596,145597],{"class":693}," ChatResponse response ",[679,145599,686],{"class":685},[679,145601,121763],{"class":693},[679,145603,55494],{"class":880},[679,145605,17545],{"class":693},[679,145607,145608,145610,145612,145614,145616],{"class":681,"line":944},[679,145609,40148],{"class":693},[679,145611,145397],{"class":880},[679,145613,745],{"class":693},[679,145615,145402],{"class":689},[679,145617,1339],{"class":693},[679,145619,145620,145622,145624],{"class":681,"line":959},[679,145621,40148],{"class":693},[679,145623,121783],{"class":880},[679,145625,9317],{"class":693},[679,145627,145628],{"class":681,"line":964},[679,145629,145630],{"class":693}," \n",[679,145632,145633,145635,145637,145639],{"class":681,"line":977},[679,145634,21478],{"class":685},[679,145636,2054],{"class":685},[679,145638,145530],{"class":880},[679,145640,21337],{"class":693},[679,145642,145643,145646,145648,145650,145652,145654,145656],{"class":681,"line":982},[679,145644,145645],{"class":693}," response.",[679,145647,145419],{"class":880},[679,145649,10541],{"class":693},[679,145651,145428],{"class":880},[679,145653,10541],{"class":693},[679,145655,77684],{"class":880},[679,145657,56208],{"class":693},[679,145659,145660,145662,145665,145667,145670],{"class":681,"line":988},[679,145661,145645],{"class":693},[679,145663,145664],{"class":880},"getMetadata",[679,145666,10541],{"class":693},[679,145668,145669],{"class":880},"getUsage",[679,145671,17545],{"class":693},[679,145673,145674],{"class":681,"line":993},[679,145675,89766],{"class":693},[679,145677,145678],{"class":681,"line":2129},[679,145679,996],{"class":693},[4542,145681,145683],{"id":145682},"automating-token-logging-with-aop","Automating Token Logging with AOP",[651,145685,145686],{},"Instead of manually logging token usage in each controller method, we can use Spring AOP to automatically track tokens across our application:",[669,145688,145690],{"className":4107,"code":145689,"language":4109,"meta":674,"style":674},"@Aspect\n@Component\npublic class TokenLoggingAspect {\n\n private static final Logger log = LoggerFactory.getLogger(TokenLoggingAspect.class);\n\n @Around(\"execution(* com.example.demo.TokenController.*(..))\")\n public Object logTokenUsage(ProceedingJoinPoint joinPoint) throws Throwable {\n String methodName = joinPoint.getSignature().getName();\n log.info(\"Entering method: {}\", methodName);\n \n long startTime = System.currentTimeMillis();\n Object result = joinPoint.proceed();\n \n if (result instanceof ChatResponse response) {\n Usage usage = response.getMetadata().getUsage();\n logUsage(usage);\n } else if (result instanceof CustomResponse customResponse) {\n logUsage(customResponse.usage());\n }\n \n log.info(\"Method {} executed in {}ms\", methodName, \n System.currentTimeMillis() - startTime);\n \n return result;\n }\n \n private void logUsage(Usage usage) {\n log.info(\"Token Usage - Prompt: {}, Completion: {}, Total: {}\", \n usage.promptTokens(), \n usage.completionTokens(), \n usage.totalTokens());\n }\n}\n",[676,145691,145692,145698,145704,145715,145719,145738,145742,145756,145778,145795,145809,145813,145830,145844,145848,145860,145877,145885,145901,145913,145917,145921,145935,145949,145953,145959,145963,145967,145983,145996,146006,146015,146024,146028],{"__ignoreMap":674},[679,145693,145694,145696],{"class":681,"line":682},[679,145695,4116],{"class":693},[679,145697,13112],{"class":685},[679,145699,145700,145702],{"class":681,"line":790},[679,145701,4116],{"class":693},[679,145703,13105],{"class":685},[679,145705,145706,145708,145710,145713],{"class":681,"line":892},[679,145707,6073],{"class":685},[679,145709,4512],{"class":685},[679,145711,145712],{"class":880}," TokenLoggingAspect",[679,145714,884],{"class":693},[679,145716,145717],{"class":681,"line":901},[679,145718,889],{"emptyLinePlaceholder":797},[679,145720,145721,145723,145725,145727,145729,145731,145733,145735],{"class":681,"line":909},[679,145722,9232],{"class":685},[679,145724,6092],{"class":685},[679,145726,12768],{"class":685},[679,145728,111111],{"class":693},[679,145730,686],{"class":685},[679,145732,9240],{"class":693},[679,145734,9243],{"class":880},[679,145736,145737],{"class":693},"(TokenLoggingAspect.class);\n",[679,145739,145740],{"class":681,"line":918},[679,145741,889],{"emptyLinePlaceholder":797},[679,145743,145744,145746,145749,145751,145754],{"class":681,"line":935},[679,145745,6872],{"class":693},[679,145747,145748],{"class":685},"Around",[679,145750,745],{"class":693},[679,145752,145753],{"class":689},"\"execution(* com.example.demo.TokenController.*(..))\"",[679,145755,1339],{"class":693},[679,145757,145758,145760,145763,145766,145769,145771,145773,145775],{"class":681,"line":944},[679,145759,6089],{"class":685},[679,145761,145762],{"class":693}," Object ",[679,145764,145765],{"class":880},"logTokenUsage",[679,145767,145768],{"class":693},"(ProceedingJoinPoint ",[679,145770,13185],{"class":2099},[679,145772,2378],{"class":693},[679,145774,9580],{"class":685},[679,145776,145777],{"class":693}," Throwable {\n",[679,145779,145780,145783,145785,145787,145789,145791,145793],{"class":681,"line":959},[679,145781,145782],{"class":693}," String methodName ",[679,145784,686],{"class":685},[679,145786,13197],{"class":693},[679,145788,13200],{"class":880},[679,145790,10541],{"class":693},[679,145792,10577],{"class":880},[679,145794,9317],{"class":693},[679,145796,145797,145799,145801,145803,145806],{"class":681,"line":964},[679,145798,119944],{"class":693},[679,145800,9415],{"class":880},[679,145802,745],{"class":693},[679,145804,145805],{"class":689},"\"Entering method: {}\"",[679,145807,145808],{"class":693},", methodName);\n",[679,145810,145811],{"class":681,"line":977},[679,145812,142274],{"class":693},[679,145814,145815,145818,145821,145823,145825,145828],{"class":681,"line":982},[679,145816,145817],{"class":685}," long",[679,145819,145820],{"class":693}," startTime ",[679,145822,686],{"class":685},[679,145824,108725],{"class":693},[679,145826,145827],{"class":880},"currentTimeMillis",[679,145829,9317],{"class":693},[679,145831,145832,145835,145837,145839,145842],{"class":681,"line":988},[679,145833,145834],{"class":693}," Object result ",[679,145836,686],{"class":685},[679,145838,13197],{"class":693},[679,145840,145841],{"class":880},"proceed",[679,145843,9317],{"class":693},[679,145845,145846],{"class":681,"line":993},[679,145847,142274],{"class":693},[679,145849,145850,145852,145855,145857],{"class":681,"line":2129},[679,145851,1249],{"class":685},[679,145853,145854],{"class":693}," (result ",[679,145856,10502],{"class":685},[679,145858,145859],{"class":693}," ChatResponse response) {\n",[679,145861,145862,145865,145867,145869,145871,145873,145875],{"class":681,"line":2140},[679,145863,145864],{"class":693}," Usage usage ",[679,145866,686],{"class":685},[679,145868,46743],{"class":693},[679,145870,145664],{"class":880},[679,145872,10541],{"class":693},[679,145874,145669],{"class":880},[679,145876,9317],{"class":693},[679,145878,145879,145882],{"class":681,"line":2145},[679,145880,145881],{"class":880}," logUsage",[679,145883,145884],{"class":693},"(usage);\n",[679,145886,145887,145889,145891,145894,145896,145898],{"class":681,"line":2154},[679,145888,15675],{"class":693},[679,145890,2256],{"class":685},[679,145892,145893],{"class":685}," if",[679,145895,145854],{"class":693},[679,145897,10502],{"class":685},[679,145899,145900],{"class":693}," CustomResponse customResponse) {\n",[679,145902,145903,145905,145908,145911],{"class":681,"line":2159},[679,145904,145881],{"class":880},[679,145906,145907],{"class":693},"(customResponse.",[679,145909,145910],{"class":880},"usage",[679,145912,9431],{"class":693},[679,145914,145915],{"class":681,"line":2164},[679,145916,1290],{"class":693},[679,145918,145919],{"class":681,"line":3134},[679,145920,142274],{"class":693},[679,145922,145923,145925,145927,145929,145932],{"class":681,"line":3139},[679,145924,119944],{"class":693},[679,145926,9415],{"class":880},[679,145928,745],{"class":693},[679,145930,145931],{"class":689},"\"Method {} executed in {}ms\"",[679,145933,145934],{"class":693},", methodName, \n",[679,145936,145937,145940,145942,145944,145946],{"class":681,"line":3144},[679,145938,145939],{"class":693}," System.",[679,145941,145827],{"class":880},[679,145943,6700],{"class":693},[679,145945,6453],{"class":685},[679,145947,145948],{"class":693}," startTime);\n",[679,145950,145951],{"class":681,"line":3149},[679,145952,142274],{"class":693},[679,145954,145955,145957],{"class":681,"line":3169},[679,145956,9444],{"class":685},[679,145958,15243],{"class":693},[679,145960,145961],{"class":681,"line":3185},[679,145962,985],{"class":693},[679,145964,145965],{"class":681,"line":3194},[679,145966,119642],{"class":693},[679,145968,145969,145971,145973,145976,145979,145981],{"class":681,"line":3199},[679,145970,9232],{"class":685},[679,145972,6095],{"class":685},[679,145974,145975],{"class":880}," logUsage",[679,145977,145978],{"class":693},"(Usage ",[679,145980,145910],{"class":2099},[679,145982,4390],{"class":693},[679,145984,145985,145987,145989,145991,145994],{"class":681,"line":3212},[679,145986,119944],{"class":693},[679,145988,9415],{"class":880},[679,145990,745],{"class":693},[679,145992,145993],{"class":689},"\"Token Usage - Prompt: {}, Completion: {}, Total: {}\"",[679,145995,144058],{"class":693},[679,145997,145998,146001,146004],{"class":681,"line":3217},[679,145999,146000],{"class":693}," usage.",[679,146002,146003],{"class":880},"promptTokens",[679,146005,144863],{"class":693},[679,146007,146008,146010,146013],{"class":681,"line":3222},[679,146009,146000],{"class":693},[679,146011,146012],{"class":880},"completionTokens",[679,146014,144863],{"class":693},[679,146016,146017,146019,146022],{"class":681,"line":3227},[679,146018,146000],{"class":693},[679,146020,146021],{"class":880},"totalTokens",[679,146023,9431],{"class":693},[679,146025,146026],{"class":681,"line":3232},[679,146027,985],{"class":693},[679,146029,146030],{"class":681,"line":3499},[679,146031,996],{"class":693},[651,146033,146034],{},"Don't forget to enable AOP in your application:",[669,146036,146038],{"className":4107,"code":146037,"language":4109,"meta":674,"style":674},"@SpringBootApplication\n@EnableAspectJAutoProxy\npublic class Application {\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n}\n",[676,146039,146040,146046,146053,146063,146083,146091,146095],{"__ignoreMap":674},[679,146041,146042,146044],{"class":681,"line":682},[679,146043,4116],{"class":693},[679,146045,6068],{"class":685},[679,146047,146048,146050],{"class":681,"line":790},[679,146049,4116],{"class":693},[679,146051,146052],{"class":685},"EnableAspectJAutoProxy\n",[679,146054,146055,146057,146059,146061],{"class":681,"line":892},[679,146056,6073],{"class":685},[679,146058,4512],{"class":685},[679,146060,16878],{"class":880},[679,146062,884],{"class":693},[679,146064,146065,146067,146069,146071,146073,146075,146077,146079,146081],{"class":681,"line":901},[679,146066,6089],{"class":685},[679,146068,6092],{"class":685},[679,146070,6095],{"class":685},[679,146072,6098],{"class":880},[679,146074,745],{"class":693},[679,146076,4758],{"class":685},[679,146078,16901],{"class":693},[679,146080,6108],{"class":2099},[679,146082,4390],{"class":693},[679,146084,146085,146087,146089],{"class":681,"line":909},[679,146086,6115],{"class":693},[679,146088,6118],{"class":880},[679,146090,16914],{"class":693},[679,146092,146093],{"class":681,"line":918},[679,146094,985],{"class":693},[679,146096,146097],{"class":681,"line":935},[679,146098,996],{"class":693},[4542,146100,146102],{"id":146101},"best-practices-for-token-management","Best Practices for Token Management",[27665,146104,146105,146111,146117,146123],{},[5332,146106,146107,146110],{},[2939,146108,146109],{},"Monitor Token Usage",": Regularly review token usage to optimize costs and prevent unexpected charges.",[5332,146112,146113,146116],{},[2939,146114,146115],{},"Use Custom Response Types",": Create domain-specific response types that include only the information you need.",[5332,146118,146119,146122],{},[2939,146120,146121],{},"Implement Logging",": Use AOP or similar patterns to consistently track token usage across your application.",[5332,146124,146125,146128],{},[2939,146126,146127],{},"Set Limits",": Consider implementing token limits for different types of requests to prevent runaway costs.",[4542,146130,9042],{"id":9041},[651,146132,146133],{},"Understanding and tracking token usage is essential when building applications with LLMs. Spring AI makes it straightforward to integrate with various AI providers, and by combining it with Spring's powerful features like AOP, you can create robust token tracking mechanisms.",[651,146135,146136],{},"Remember that tokens directly impact your costs, so implementing proper tracking and monitoring is not just a technical consideration but a business requirement. The approaches shown here will help you build more cost-effective and efficient AI-powered applications.",[651,146138,146139],{},"Have you implemented token tracking in your Spring AI applications? What challenges have you faced? Share your experiences in the comments below!",[786,146141,146142],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":146144},[146145,146146,146147,146148,146149,146150],{"id":145006,"depth":790,"text":145007},{"id":145166,"depth":790,"text":145167},{"id":145512,"depth":790,"text":145513},{"id":145682,"depth":790,"text":145683},{"id":146101,"depth":790,"text":146102},{"id":9041,"depth":790,"text":9042},"Learn how to track and manage token usage in Spring AI applications, including implementing custom response types and automated logging with AOP.",{"slug":146153,"date":146154,"published":797,"author":798,"tags":146155,"video":146156,"keywords":146157},"spring-ai-tokens","2024-11-01T17:00:00.000Z",[123549],"https://www.youtube.com/embed/ZUCVRppXPSc","spring boot, spring framework, spring ai, tokens, llm, claude, chatgpt, java, spring boot, aop",{"title":36,"description":146151},"blog/2024/11/01/spring-ai-tokens","yFCKjvb5VAN69Ya-5gUvn_MXQHqJc1FHifhDBes2aU4",{"id":146162,"title":33,"body":146163,"description":147055,"extension":793,"meta":147056,"navigation":797,"path":34,"seo":147063,"stem":147064,"__hash__":147065},"content/blog/2024/11/05/rest-client-oauth2-support.md",{"type":648,"value":146164,"toc":147043},[146165,146168,146172,146175,146179,146182,146202,146205,146219,146225,146229,146232,146236,146239,146352,146356,146359,146455,146462,146466,146473,146656,146660,146667,146670,146792,146799,146802,146857,146861,146864,147012,147014,147017,147020,147034,147037,147040],[651,146166,146167],{},"Spring Security 6.4 introduces native OAuth2 support for the RestClient, making it easier than ever to secure your service-to-service communications. If you've been using RestClient since its introduction in Spring Framework 6.1, you'll appreciate this streamlined approach to OAuth2 integration. Let's explore how this new feature simplifies secure API interactions.",[4542,146169,146171],{"id":146170},"the-evolution-of-http-clients-in-spring","The Evolution of HTTP Clients in Spring",[651,146173,146174],{},"The RestClient, introduced in Spring Framework 6.1, provided developers with a fluent, synchronous API for HTTP communications. While it quickly gained popularity for its clean interface and lack of reactive dependencies, implementing OAuth2 security required custom solutions. Spring Security 6.4 addresses this gap with built-in OAuth2 support.",[4542,146176,146178],{"id":146177},"understanding-the-architecture","Understanding the Architecture",[651,146180,146181],{},"Before diving into the implementation, let's understand the key components in a typical OAuth2 setup:",[27665,146183,146184,146190,146196],{},[5332,146185,146186,146189],{},[2939,146187,146188],{},"Authorization Server"," - Issues and validates OAuth2 tokens",[5332,146191,146192,146195],{},[2939,146193,146194],{},"Resource Server"," - Hosts protected resources requiring OAuth2 authentication",[5332,146197,146198,146201],{},[2939,146199,146200],{},"Client Application"," - Makes authenticated requests using OAuth2 tokens",[651,146203,146204],{},"Here's how these components interact in a typical flow:",[27665,146206,146207,146210,146213,146216],{},[5332,146208,146209],{},"The client requests an access token from the Authorization Server",[5332,146211,146212],{},"The Authorization Server validates credentials and returns a token",[5332,146214,146215],{},"The client includes this token when requesting protected resources",[5332,146217,146218],{},"The Resource Server validates the token before serving the request",[651,146220,146221],{},[660,146222],{"alt":146223,"src":146224},"OAuth2 Architecture","/images/blog/2024/11/05/oauth2_architecture.png",[4542,146226,146228],{"id":146227},"implementing-oauth2-with-restclient","Implementing OAuth2 with RestClient",[651,146230,146231],{},"Let's implement this using Spring Security 6.4's new RestClient OAuth2 support. We'll create a client application that securely accesses protected resources.",[5909,146233,146235],{"id":146234},"client-configuration","Client Configuration",[651,146237,146238],{},"First, configure your OAuth2 client properties:",[669,146240,146242],{"className":67476,"code":146241,"language":56308,"meta":674,"style":674},"spring:\n security:\n oauth2:\n client:\n registration:\n golf-client:\n provider: spring\n client-id: golf-client\n client-secret: golf-secret\n authorization-grant-type: client_credentials\n scope: read\n provider:\n spring:\n token-uri: http://localhost:9000/oauth2/token\n",[676,146243,146244,146250,146256,146262,146268,146274,146281,146291,146300,146309,146319,146328,146335,146342],{"__ignoreMap":674},[679,146245,146246,146248],{"class":681,"line":682},[679,146247,7055],{"class":4508},[679,146249,3327],{"class":693},[679,146251,146252,146254],{"class":681,"line":790},[679,146253,142571],{"class":4508},[679,146255,3327],{"class":693},[679,146257,146258,146260],{"class":681,"line":892},[679,146259,142578],{"class":4508},[679,146261,3327],{"class":693},[679,146263,146264,146266],{"class":681,"line":901},[679,146265,142585],{"class":4508},[679,146267,3327],{"class":693},[679,146269,146270,146272],{"class":681,"line":909},[679,146271,142592],{"class":4508},[679,146273,3327],{"class":693},[679,146275,146276,146279],{"class":681,"line":918},[679,146277,146278],{"class":4508}," golf-client",[679,146280,3327],{"class":693},[679,146282,146283,146286,146288],{"class":681,"line":935},[679,146284,146285],{"class":4508}," provider",[679,146287,4282],{"class":693},[679,146289,146290],{"class":689},"spring\n",[679,146292,146293,146295,146297],{"class":681,"line":944},[679,146294,142606],{"class":4508},[679,146296,4282],{"class":693},[679,146298,146299],{"class":689},"golf-client\n",[679,146301,146302,146304,146306],{"class":681,"line":959},[679,146303,142616],{"class":4508},[679,146305,4282],{"class":693},[679,146307,146308],{"class":689},"golf-secret\n",[679,146310,146311,146314,146316],{"class":681,"line":964},[679,146312,146313],{"class":4508}," authorization-grant-type",[679,146315,4282],{"class":693},[679,146317,146318],{"class":689},"client_credentials\n",[679,146320,146321,146323,146325],{"class":681,"line":977},[679,146322,142626],{"class":4508},[679,146324,4282],{"class":693},[679,146326,146327],{"class":689},"read\n",[679,146329,146330,146333],{"class":681,"line":982},[679,146331,146332],{"class":4508}," provider",[679,146334,3327],{"class":693},[679,146336,146337,146340],{"class":681,"line":988},[679,146338,146339],{"class":4508}," spring",[679,146341,3327],{"class":693},[679,146343,146344,146347,146349],{"class":681,"line":993},[679,146345,146346],{"class":4508}," token-uri",[679,146348,4282],{"class":693},[679,146350,146351],{"class":689},"http://localhost:9000/oauth2/token\n",[5909,146353,146355],{"id":146354},"restclient-setup","RestClient Setup",[651,146357,146358],{},"Create a RestClient bean with OAuth2 support:",[669,146360,146362],{"className":4107,"code":146361,"language":4109,"meta":674,"style":674},"@Configuration\npublic class ClientConfig {\n\n @Bean\n RestClient restClient(RestClient.Builder builder, OAuth2ClientHttpRequestInterceptor interceptor) {\n return builder\n .baseUrl(\"http://localhost:8081\")\n .requestInterceptor(interceptor)\n .build();\n }\n}\n",[676,146363,146364,146370,146381,146385,146391,146411,146417,146430,146439,146447,146451],{"__ignoreMap":674},[679,146365,146366,146368],{"class":681,"line":682},[679,146367,4116],{"class":693},[679,146369,6212],{"class":685},[679,146371,146372,146374,146376,146379],{"class":681,"line":790},[679,146373,6073],{"class":685},[679,146375,4512],{"class":685},[679,146377,146378],{"class":880}," ClientConfig",[679,146380,884],{"class":693},[679,146382,146383],{"class":681,"line":892},[679,146384,889],{"emptyLinePlaceholder":797},[679,146386,146387,146389],{"class":681,"line":901},[679,146388,6872],{"class":693},[679,146390,16929],{"class":685},[679,146392,146393,146396,146399,146401,146403,146406,146409],{"class":681,"line":909},[679,146394,146395],{"class":693}," RestClient ",[679,146397,146398],{"class":880},"restClient",[679,146400,117853],{"class":693},[679,146402,90934],{"class":2099},[679,146404,146405],{"class":693},", OAuth2ClientHttpRequestInterceptor ",[679,146407,146408],{"class":2099},"interceptor",[679,146410,4390],{"class":693},[679,146412,146413,146415],{"class":681,"line":918},[679,146414,9444],{"class":685},[679,146416,119021],{"class":693},[679,146418,146419,146421,146423,146425,146428],{"class":681,"line":935},[679,146420,40148],{"class":693},[679,146422,100528],{"class":880},[679,146424,745],{"class":693},[679,146426,146427],{"class":689},"\"http://localhost:8081\"",[679,146429,1339],{"class":693},[679,146431,146432,146434,146436],{"class":681,"line":944},[679,146433,40148],{"class":693},[679,146435,143669],{"class":880},[679,146437,146438],{"class":693},"(interceptor)\n",[679,146440,146441,146443,146445],{"class":681,"line":959},[679,146442,40148],{"class":693},[679,146444,23612],{"class":880},[679,146446,9317],{"class":693},[679,146448,146449],{"class":681,"line":964},[679,146450,985],{"class":693},[679,146452,146453],{"class":681,"line":977},[679,146454,996],{"class":693},[651,146456,146457,146458,146461],{},"The new ",[676,146459,146460],{},"OAuth2ClientHttpRequestInterceptor"," handles token acquisition and request enhancement automatically.",[5909,146463,146465],{"id":146464},"making-authenticated-requests","Making Authenticated Requests",[651,146467,146468,146469,146472],{},"Use the ",[676,146470,146471],{},"attributes()"," method to specify OAuth2 details:",[669,146474,146476],{"className":4107,"code":146475,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api\")\npublic class LessonsController {\n\n private final RestClient restClient;\n\n public LessonsController(RestClient restClient) {\n this.restClient = restClient;\n }\n\n @GetMapping(\"/lessons\")\n public List\u003CLesson> getLessons() {\n return restClient.get()\n .uri(\"/lessons\")\n .attributes(OAuth2ClientAttributesUtils.clientRegistrationId(\"golf-client\"))\n .retrieve()\n .body(new ParameterizedTypeReference\u003C>() {});\n }\n}\n",[676,146477,146478,146484,146497,146508,146512,146520,146524,146537,146548,146552,146556,146569,146585,146595,146607,146627,146635,146648,146652],{"__ignoreMap":674},[679,146479,146480,146482],{"class":681,"line":682},[679,146481,4116],{"class":693},[679,146483,9212],{"class":685},[679,146485,146486,146488,146490,146492,146495],{"class":681,"line":790},[679,146487,4116],{"class":693},[679,146489,9275],{"class":685},[679,146491,745],{"class":693},[679,146493,146494],{"class":689},"\"/api\"",[679,146496,1339],{"class":693},[679,146498,146499,146501,146503,146506],{"class":681,"line":892},[679,146500,6073],{"class":685},[679,146502,4512],{"class":685},[679,146504,146505],{"class":880}," LessonsController",[679,146507,884],{"class":693},[679,146509,146510],{"class":681,"line":901},[679,146511,889],{"emptyLinePlaceholder":797},[679,146513,146514,146516,146518],{"class":681,"line":909},[679,146515,9232],{"class":685},[679,146517,12768],{"class":685},[679,146519,113669],{"class":693},[679,146521,146522],{"class":681,"line":918},[679,146523,889],{"emptyLinePlaceholder":797},[679,146525,146526,146528,146530,146533,146535],{"class":681,"line":935},[679,146527,6089],{"class":685},[679,146529,146505],{"class":880},[679,146531,146532],{"class":693},"(RestClient ",[679,146534,146398],{"class":2099},[679,146536,4390],{"class":693},[679,146538,146539,146541,146543,146545],{"class":681,"line":944},[679,146540,7862],{"class":931},[679,146542,119016],{"class":693},[679,146544,686],{"class":685},[679,146546,146547],{"class":693}," restClient;\n",[679,146549,146550],{"class":681,"line":959},[679,146551,985],{"class":693},[679,146553,146554],{"class":681,"line":964},[679,146555,889],{"emptyLinePlaceholder":797},[679,146557,146558,146560,146562,146564,146567],{"class":681,"line":977},[679,146559,6872],{"class":693},[679,146561,20852],{"class":685},[679,146563,745],{"class":693},[679,146565,146566],{"class":689},"\"/lessons\"",[679,146568,1339],{"class":693},[679,146570,146571,146573,146575,146578,146580,146583],{"class":681,"line":982},[679,146572,6089],{"class":685},[679,146574,87217],{"class":693},[679,146576,146577],{"class":685},"Lesson",[679,146579,20881],{"class":693},[679,146581,146582],{"class":880},"getLessons",[679,146584,2667],{"class":693},[679,146586,146587,146589,146591,146593],{"class":681,"line":988},[679,146588,9444],{"class":685},[679,146590,113761],{"class":693},[679,146592,7626],{"class":880},[679,146594,17545],{"class":693},[679,146596,146597,146599,146601,146603,146605],{"class":681,"line":993},[679,146598,40148],{"class":693},[679,146600,4836],{"class":880},[679,146602,745],{"class":693},[679,146604,146566],{"class":689},[679,146606,1339],{"class":693},[679,146608,146609,146611,146614,146617,146620,146622,146625],{"class":681,"line":2129},[679,146610,40148],{"class":693},[679,146612,146613],{"class":880},"attributes",[679,146615,146616],{"class":693},"(OAuth2ClientAttributesUtils.",[679,146618,146619],{"class":880},"clientRegistrationId",[679,146621,745],{"class":693},[679,146623,146624],{"class":689},"\"golf-client\"",[679,146626,40143],{"class":693},[679,146628,146629,146631,146633],{"class":681,"line":2140},[679,146630,40148],{"class":693},[679,146632,105458],{"class":880},[679,146634,17545],{"class":693},[679,146636,146637,146639,146641,146643,146645],{"class":681,"line":2145},[679,146638,40148],{"class":693},[679,146640,3006],{"class":880},[679,146642,745],{"class":693},[679,146644,8930],{"class":685},[679,146646,146647],{"class":693}," ParameterizedTypeReference\u003C>() {});\n",[679,146649,146650],{"class":681,"line":2154},[679,146651,985],{"class":693},[679,146653,146654],{"class":681,"line":2159},[679,146655,996],{"class":693},[4542,146657,146659],{"id":146658},"common-pitfalls-and-solutions","Common Pitfalls and Solutions",[27665,146661,146662],{},[5332,146663,146664],{},[2939,146665,146666],{},"Missing Security Configuration",[651,146668,146669],{},"Without proper security configuration, you might get redirected to form login. Add this configuration:",[669,146671,146673],{"className":4107,"code":146672,"language":4109,"meta":674,"style":674},"@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n @Bean\n SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n return http\n .formLogin(AbstractHttpConfigurer::disable)\n .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())\n .csrf(AbstractHttpConfigurer::disable)\n .build();\n }\n}\n",[676,146674,146675,146681,146687,146697,146701,146707,146723,146729,146743,146764,146776,146784,146788],{"__ignoreMap":674},[679,146676,146677,146679],{"class":681,"line":682},[679,146678,4116],{"class":693},[679,146680,6212],{"class":685},[679,146682,146683,146685],{"class":681,"line":790},[679,146684,4116],{"class":693},[679,146686,89474],{"class":685},[679,146688,146689,146691,146693,146695],{"class":681,"line":892},[679,146690,6073],{"class":685},[679,146692,4512],{"class":685},[679,146694,89483],{"class":880},[679,146696,884],{"class":693},[679,146698,146699],{"class":681,"line":901},[679,146700,889],{"emptyLinePlaceholder":797},[679,146702,146703,146705],{"class":681,"line":909},[679,146704,6872],{"class":693},[679,146706,16929],{"class":685},[679,146708,146709,146711,146713,146715,146717,146719,146721],{"class":681,"line":918},[679,146710,107584],{"class":693},[679,146712,89505],{"class":880},[679,146714,89508],{"class":693},[679,146716,89511],{"class":2099},[679,146718,2378],{"class":693},[679,146720,9580],{"class":685},[679,146722,10466],{"class":693},[679,146724,146725,146727],{"class":681,"line":935},[679,146726,9444],{"class":685},[679,146728,89524],{"class":693},[679,146730,146731,146733,146735,146738,146740],{"class":681,"line":944},[679,146732,40148],{"class":693},[679,146734,95511],{"class":880},[679,146736,146737],{"class":693},"(AbstractHttpConfigurer",[679,146739,90007],{"class":685},[679,146741,146742],{"class":693},"disable)\n",[679,146744,146745,146747,146749,146751,146753,146756,146758,146760,146762],{"class":681,"line":959},[679,146746,40148],{"class":693},[679,146748,95392],{"class":880},[679,146750,95465],{"class":693},[679,146752,16955],{"class":685},[679,146754,146755],{"class":693}," auth.",[679,146757,89569],{"class":880},[679,146759,10541],{"class":693},[679,146761,40151],{"class":880},[679,146763,40172],{"class":693},[679,146765,146766,146768,146770,146772,146774],{"class":681,"line":964},[679,146767,40148],{"class":693},[679,146769,89531],{"class":880},[679,146771,146737],{"class":693},[679,146773,90007],{"class":685},[679,146775,146742],{"class":693},[679,146777,146778,146780,146782],{"class":681,"line":977},[679,146779,40148],{"class":693},[679,146781,23612],{"class":880},[679,146783,9317],{"class":693},[679,146785,146786],{"class":681,"line":982},[679,146787,985],{"class":693},[679,146789,146790],{"class":681,"line":988},[679,146791,996],{"class":693},[27665,146793,146794],{"start":790},[5332,146795,146796],{},[2939,146797,146798],{},"Token Validation Issues",[651,146800,146801],{},"Ensure your Resource Server is configured to validate tokens:",[669,146803,146805],{"className":4107,"code":146804,"language":4109,"meta":674,"style":674},"spring:\n security:\n oauth2:\n resourceserver:\n jwt:\n issuer-uri: http://localhost:9000\n",[676,146806,146807,146813,146819,146825,146832,146839],{"__ignoreMap":674},[679,146808,146809,146811],{"class":681,"line":682},[679,146810,7055],{"class":693},[679,146812,3327],{"class":685},[679,146814,146815,146817],{"class":681,"line":790},[679,146816,142571],{"class":693},[679,146818,3327],{"class":685},[679,146820,146821,146823],{"class":681,"line":892},[679,146822,142578],{"class":693},[679,146824,3327],{"class":685},[679,146826,146827,146830],{"class":681,"line":901},[679,146828,146829],{"class":693}," resourceserver",[679,146831,3327],{"class":685},[679,146833,146834,146837],{"class":681,"line":909},[679,146835,146836],{"class":693}," jwt",[679,146838,3327],{"class":685},[679,146840,146841,146844,146846,146848,146850,146852,146854],{"class":681,"line":918},[679,146842,146843],{"class":693}," issuer",[679,146845,6453],{"class":685},[679,146847,4836],{"class":693},[679,146849,2391],{"class":685},[679,146851,122723],{"class":693},[679,146853,2391],{"class":685},[679,146855,146856],{"class":1400},"//localhost:9000\n",[4542,146858,146860],{"id":146859},"testing-your-implementation","Testing Your Implementation",[651,146862,146863],{},"Here's a simple test to verify your OAuth2 integration:",[669,146865,146867],{"className":4107,"code":146866,"language":4109,"meta":674,"style":674},"@SpringBootTest\nclass LessonsControllerTest {\n\n @Autowired\n private TestRestTemplate restTemplate;\n\n @Test\n void whenRequestingLessons_thenReturnsAuthorizedResponse() {\n ResponseEntity\u003CList\u003CLesson>> response = restTemplate.exchange(\n \"/api/lessons\",\n HttpMethod.GET,\n null,\n new ParameterizedTypeReference\u003C>() {}\n );\n \n assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);\n assertThat(response.getBody()).isNotEmpty();\n }\n}\n",[676,146868,146869,146875,146884,146888,146894,146901,146905,146911,146920,146940,146947,146952,146959,146966,146970,146974,146989,147004,147008],{"__ignoreMap":674},[679,146870,146871,146873],{"class":681,"line":682},[679,146872,4116],{"class":693},[679,146874,144726],{"class":685},[679,146876,146877,146879,146882],{"class":681,"line":790},[679,146878,877],{"class":685},[679,146880,146881],{"class":880}," LessonsControllerTest",[679,146883,884],{"class":693},[679,146885,146886],{"class":681,"line":892},[679,146887,889],{"emptyLinePlaceholder":797},[679,146889,146890,146892],{"class":681,"line":901},[679,146891,6872],{"class":693},[679,146893,9257],{"class":685},[679,146895,146896,146898],{"class":681,"line":909},[679,146897,9232],{"class":685},[679,146899,146900],{"class":693}," TestRestTemplate restTemplate;\n",[679,146902,146903],{"class":681,"line":918},[679,146904,889],{"emptyLinePlaceholder":797},[679,146906,146907,146909],{"class":681,"line":935},[679,146908,6872],{"class":693},[679,146910,73087],{"class":685},[679,146912,146913,146915,146918],{"class":681,"line":944},[679,146914,3314],{"class":685},[679,146916,146917],{"class":880}," whenRequestingLessons_thenReturnsAuthorizedResponse",[679,146919,2667],{"class":693},[679,146921,146922,146925,146927,146930,146932,146935,146938],{"class":681,"line":959},[679,146923,146924],{"class":693}," ResponseEntity\u003CList\u003C",[679,146926,146577],{"class":685},[679,146928,146929],{"class":693},">> response ",[679,146931,686],{"class":685},[679,146933,146934],{"class":693}," restTemplate.",[679,146936,146937],{"class":880},"exchange",[679,146939,21337],{"class":693},[679,146941,146942,146945],{"class":681,"line":964},[679,146943,146944],{"class":689}," \"/api/lessons\"",[679,146946,12083],{"class":693},[679,146948,146949],{"class":681,"line":977},[679,146950,146951],{"class":693}," HttpMethod.GET,\n",[679,146953,146954,146957],{"class":681,"line":982},[679,146955,146956],{"class":931}," null",[679,146958,12083],{"class":693},[679,146960,146961,146963],{"class":681,"line":988},[679,146962,84746],{"class":685},[679,146964,146965],{"class":693}," ParameterizedTypeReference\u003C>() {}\n",[679,146967,146968],{"class":681,"line":993},[679,146969,21400],{"class":693},[679,146971,146972],{"class":681,"line":2129},[679,146973,142274],{"class":693},[679,146975,146976,146978,146980,146982,146984,146986],{"class":681,"line":2140},[679,146977,144936],{"class":880},[679,146979,124023],{"class":693},[679,146981,118004],{"class":880},[679,146983,90621],{"class":693},[679,146985,144942],{"class":880},[679,146987,146988],{"class":693},"(HttpStatus.OK);\n",[679,146990,146991,146993,146995,146997,146999,147002],{"class":681,"line":2145},[679,146992,144936],{"class":880},[679,146994,124023],{"class":693},[679,146996,143918],{"class":880},[679,146998,90621],{"class":693},[679,147000,147001],{"class":880},"isNotEmpty",[679,147003,9317],{"class":693},[679,147005,147006],{"class":681,"line":2154},[679,147007,985],{"class":693},[679,147009,147010],{"class":681,"line":2159},[679,147011,996],{"class":693},[4542,147013,9042],{"id":9041},[651,147015,147016],{},"Spring Security 6.4's RestClient OAuth2 support significantly simplifies secure service-to-service communication. The integration feels natural and follows Spring's philosophy of convention over configuration.",[651,147018,147019],{},"For developers currently using custom OAuth2 solutions with RestClient, migrating to this official support offers several advantages:",[5316,147021,147022,147025,147028,147031],{},[5332,147023,147024],{},"Reduced boilerplate code`",[5332,147026,147027],{},"Automatic token management`",[5332,147029,147030],{},"Consistent security configuration",[5332,147032,147033],{},"Better integration with Spring Security's features",[651,147035,147036],{},"Ready to try it yourself? Start by upgrading to Spring Security 6.4 and following the implementation pattern shown above. Remember to check the official Spring documentation for the most up-to-date details and best practices.",[651,147038,147039],{},"Have you already implemented OAuth2 with RestClient? How does this new approach compare to your current solution? Share your thoughts and experiences in the comments below!",[786,147041,147042],{},"html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}",{"title":674,"searchDepth":790,"depth":790,"links":147044},[147045,147046,147047,147052,147053,147054],{"id":146170,"depth":790,"text":146171},{"id":146177,"depth":790,"text":146178},{"id":146227,"depth":790,"text":146228,"children":147048},[147049,147050,147051],{"id":146234,"depth":892,"text":146235},{"id":146354,"depth":892,"text":146355},{"id":146464,"depth":892,"text":146465},{"id":146658,"depth":790,"text":146659},{"id":146859,"depth":790,"text":146860},{"id":9041,"depth":790,"text":9042},"Learn how to implement OAuth2 authentication in your Spring applications using the new RestClient OAuth2 support in Spring Security 6.4. This guide covers architecture, implementation, and best practices for secure service-to-service communication.",{"slug":147057,"date":147058,"published":797,"author":798,"tags":147059,"video":147060,"github":147061,"keywords":147062},"rest-client-oauth2-support","2024-11-05T17:00:00.000Z",[7077,23988],"https://www.youtube.com/embed/nFKcJDpUuZ8","https://github.com/danvega/golf-scheduler","spring security, oauth2, restclient, spring boot, java, authentication, authorization, security",{"title":33,"description":147055},"blog/2024/11/05/rest-client-oauth2-support","zBX5IcJgNIOh2uw_i5UiEKJ9M4H3YEIWZLFLkaJesRE",{"id":147067,"title":30,"body":147068,"description":147806,"extension":793,"meta":147807,"navigation":797,"path":31,"seo":147814,"stem":147815,"__hash__":147816},"content/blog/2024/11/08/spring-data-jpa-query-by-example.md",{"type":648,"value":147069,"toc":147797},[147070,147073,147077,147080,147082,147210,147213,147217,147224,147273,147276,147298,147302,147309,147461,147464,147475,147479,147482,147688,147691,147695,147698,147712,147715,147729,147731,147773,147775,147778,147789,147792,147795],[651,147071,147072],{},"Have you ever found yourself writing countless repository methods to handle different search combinations in your Spring Data JPA applications? If so, Query By Example (QBE) might be the solution you've been looking for. This powerful feature allows you to create dynamic queries using domain object instances as templates, significantly reducing boilerplate code while maintaining type safety.",[4542,147074,147076],{"id":147075},"understanding-query-by-example","Understanding Query By Example",[651,147078,147079],{},"Query By Example provides an intuitive API for dynamic query creation. Instead of writing multiple repository methods or complex specifications, you create a sample instance of your entity (called a probe) with the properties you want to match. Spring Data then uses this probe to generate the appropriate query.",[651,147081,73042],{},[669,147083,147085],{"className":4107,"code":147084,"language":4109,"meta":674,"style":674},"@Service\npublic class EmployeeService {\n \n private final EmployeeRepository repository;\n \n public List\u003CEmployee> findEmployees(String department, String position) {\n Employee probe = new Employee();\n probe.setDepartment(department);\n probe.setPosition(position);\n \n return repository.findAll(Example.of(probe));\n }\n}\n",[676,147086,147087,147093,147104,147108,147117,147121,147147,147161,147172,147182,147186,147202,147206],{"__ignoreMap":674},[679,147088,147089,147091],{"class":681,"line":682},[679,147090,4116],{"class":693},[679,147092,9486],{"class":685},[679,147094,147095,147097,147099,147102],{"class":681,"line":790},[679,147096,6073],{"class":685},[679,147098,4512],{"class":685},[679,147100,147101],{"class":880}," EmployeeService",[679,147103,884],{"class":693},[679,147105,147106],{"class":681,"line":892},[679,147107,119642],{"class":693},[679,147109,147110,147112,147114],{"class":681,"line":901},[679,147111,9232],{"class":685},[679,147113,12768],{"class":685},[679,147115,147116],{"class":693}," EmployeeRepository repository;\n",[679,147118,147119],{"class":681,"line":909},[679,147120,119642],{"class":693},[679,147122,147123,147125,147127,147130,147132,147135,147137,147140,147142,147145],{"class":681,"line":918},[679,147124,6089],{"class":685},[679,147126,87217],{"class":693},[679,147128,147129],{"class":685},"Employee",[679,147131,20881],{"class":693},[679,147133,147134],{"class":880},"findEmployees",[679,147136,11400],{"class":693},[679,147138,147139],{"class":2099},"department",[679,147141,20006],{"class":693},[679,147143,147144],{"class":2099},"position",[679,147146,4390],{"class":693},[679,147148,147149,147152,147154,147156,147159],{"class":681,"line":935},[679,147150,147151],{"class":693}," Employee probe ",[679,147153,686],{"class":685},[679,147155,2054],{"class":685},[679,147157,147158],{"class":880}," Employee",[679,147160,9317],{"class":693},[679,147162,147163,147166,147169],{"class":681,"line":944},[679,147164,147165],{"class":693}," probe.",[679,147167,147168],{"class":880},"setDepartment",[679,147170,147171],{"class":693},"(department);\n",[679,147173,147174,147176,147179],{"class":681,"line":959},[679,147175,147165],{"class":693},[679,147177,147178],{"class":880},"setPosition",[679,147180,147181],{"class":693},"(position);\n",[679,147183,147184],{"class":681,"line":964},[679,147185,142274],{"class":693},[679,147187,147188,147190,147192,147194,147197,147199],{"class":681,"line":977},[679,147189,9444],{"class":685},[679,147191,85606],{"class":693},[679,147193,34142],{"class":880},[679,147195,147196],{"class":693},"(Example.",[679,147198,16672],{"class":880},[679,147200,147201],{"class":693},"(probe));\n",[679,147203,147204],{"class":681,"line":982},[679,147205,985],{"class":693},[679,147207,147208],{"class":681,"line":988},[679,147209,996],{"class":693},[651,147211,147212],{},"This approach is particularly powerful when dealing with optional search parameters. No more concatenating SQL strings or building complex specifications!",[4542,147214,147216],{"id":147215},"setting-up-query-by-example","Setting Up Query By Example",[651,147218,147219,147220,147223],{},"To start using QBE, your repository needs to extend ",[676,147221,147222],{},"QueryByExampleExecutor"," along with the standard JPA repository:",[669,147225,147227],{"className":4107,"code":147226,"language":4109,"meta":674,"style":674},"public interface EmployeeRepository extends \n JpaRepository\u003CEmployee, Long>,\n QueryByExampleExecutor\u003CEmployee> {\n}\n",[676,147228,147229,147242,147258,147269],{"__ignoreMap":674},[679,147230,147231,147233,147235,147238,147240],{"class":681,"line":682},[679,147232,6073],{"class":685},[679,147234,6994],{"class":685},[679,147236,147237],{"class":880}," EmployeeRepository",[679,147239,2767],{"class":685},[679,147241,138414],{"class":693},[679,147243,147244,147247,147249,147251,147253,147255],{"class":681,"line":790},[679,147245,147246],{"class":880}," JpaRepository",[679,147248,4505],{"class":693},[679,147250,147129],{"class":685},[679,147252,2797],{"class":693},[679,147254,1094],{"class":685},[679,147256,147257],{"class":693},">,\n",[679,147259,147260,147263,147265,147267],{"class":681,"line":892},[679,147261,147262],{"class":880}," QueryByExampleExecutor",[679,147264,4505],{"class":693},[679,147266,147129],{"class":685},[679,147268,16397],{"class":693},[679,147270,147271],{"class":681,"line":901},[679,147272,996],{"class":693},[651,147274,147275],{},"That's it! You now have access to several methods for querying by example:",[5316,147277,147278,147283,147288,147293],{},[5332,147279,147280],{},[676,147281,147282],{},"findAll(Example\u003CS>)",[5332,147284,147285],{},[676,147286,147287],{},"findOne(Example\u003CS>)",[5332,147289,147290],{},[676,147291,147292],{},"exists(Example\u003CS>)",[5332,147294,147295],{},[676,147296,147297],{},"count(Example\u003CS>)",[4542,147299,147301],{"id":147300},"advanced-matching-strategies","Advanced Matching Strategies",[651,147303,147304,147305,147308],{},"While basic equality matching is useful, QBE really shines when you need more sophisticated matching strategies. The ",[676,147306,147307],{},"ExampleMatcher"," class allows you to customize how properties are matched:",[669,147310,147312],{"className":4107,"code":147311,"language":4109,"meta":674,"style":674},"@Service\npublic class EmployeeService {\n\n public List\u003CEmployee> searchEmployees(String name, String department) {\n Employee probe = new Employee();\n probe.setName(name);\n probe.setDepartment(department);\n \n ExampleMatcher matcher = ExampleMatcher.matching()\n .withStringMatcher(StringMatcher.CONTAINING)\n .withIgnoreCase()\n .withIgnoreNullValues();\n \n return repository.findAll(Example.of(probe, matcher));\n }\n}\n",[676,147313,147314,147320,147330,147334,147357,147369,147379,147387,147391,147406,147416,147425,147434,147438,147453,147457],{"__ignoreMap":674},[679,147315,147316,147318],{"class":681,"line":682},[679,147317,4116],{"class":693},[679,147319,9486],{"class":685},[679,147321,147322,147324,147326,147328],{"class":681,"line":790},[679,147323,6073],{"class":685},[679,147325,4512],{"class":685},[679,147327,147101],{"class":880},[679,147329,884],{"class":693},[679,147331,147332],{"class":681,"line":892},[679,147333,889],{"emptyLinePlaceholder":797},[679,147335,147336,147338,147340,147342,147344,147347,147349,147351,147353,147355],{"class":681,"line":901},[679,147337,6089],{"class":685},[679,147339,87217],{"class":693},[679,147341,147129],{"class":685},[679,147343,20881],{"class":693},[679,147345,147346],{"class":880},"searchEmployees",[679,147348,11400],{"class":693},[679,147350,16334],{"class":2099},[679,147352,20006],{"class":693},[679,147354,147139],{"class":2099},[679,147356,4390],{"class":693},[679,147358,147359,147361,147363,147365,147367],{"class":681,"line":909},[679,147360,147151],{"class":693},[679,147362,686],{"class":685},[679,147364,2054],{"class":685},[679,147366,147158],{"class":880},[679,147368,9317],{"class":693},[679,147370,147371,147373,147376],{"class":681,"line":918},[679,147372,147165],{"class":693},[679,147374,147375],{"class":880},"setName",[679,147377,147378],{"class":693},"(name);\n",[679,147380,147381,147383,147385],{"class":681,"line":935},[679,147382,147165],{"class":693},[679,147384,147168],{"class":880},[679,147386,147171],{"class":693},[679,147388,147389],{"class":681,"line":944},[679,147390,142274],{"class":693},[679,147392,147393,147396,147398,147401,147404],{"class":681,"line":959},[679,147394,147395],{"class":693}," ExampleMatcher matcher ",[679,147397,686],{"class":685},[679,147399,147400],{"class":693}," ExampleMatcher.",[679,147402,147403],{"class":880},"matching",[679,147405,17545],{"class":693},[679,147407,147408,147410,147413],{"class":681,"line":964},[679,147409,40148],{"class":693},[679,147411,147412],{"class":880},"withStringMatcher",[679,147414,147415],{"class":693},"(StringMatcher.CONTAINING)\n",[679,147417,147418,147420,147423],{"class":681,"line":977},[679,147419,40148],{"class":693},[679,147421,147422],{"class":880},"withIgnoreCase",[679,147424,17545],{"class":693},[679,147426,147427,147429,147432],{"class":681,"line":982},[679,147428,40148],{"class":693},[679,147430,147431],{"class":880},"withIgnoreNullValues",[679,147433,9317],{"class":693},[679,147435,147436],{"class":681,"line":988},[679,147437,145630],{"class":693},[679,147439,147440,147442,147444,147446,147448,147450],{"class":681,"line":993},[679,147441,9444],{"class":685},[679,147443,85606],{"class":693},[679,147445,34142],{"class":880},[679,147447,147196],{"class":693},[679,147449,16672],{"class":880},[679,147451,147452],{"class":693},"(probe, matcher));\n",[679,147454,147455],{"class":681,"line":2129},[679,147456,985],{"class":693},[679,147458,147459],{"class":681,"line":2140},[679,147460,996],{"class":693},[651,147462,147463],{},"This configuration:",[5316,147465,147466,147469,147472],{},[5332,147467,147468],{},"Matches string values using a LIKE expression",[5332,147470,147471],{},"Ignores case for string comparisons",[5332,147473,147474],{},"Ignores null values in the probe",[4542,147476,147478],{"id":147477},"real-world-use-case-search-forms","Real-world Use Case: Search Forms",[651,147480,147481],{},"One of the most common use cases for QBE is implementing search functionality with multiple optional fields. Consider a typical employee search form:",[669,147483,147485],{"className":4107,"code":147484,"language":4109,"meta":674,"style":674},"@RestController\n@RequestMapping(\"/api/employees\")\npublic class EmployeeController {\n\n private final EmployeeService employeeService;\n \n @PostMapping(\"/search\")\n public List\u003CEmployee> search(@RequestBody EmployeeSearchCriteria criteria) {\n Employee probe = new Employee();\n probe.setDepartment(criteria.getDepartment());\n probe.setPosition(criteria.getPosition());\n probe.setLocation(criteria.getLocation());\n \n ExampleMatcher matcher = ExampleMatcher.matching()\n .withStringMatcher(StringMatcher.CONTAINING)\n .withIgnoreCase()\n .withIgnoreNullValues();\n \n return employeeService.search(probe, matcher);\n }\n}\n",[676,147486,147487,147493,147506,147517,147521,147530,147534,147547,147571,147583,147597,147610,147624,147628,147640,147648,147656,147664,147668,147680,147684],{"__ignoreMap":674},[679,147488,147489,147491],{"class":681,"line":682},[679,147490,4116],{"class":693},[679,147492,9212],{"class":685},[679,147494,147495,147497,147499,147501,147504],{"class":681,"line":790},[679,147496,4116],{"class":693},[679,147498,9275],{"class":685},[679,147500,745],{"class":693},[679,147502,147503],{"class":689},"\"/api/employees\"",[679,147505,1339],{"class":693},[679,147507,147508,147510,147512,147515],{"class":681,"line":892},[679,147509,6073],{"class":685},[679,147511,4512],{"class":685},[679,147513,147514],{"class":880}," EmployeeController",[679,147516,884],{"class":693},[679,147518,147519],{"class":681,"line":901},[679,147520,889],{"emptyLinePlaceholder":797},[679,147522,147523,147525,147527],{"class":681,"line":909},[679,147524,9232],{"class":685},[679,147526,12768],{"class":685},[679,147528,147529],{"class":693}," EmployeeService employeeService;\n",[679,147531,147532],{"class":681,"line":918},[679,147533,119642],{"class":693},[679,147535,147536,147538,147540,147542,147545],{"class":681,"line":935},[679,147537,6872],{"class":693},[679,147539,91165],{"class":685},[679,147541,745],{"class":693},[679,147543,147544],{"class":689},"\"/search\"",[679,147546,1339],{"class":693},[679,147548,147549,147551,147553,147555,147557,147559,147561,147563,147566,147569],{"class":681,"line":944},[679,147550,6089],{"class":685},[679,147552,87217],{"class":693},[679,147554,147129],{"class":685},[679,147556,20881],{"class":693},[679,147558,66944],{"class":880},[679,147560,73246],{"class":693},[679,147562,96282],{"class":685},[679,147564,147565],{"class":693}," EmployeeSearchCriteria ",[679,147567,147568],{"class":2099},"criteria",[679,147570,4390],{"class":693},[679,147572,147573,147575,147577,147579,147581],{"class":681,"line":959},[679,147574,147151],{"class":693},[679,147576,686],{"class":685},[679,147578,2054],{"class":685},[679,147580,147158],{"class":880},[679,147582,9317],{"class":693},[679,147584,147585,147587,147589,147592,147595],{"class":681,"line":964},[679,147586,147165],{"class":693},[679,147588,147168],{"class":880},[679,147590,147591],{"class":693},"(criteria.",[679,147593,147594],{"class":880},"getDepartment",[679,147596,9431],{"class":693},[679,147598,147599,147601,147603,147605,147608],{"class":681,"line":977},[679,147600,147165],{"class":693},[679,147602,147178],{"class":880},[679,147604,147591],{"class":693},[679,147606,147607],{"class":880},"getPosition",[679,147609,9431],{"class":693},[679,147611,147612,147614,147617,147619,147622],{"class":681,"line":982},[679,147613,147165],{"class":693},[679,147615,147616],{"class":880},"setLocation",[679,147618,147591],{"class":693},[679,147620,147621],{"class":880},"getLocation",[679,147623,9431],{"class":693},[679,147625,147626],{"class":681,"line":988},[679,147627,142274],{"class":693},[679,147629,147630,147632,147634,147636,147638],{"class":681,"line":993},[679,147631,147395],{"class":693},[679,147633,686],{"class":685},[679,147635,147400],{"class":693},[679,147637,147403],{"class":880},[679,147639,17545],{"class":693},[679,147641,147642,147644,147646],{"class":681,"line":2129},[679,147643,40148],{"class":693},[679,147645,147412],{"class":880},[679,147647,147415],{"class":693},[679,147649,147650,147652,147654],{"class":681,"line":2140},[679,147651,40148],{"class":693},[679,147653,147422],{"class":880},[679,147655,17545],{"class":693},[679,147657,147658,147660,147662],{"class":681,"line":2145},[679,147659,40148],{"class":693},[679,147661,147431],{"class":880},[679,147663,9317],{"class":693},[679,147665,147666],{"class":681,"line":2154},[679,147667,145630],{"class":693},[679,147669,147670,147672,147675,147677],{"class":681,"line":2159},[679,147671,9444],{"class":685},[679,147673,147674],{"class":693}," employeeService.",[679,147676,66944],{"class":880},[679,147678,147679],{"class":693},"(probe, matcher);\n",[679,147681,147682],{"class":681,"line":2164},[679,147683,985],{"class":693},[679,147685,147686],{"class":681,"line":3134},[679,147687,996],{"class":693},[651,147689,147690],{},"With this approach, users can search using any combination of fields without requiring separate repository methods for each combination.",[4542,147692,147694],{"id":147693},"when-to-use-query-by-example","When to Use Query By Example",[651,147696,147697],{},"QBE is particularly well-suited for:",[5316,147699,147700,147703,147706,147709],{},[5332,147701,147702],{},"Search forms with multiple optional fields",[5332,147704,147705],{},"Rapid prototyping and development",[5332,147707,147708],{},"Simple equality-based queries",[5332,147710,147711],{},"Scenarios where search criteria are unknown at compile time",[651,147713,147714],{},"However, consider alternatives when you need:",[5316,147716,147717,147720,147723,147726],{},[5332,147718,147719],{},"Complex comparisons (>, \u003C, BETWEEN)",[5332,147721,147722],{},"OR conditions",[5332,147724,147725],{},"Complex JOIN operations",[5332,147727,147728],{},"Custom SQL functions",[4542,147730,74030],{"id":140692},[27665,147732,147733,147743,147753,147763],{},[5332,147734,147735,147738],{},[2939,147736,147737],{},"Always use ExampleMatcher for string fields",[5316,147739,147740],{},[5332,147741,147742],{},"String matching behavior can be unexpected without proper configuration",[5332,147744,147745,147748],{},[2939,147746,147747],{},"Consider null handling",[5316,147749,147750],{},[5332,147751,147752],{},"Decide whether null values should be ignored or matched explicitly",[5332,147754,147755,147758],{},[2939,147756,147757],{},"Keep it simple",[5316,147759,147760],{},[5332,147761,147762],{},"If you need complex conditions, consider using Specifications or native queries instead",[5332,147764,147765,147768],{},[2939,147766,147767],{},"Test thoroughly",[5316,147769,147770],{},[5332,147771,147772],{},"Different combinations of search criteria can produce unexpected results",[4542,147774,140744],{"id":140743},[651,147776,147777],{},"Query By Example is just one of many powerful features in Spring Data JPA. Consider exploring related topics:",[5316,147779,147780,147783,147786],{},[5332,147781,147782],{},"Specifications for complex queries",[5332,147784,147785],{},"Projections for optimized data retrieval",[5332,147787,147788],{},"Querydsl for type-safe dynamic queries",[651,147790,147791],{},"By incorporating QBE into your Spring Data JPA applications, you can write cleaner, more maintainable code while providing flexible search capabilities. The type safety and simplicity of the API make it an excellent choice for many common scenarios in enterprise applications.",[651,147793,147794],{},"Remember, the best approach depends on your specific requirements. QBE excels at simple, dynamic queries but shouldn't be forced into scenarios where other solutions might be more appropriate.",[786,147796,95713],{},{"title":674,"searchDepth":790,"depth":790,"links":147798},[147799,147800,147801,147802,147803,147804,147805],{"id":147075,"depth":790,"text":147076},{"id":147215,"depth":790,"text":147216},{"id":147300,"depth":790,"text":147301},{"id":147477,"depth":790,"text":147478},{"id":147693,"depth":790,"text":147694},{"id":140692,"depth":790,"text":74030},{"id":140743,"depth":790,"text":140744},"Learn how to simplify your Spring Data JPA queries using Query By Example (QBE) and create dynamic, type-safe queries without the boilerplate code.",{"slug":147808,"date":147809,"published":797,"author":798,"tags":147810,"video":147811,"github":147812,"keywords":147813},"spring-data-jpa-query-by-example","2024-11-08T17:00:00.000Z",[7077,84347],"https://www.youtube.com/embed/NGVWHdGNbiI","https://github.com/danvega/qbe","spring data jpa, query by example, dynamic queries, spring boot, java, database, search functionality",{"title":30,"description":147806},"blog/2024/11/08/spring-data-jpa-query-by-example","Vf2TZBYkz82jzPwv5Fn-zg4SwypwRh8jdjmdchLbz6c",{"id":147818,"title":27,"body":147819,"description":148680,"extension":793,"meta":148681,"navigation":797,"path":28,"seo":148687,"stem":148688,"__hash__":148689},"content/blog/2024/11/11/no-lombok.md",{"type":648,"value":147820,"toc":148663},[147821,147824,147828,147831,147835,147838,147883,147890,147894,147897,147911,147914,147918,147921,147964,147967,147971,147974,147988,147992,147995,148051,148054,148068,148071,148075,148078,148148,148155,148169,148173,148176,148180,148186,148256,148259,148263,148270,148549,148553,148556,148603,148607,148610,148627,148629,148632,148635,148652,148655,148657,148660],[651,147822,147823],{},"If you've been developing Java applications over the past decade, you've likely encountered or used Project Lombok. This library promised to eliminate boilerplate code through annotations, making Java development more concise and enjoyable. However, as Java has evolved and modern development practices have emerged, it's time to reevaluate Lombok's place in our toolbox.",[4542,147825,147827],{"id":147826},"the-hidden-costs-of-lombok","The Hidden Costs of Lombok",[651,147829,147830],{},"While Lombok's promise of reducing boilerplate code is appealing, it comes with significant drawbacks that aren't immediately apparent. Recent discussions in the Java community have highlighted serious concerns about Lombok's impact on development workflows and tool chains. Let's examine why many developers and teams are moving away from it.",[5909,147832,147834],{"id":147833},"breaking-ide-features","Breaking IDE Features",[651,147836,147837],{},"One of the most frustrating aspects of Lombok is its interference with IDE functionality. Consider this scenario:",[669,147839,147841],{"className":4107,"code":147840,"language":4109,"meta":674,"style":674},"@Builder\npublic class OrderService {\n private final OrderRepository repository;\n\n // ... service methods\n}\n",[676,147842,147843,147850,147861,147870,147874,147879],{"__ignoreMap":674},[679,147844,147845,147847],{"class":681,"line":682},[679,147846,4116],{"class":693},[679,147848,147849],{"class":685},"Builder\n",[679,147851,147852,147854,147856,147859],{"class":681,"line":790},[679,147853,6073],{"class":685},[679,147855,4512],{"class":685},[679,147857,147858],{"class":880}," OrderService",[679,147860,884],{"class":693},[679,147862,147863,147865,147867],{"class":681,"line":892},[679,147864,9232],{"class":685},[679,147866,12768],{"class":685},[679,147868,147869],{"class":693}," OrderRepository repository;\n",[679,147871,147872],{"class":681,"line":901},[679,147873,889],{"emptyLinePlaceholder":797},[679,147875,147876],{"class":681,"line":909},[679,147877,147878],{"class":1400}," // ... service methods\n",[679,147880,147881],{"class":681,"line":918},[679,147882,996],{"class":693},[651,147884,147885,147886,147889],{},"When you try to find all usages of this class's builder (using ",[676,147887,147888],{},"OrderService.builder()","), your IDE's \"Find Usages\" feature may fail to locate all instances. This breaks one of the most fundamental features developers rely on for code navigation and refactoring. As codebases grow, this limitation becomes increasingly problematic for maintenance and code reviews.",[5909,147891,147893],{"id":147892},"annotation-processor-conflicts","Annotation Processor Conflicts",[651,147895,147896],{},"Lombok's aggressive manipulation of the compilation process often conflicts with other annotation processors. This isn't just an inconvenience—it can prevent you from using important tools in your development stack:",[5316,147898,147899,147902,147905,147908],{},[5332,147900,147901],{},"MapStruct for object mapping",[5332,147903,147904],{},"QueryDSL for type-safe queries",[5332,147906,147907],{},"JPA Buddy for entity management",[5332,147909,147910],{},"Other custom annotation processors in your toolchain",[651,147912,147913],{},"The root cause? Lombok doesn't play by the rules of standard annotation processing. Instead, it hijacks the compilation process, leading to unpredictable interactions with other processors.",[5909,147915,147917],{"id":147916},"debugging-and-development-challenges","Debugging and Development Challenges",[651,147919,147920],{},"One of the most significant issues with Lombok is its impact on debugging. Since Lombok modifies the Abstract Syntax Tree (AST) at compile time, the code you write isn't the code that runs. This can lead to several problems:",[669,147922,147924],{"className":4107,"code":147923,"language":4109,"meta":674,"style":674},"@Data\npublic class Customer {\n private String name;\n private List\u003COrder> orders;\n}\n",[676,147925,147926,147932,147943,147949,147960],{"__ignoreMap":674},[679,147927,147928,147930],{"class":681,"line":682},[679,147929,4116],{"class":693},[679,147931,15305],{"class":685},[679,147933,147934,147936,147938,147941],{"class":681,"line":790},[679,147935,6073],{"class":685},[679,147937,4512],{"class":685},[679,147939,147940],{"class":880}," Customer",[679,147942,884],{"class":693},[679,147944,147945,147947],{"class":681,"line":892},[679,147946,9232],{"class":685},[679,147948,16319],{"class":693},[679,147950,147951,147953,147955,147957],{"class":681,"line":901},[679,147952,9232],{"class":685},[679,147954,87217],{"class":693},[679,147956,17055],{"class":685},[679,147958,147959],{"class":693},"> orders;\n",[679,147961,147962],{"class":681,"line":909},[679,147963,996],{"class":693},[651,147965,147966],{},"This seemingly simple class hides generated methods that you can't step through during debugging. When issues arise, you're left investigating code that doesn't exist in your source files.",[5909,147968,147970],{"id":147969},"ide-integration-and-tooling-issues","IDE Integration and Tooling Issues",[651,147972,147973],{},"Modern IDEs are powerful development tools, but Lombok can interfere with their functionality:",[5316,147975,147976,147979,147982,147985],{},[5332,147977,147978],{},"Find usages features become unreliable",[5332,147980,147981],{},"Code navigation breaks down",[5332,147983,147984],{},"Refactoring tools may not work as expected",[5332,147986,147987],{},"Auto-completion can be inconsistent",[5909,147989,147991],{"id":147990},"ast-manipulation-and-compilation-issues","AST Manipulation and Compilation Issues",[651,147993,147994],{},"Lombok's approach to code generation is fundamentally different from standard annotation processors. It directly manipulates the Abstract Syntax Tree (AST) during compilation, which leads to several serious issues:",[669,147996,147998],{"className":4107,"code":147997,"language":4109,"meta":674,"style":674},"@Data\n@Builder\npublic class Customer {\n private String name;\n private List\u003COrder> orders;\n\n // Imagine dozens of fields and complex relationships\n}\n",[676,147999,148000,148006,148012,148022,148028,148038,148042,148047],{"__ignoreMap":674},[679,148001,148002,148004],{"class":681,"line":682},[679,148003,4116],{"class":693},[679,148005,15305],{"class":685},[679,148007,148008,148010],{"class":681,"line":790},[679,148009,4116],{"class":693},[679,148011,147849],{"class":685},[679,148013,148014,148016,148018,148020],{"class":681,"line":892},[679,148015,6073],{"class":685},[679,148017,4512],{"class":685},[679,148019,147940],{"class":880},[679,148021,884],{"class":693},[679,148023,148024,148026],{"class":681,"line":901},[679,148025,9232],{"class":685},[679,148027,16319],{"class":693},[679,148029,148030,148032,148034,148036],{"class":681,"line":909},[679,148031,9232],{"class":685},[679,148033,87217],{"class":693},[679,148035,17055],{"class":685},[679,148037,147959],{"class":693},[679,148039,148040],{"class":681,"line":918},[679,148041,889],{"emptyLinePlaceholder":797},[679,148043,148044],{"class":681,"line":935},[679,148045,148046],{"class":1400}," // Imagine dozens of fields and complex relationships\n",[679,148048,148049],{"class":681,"line":944},[679,148050,996],{"class":693},[651,148052,148053],{},"When this code compiles:",[27665,148055,148056,148059,148062,148065],{},[5332,148057,148058],{},"Lombok modifies the AST before other processors see it",[5332,148060,148061],{},"This breaks incremental compilation, forcing full recompilation more often",[5332,148063,148064],{},"The generated code isn't visible until after compilation",[5332,148066,148067],{},"The actual bytecode might not match what you expect",[651,148069,148070],{},"The impact on build times can be significant, especially in larger projects. What's worse, these issues compound as your project grows.",[5909,148072,148074],{"id":148073},"debugger-alterations-and-java-version-compatibility","Debugger Alterations and Java Version Compatibility",[651,148076,148077],{},"Two of the most serious issues with Lombok affect core development workflows:",[27665,148079,148080,148096],{},[5332,148081,148082,2391,148085],{},[2939,148083,148084],{},"Debugger Interference",[5316,148086,148087,148090,148093],{},[5332,148088,148089],{},"You can't step into generated methods",[5332,148091,148092],{},"Breakpoints in generated code don't work as expected",[5332,148094,148095],{},"Stack traces can be confusing or misleading",[5332,148097,148098,2391,148101],{},[2939,148099,148100],{},"Java Version Compatibility",[669,148102,148104],{"className":4107,"code":148103,"language":4109,"meta":674,"style":674},"// This might work in Java 17...\n@Data\npublic class Product {\n private final BigDecimal price;\n}\n\n// But break in Java 21 due to Lombok's use of internal APIs\n",[676,148105,148106,148111,148117,148127,148135,148139,148143],{"__ignoreMap":674},[679,148107,148108],{"class":681,"line":682},[679,148109,148110],{"class":1400},"// This might work in Java 17...\n",[679,148112,148113,148115],{"class":681,"line":790},[679,148114,4116],{"class":693},[679,148116,15305],{"class":685},[679,148118,148119,148121,148123,148125],{"class":681,"line":892},[679,148120,6073],{"class":685},[679,148122,4512],{"class":685},[679,148124,97646],{"class":880},[679,148126,884],{"class":693},[679,148128,148129,148131,148133],{"class":681,"line":901},[679,148130,9232],{"class":685},[679,148132,12768],{"class":685},[679,148134,98891],{"class":693},[679,148136,148137],{"class":681,"line":909},[679,148138,996],{"class":693},[679,148140,148141],{"class":681,"line":918},[679,148142,889],{"emptyLinePlaceholder":797},[679,148144,148145],{"class":681,"line":935},[679,148146,148147],{"class":1400},"// But break in Java 21 due to Lombok's use of internal APIs\n",[651,148149,148150,148151,148154],{},"Lombok relies on internal compiler APIs (",[676,148152,148153],{},"com.sun.tools.javac","), which:",[5316,148156,148157,148160,148163,148166],{},[5332,148158,148159],{},"Are not part of the public API",[5332,148161,148162],{},"Can change without warning",[5332,148164,148165],{},"Often break with new Java releases",[5332,148167,148168],{},"Force teams to wait for Lombok updates before upgrading Java versions",[4542,148170,148172],{"id":148171},"modern-java-solutions","Modern Java Solutions",[651,148174,148175],{},"The good news is that modern Java provides better alternatives to Lombok. These solutions don't just avoid Lombok's problems—they often provide better, more maintainable approaches that work reliably with all development tools. Here's how you can achieve the same goals with standard Java features:",[5909,148177,148179],{"id":148178},"records-for-data-classes","Records for Data Classes",[651,148181,95386,148182,148185],{},[676,148183,148184],{},"@Data",", leverage Java records:",[669,148187,148189],{"className":4107,"code":148188,"language":4109,"meta":674,"style":674},"// Before with Lombok\n@Data\npublic class Product {\n private final String name;\n private final BigDecimal price;\n}\n\n// After with Java Record\npublic record Product(String name, BigDecimal price) {\n}\n",[676,148190,148191,148196,148202,148212,148220,148228,148232,148236,148241,148252],{"__ignoreMap":674},[679,148192,148193],{"class":681,"line":682},[679,148194,148195],{"class":1400},"// Before with Lombok\n",[679,148197,148198,148200],{"class":681,"line":790},[679,148199,4116],{"class":693},[679,148201,15305],{"class":685},[679,148203,148204,148206,148208,148210],{"class":681,"line":892},[679,148205,6073],{"class":685},[679,148207,4512],{"class":685},[679,148209,97646],{"class":880},[679,148211,884],{"class":693},[679,148213,148214,148216,148218],{"class":681,"line":901},[679,148215,9232],{"class":685},[679,148217,12768],{"class":685},[679,148219,16319],{"class":693},[679,148221,148222,148224,148226],{"class":681,"line":909},[679,148223,9232],{"class":685},[679,148225,12768],{"class":685},[679,148227,98891],{"class":693},[679,148229,148230],{"class":681,"line":918},[679,148231,996],{"class":693},[679,148233,148234],{"class":681,"line":935},[679,148235,889],{"emptyLinePlaceholder":797},[679,148237,148238],{"class":681,"line":944},[679,148239,148240],{"class":1400},"// After with Java Record\n",[679,148242,148243,148245,148247,148249],{"class":681,"line":959},[679,148244,6073],{"class":685},[679,148246,86928],{"class":685},[679,148248,97646],{"class":880},[679,148250,148251],{"class":693},"(String name, BigDecimal price) {\n",[679,148253,148254],{"class":681,"line":964},[679,148255,996],{"class":693},[651,148257,148258],{},"Records provide immutability, proper equals/hashCode implementations, and a clear indication of the class's purpose.",[5909,148260,148262],{"id":148261},"builder-pattern-without-lombok","Builder Pattern Without Lombok",[651,148264,148265,148266,148269],{},"While Lombok's ",[676,148267,148268],{},"@Builder"," is convenient, modern IDEs can generate builder patterns with a few clicks:",[669,148271,148273],{"className":4107,"code":148272,"language":4109,"meta":674,"style":674},"public class Order {\n private final String id;\n private final LocalDateTime createdAt;\n private final List\u003CProduct> products;\n\n private Order(Builder builder) {\n this.id = builder.id;\n this.createdAt = builder.createdAt;\n this.products = List.copyOf(builder.products);\n }\n\n public static Builder builder() {\n return new Builder();\n }\n\n // Builder class with fluent API\n public static class Builder {\n private String id;\n private LocalDateTime createdAt;\n private List\u003CProduct> products = new ArrayList\u003C>();\n\n public Builder id(String id) {\n this.id = id;\n return this;\n }\n\n // Additional builder methods...\n\n public Order build() {\n return new Order(this);\n }\n }\n}\n",[676,148274,148275,148286,148294,148303,148316,148320,148333,148344,148356,148373,148377,148381,148394,148405,148409,148413,148418,148430,148437,148443,148459,148463,148477,148487,148495,148499,148503,148508,148512,148523,148537,148541,148545],{"__ignoreMap":674},[679,148276,148277,148279,148281,148284],{"class":681,"line":682},[679,148278,6073],{"class":685},[679,148280,4512],{"class":685},[679,148282,148283],{"class":880}," Order",[679,148285,884],{"class":693},[679,148287,148288,148290,148292],{"class":681,"line":790},[679,148289,9232],{"class":685},[679,148291,12768],{"class":685},[679,148293,133848],{"class":693},[679,148295,148296,148298,148300],{"class":681,"line":892},[679,148297,9232],{"class":685},[679,148299,12768],{"class":685},[679,148301,148302],{"class":693}," LocalDateTime createdAt;\n",[679,148304,148305,148307,148309,148311,148313],{"class":681,"line":901},[679,148306,9232],{"class":685},[679,148308,12768],{"class":685},[679,148310,87217],{"class":693},[679,148312,97605],{"class":685},[679,148314,148315],{"class":693},"> products;\n",[679,148317,148318],{"class":681,"line":909},[679,148319,889],{"emptyLinePlaceholder":797},[679,148321,148322,148324,148326,148329,148331],{"class":681,"line":918},[679,148323,9232],{"class":685},[679,148325,148283],{"class":880},[679,148327,148328],{"class":693},"(Builder ",[679,148330,90934],{"class":2099},[679,148332,4390],{"class":693},[679,148334,148335,148337,148339,148341],{"class":681,"line":935},[679,148336,7862],{"class":931},[679,148338,11350],{"class":693},[679,148340,686],{"class":685},[679,148342,148343],{"class":693}," builder.id;\n",[679,148345,148346,148348,148351,148353],{"class":681,"line":944},[679,148347,7862],{"class":931},[679,148349,148350],{"class":693},".createdAt ",[679,148352,686],{"class":685},[679,148354,148355],{"class":693}," builder.createdAt;\n",[679,148357,148358,148360,148363,148365,148367,148370],{"class":681,"line":959},[679,148359,7862],{"class":931},[679,148361,148362],{"class":693},".products ",[679,148364,686],{"class":685},[679,148366,16669],{"class":693},[679,148368,148369],{"class":880},"copyOf",[679,148371,148372],{"class":693},"(builder.products);\n",[679,148374,148375],{"class":681,"line":964},[679,148376,985],{"class":693},[679,148378,148379],{"class":681,"line":977},[679,148380,889],{"emptyLinePlaceholder":797},[679,148382,148383,148385,148387,148390,148392],{"class":681,"line":982},[679,148384,6089],{"class":685},[679,148386,6092],{"class":685},[679,148388,148389],{"class":693}," Builder ",[679,148391,90934],{"class":880},[679,148393,2667],{"class":693},[679,148395,148396,148398,148400,148403],{"class":681,"line":988},[679,148397,9444],{"class":685},[679,148399,2054],{"class":685},[679,148401,148402],{"class":880}," Builder",[679,148404,9317],{"class":693},[679,148406,148407],{"class":681,"line":993},[679,148408,985],{"class":693},[679,148410,148411],{"class":681,"line":2129},[679,148412,889],{"emptyLinePlaceholder":797},[679,148414,148415],{"class":681,"line":2140},[679,148416,148417],{"class":1400}," // Builder class with fluent API\n",[679,148419,148420,148422,148424,148426,148428],{"class":681,"line":2145},[679,148421,6089],{"class":685},[679,148423,6092],{"class":685},[679,148425,4512],{"class":685},[679,148427,148402],{"class":880},[679,148429,884],{"class":693},[679,148431,148432,148435],{"class":681,"line":2154},[679,148433,148434],{"class":685}," private",[679,148436,133848],{"class":693},[679,148438,148439,148441],{"class":681,"line":2159},[679,148440,148434],{"class":685},[679,148442,148302],{"class":693},[679,148444,148445,148447,148449,148451,148453,148455,148457],{"class":681,"line":2164},[679,148446,148434],{"class":685},[679,148448,87217],{"class":693},[679,148450,97605],{"class":685},[679,148452,97908],{"class":693},[679,148454,686],{"class":685},[679,148456,2054],{"class":685},[679,148458,87229],{"class":693},[679,148460,148461],{"class":681,"line":3134},[679,148462,889],{"emptyLinePlaceholder":797},[679,148464,148465,148467,148469,148471,148473,148475],{"class":681,"line":3139},[679,148466,87238],{"class":685},[679,148468,148389],{"class":693},[679,148470,11341],{"class":880},[679,148472,11400],{"class":693},[679,148474,11341],{"class":2099},[679,148476,4390],{"class":693},[679,148478,148479,148481,148483,148485],{"class":681,"line":3144},[679,148480,80006],{"class":931},[679,148482,11350],{"class":693},[679,148484,686],{"class":685},[679,148486,11318],{"class":693},[679,148488,148489,148491,148493],{"class":681,"line":3149},[679,148490,20443],{"class":685},[679,148492,21353],{"class":931},[679,148494,1186],{"class":693},[679,148496,148497],{"class":681,"line":3169},[679,148498,1290],{"class":693},[679,148500,148501],{"class":681,"line":3185},[679,148502,889],{"emptyLinePlaceholder":797},[679,148504,148505],{"class":681,"line":3194},[679,148506,148507],{"class":1400}," // Additional builder methods...\n",[679,148509,148510],{"class":681,"line":3199},[679,148511,889],{"emptyLinePlaceholder":797},[679,148513,148514,148516,148519,148521],{"class":681,"line":3212},[679,148515,87238],{"class":685},[679,148517,148518],{"class":693}," Order ",[679,148520,23612],{"class":880},[679,148522,2667],{"class":693},[679,148524,148525,148527,148529,148531,148533,148535],{"class":681,"line":3217},[679,148526,20443],{"class":685},[679,148528,2054],{"class":685},[679,148530,148283],{"class":880},[679,148532,745],{"class":693},[679,148534,4732],{"class":931},[679,148536,1208],{"class":693},[679,148538,148539],{"class":681,"line":3222},[679,148540,1290],{"class":693},[679,148542,148543],{"class":681,"line":3227},[679,148544,985],{"class":693},[679,148546,148547],{"class":681,"line":3232},[679,148548,996],{"class":693},[4542,148550,148552],{"id":148551},"migration-strategy","Migration Strategy",[651,148554,148555],{},"If you're convinced it's time to move away from Lombok, here's a practical approach to migration:",[27665,148557,148558,148572,148589],{},[5332,148559,148560,148561],{},"Start with new code:",[5316,148562,148563,148566,148569],{},[5332,148564,148565],{},"Use records for simple data classes",[5332,148567,148568],{},"Leverage IDE generation for constructors and methods",[5332,148570,148571],{},"Document standard patterns for the team",[5332,148573,148574,148575],{},"Gradual migration of existing code:",[5316,148576,148577,148580,148583,148586],{},[5332,148578,148579],{},"Identify classes with minimal Lombok usage",[5332,148581,148582],{},"Convert one annotation at a time",[5332,148584,148585],{},"Use IDE refactoring tools to generate standard Java code",[5332,148587,148588],{},"Add tests before making changes",[5332,148590,148591,148592],{},"Tooling support:",[5316,148593,148594,148597,148600],{},[5332,148595,148596],{},"Configure IDE templates for common patterns",[5332,148598,148599],{},"Set up code style guidelines",[5332,148601,148602],{},"Use static analysis tools to enforce practices",[4542,148604,148606],{"id":148605},"the-real-cost-of-convenience","The Real Cost of Convenience",[651,148608,148609],{},"The Java community's initial embrace of Lombok was understandable—Java was verbose, and Lombok offered a seductive solution. However, we now know this convenience came at a steep price:",[5316,148611,148612,148615,148618,148621,148624],{},[5332,148613,148614],{},"Broken IDE features that hamper productivity",[5332,148616,148617],{},"Unreliable debugging that costs development time",[5332,148619,148620],{},"Build tool conflicts that complicate project setup",[5332,148622,148623],{},"Java version upgrades that become more difficult",[5332,148625,148626],{},"Development tools that can't be fully utilized",[4542,148628,9042],{"id":9041},[651,148630,148631],{},"While Lombok served a purpose in Java's history, its costs now far outweigh its benefits. The introduction of records, enhanced IDE capabilities, and modern Java features provide better solutions without the drawbacks of bytecode manipulation and tooling interference.",[651,148633,148634],{},"The path forward is clear: embrace modern Java features, leverage your IDE's capabilities, and gradually migrate away from Lombok. Your future self (and your team) will thank you for having:",[5316,148636,148637,148640,148643,148646,148649],{},[5332,148638,148639],{},"Fully debuggable code",[5332,148641,148642],{},"Reliable IDE features",[5332,148644,148645],{},"Faster builds with proper incremental compilation",[5332,148647,148648],{},"Easier Java version upgrades",[5332,148650,148651],{},"Better integration with the entire Java ecosystem",[651,148653,148654],{},"Remember, good code isn't just about reducing verbosity—it's about clarity, maintainability, and leveraging the best tools for the job. As our tools and language evolve, so should our practices.",[651,148656,143516],{},[651,148658,148659],{},"P.S. If you're starting a new project, save yourself future headaches and skip Lombok entirely. Your development tools will thank you.",[786,148661,148662],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":148664},[148665,148673,148677,148678,148679],{"id":147826,"depth":790,"text":147827,"children":148666},[148667,148668,148669,148670,148671,148672],{"id":147833,"depth":892,"text":147834},{"id":147892,"depth":892,"text":147893},{"id":147916,"depth":892,"text":147917},{"id":147969,"depth":892,"text":147970},{"id":147990,"depth":892,"text":147991},{"id":148073,"depth":892,"text":148074},{"id":148171,"depth":790,"text":148172,"children":148674},[148675,148676],{"id":148178,"depth":892,"text":148179},{"id":148261,"depth":892,"text":148262},{"id":148551,"depth":790,"text":148552},{"id":148605,"depth":790,"text":148606},{"id":9041,"depth":790,"text":9042},"A detailed exploration of why Java developers should reconsider using Project Lombok, examining its drawbacks and modern alternatives in Java development.",{"slug":148682,"date":148683,"published":797,"author":798,"tags":148684,"cover":148685,"keywords":148686},"no-lombok","2024-11-11T09:00:00.000Z",[36422],"./no_lombok_cover.png","java, lombok, project lombok, java development, java records, clean code, software development, java best practices",{"title":27,"description":148680},"blog/2024/11/11/no-lombok","QkryJ3EqfKQbJg96I7_whvRD43rBe0uVDctxbMeDcNY",{"id":148691,"title":24,"body":148692,"description":149410,"extension":793,"meta":149411,"navigation":797,"path":25,"seo":149418,"stem":149419,"__hash__":149420},"content/blog/2024/12/02/spring-boot-graphql-query-by-example.md",{"type":648,"value":148693,"toc":149403},[148694,148697,148701,148704,148874,148877,148881,148884,148987,148993,149146,149149,149266,149276,149280,149283,149293,149296,149368,149371,149373,149376,149390,149392,149395,149398,149400],[651,148695,148696],{},"When building APIs, one of the most common challenges developers face is implementing flexible search functionality. You often need to support filtering based on multiple optional criteria, leading to complex query logic and verbose repository methods. Spring Boot 3.2 introduces a powerful combination: GraphQL with Query by Example (QBE) support, offering an elegant solution to this challenge.",[4542,148698,148700],{"id":148699},"the-power-of-query-by-example","The Power of Query by Example",[651,148702,148703],{},"Traditional approaches to implementing dynamic queries often involve writing multiple repository methods or building complex predicates. Consider a book management system where users need to search by title, author, or publication year in any combination. Your repository might end up looking like this:",[669,148705,148707],{"className":4107,"code":148706,"language":4109,"meta":674,"style":674},"public interface BookRepository extends JpaRepository\u003CBook, Long> {\n List\u003CBook> findByTitle(String title);\n List\u003CBook> findByAuthor(String author);\n List\u003CBook> findByPublishedYear(Integer year);\n List\u003CBook> findByTitleAndAuthor(String title, String author);\n List\u003CBook> findByTitleAndPublishedYear(String title, Integer year);\n List\u003CBook> findByAuthorAndPublishedYear(String author, Integer year);\n List\u003CBook> findByTitleAndAuthorAndPublishedYear(String title, String author, Integer year);\n}\n",[676,148708,148709,148731,148747,148764,148781,148802,148824,148845,148870],{"__ignoreMap":674},[679,148710,148711,148713,148715,148717,148719,148721,148723,148725,148727,148729],{"class":681,"line":682},[679,148712,6073],{"class":685},[679,148714,6994],{"class":685},[679,148716,87191],{"class":880},[679,148718,2767],{"class":685},[679,148720,103199],{"class":880},[679,148722,4505],{"class":693},[679,148724,86678],{"class":685},[679,148726,2797],{"class":693},[679,148728,1094],{"class":685},[679,148730,16397],{"class":693},[679,148732,148733,148735,148737,148739,148741,148743,148745],{"class":681,"line":790},[679,148734,84917],{"class":693},[679,148736,86678],{"class":685},[679,148738,20881],{"class":693},[679,148740,7638],{"class":880},[679,148742,11400],{"class":693},[679,148744,11750],{"class":2099},[679,148746,1208],{"class":693},[679,148748,148749,148751,148753,148755,148758,148760,148762],{"class":681,"line":892},[679,148750,84917],{"class":693},[679,148752,86678],{"class":685},[679,148754,20881],{"class":693},[679,148756,148757],{"class":880},"findByAuthor",[679,148759,11400],{"class":693},[679,148761,6526],{"class":2099},[679,148763,1208],{"class":693},[679,148765,148766,148768,148770,148772,148775,148777,148779],{"class":681,"line":901},[679,148767,84917],{"class":693},[679,148769,86678],{"class":685},[679,148771,20881],{"class":693},[679,148773,148774],{"class":880},"findByPublishedYear",[679,148776,87309],{"class":693},[679,148778,48901],{"class":2099},[679,148780,1208],{"class":693},[679,148782,148783,148785,148787,148789,148792,148794,148796,148798,148800],{"class":681,"line":909},[679,148784,84917],{"class":693},[679,148786,86678],{"class":685},[679,148788,20881],{"class":693},[679,148790,148791],{"class":880},"findByTitleAndAuthor",[679,148793,11400],{"class":693},[679,148795,11750],{"class":2099},[679,148797,20006],{"class":693},[679,148799,6526],{"class":2099},[679,148801,1208],{"class":693},[679,148803,148804,148806,148808,148810,148813,148815,148817,148820,148822],{"class":681,"line":918},[679,148805,84917],{"class":693},[679,148807,86678],{"class":685},[679,148809,20881],{"class":693},[679,148811,148812],{"class":880},"findByTitleAndPublishedYear",[679,148814,11400],{"class":693},[679,148816,11750],{"class":2099},[679,148818,148819],{"class":693},", Integer ",[679,148821,48901],{"class":2099},[679,148823,1208],{"class":693},[679,148825,148826,148828,148830,148832,148835,148837,148839,148841,148843],{"class":681,"line":935},[679,148827,84917],{"class":693},[679,148829,86678],{"class":685},[679,148831,20881],{"class":693},[679,148833,148834],{"class":880},"findByAuthorAndPublishedYear",[679,148836,11400],{"class":693},[679,148838,6526],{"class":2099},[679,148840,148819],{"class":693},[679,148842,48901],{"class":2099},[679,148844,1208],{"class":693},[679,148846,148847,148849,148851,148853,148856,148858,148860,148862,148864,148866,148868],{"class":681,"line":944},[679,148848,84917],{"class":693},[679,148850,86678],{"class":685},[679,148852,20881],{"class":693},[679,148854,148855],{"class":880},"findByTitleAndAuthorAndPublishedYear",[679,148857,11400],{"class":693},[679,148859,11750],{"class":2099},[679,148861,20006],{"class":693},[679,148863,6526],{"class":2099},[679,148865,148819],{"class":693},[679,148867,48901],{"class":2099},[679,148869,1208],{"class":693},[679,148871,148872],{"class":681,"line":959},[679,148873,996],{"class":693},[651,148875,148876],{},"This approach quickly becomes unmaintainable as the number of searchable fields grows. Query by Example offers a more elegant solution by allowing you to create a prototype (example) of what you're looking for.",[4542,148878,148880],{"id":148879},"implementing-graphql-with-qbe","Implementing GraphQL with QBE",[651,148882,148883],{},"Let's build a practical example using Spring Boot 3.2. First, create a new project with the following dependencies:",[669,148885,148887],{"className":9101,"code":148886,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-graphql\u003C/artifactId>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-data-jpa\u003C/artifactId>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,148888,148889,148897,148905,148917,148930,148938,148946,148958,148971,148979],{"__ignoreMap":674},[679,148890,148891,148893,148895],{"class":681,"line":682},[679,148892,4505],{"class":693},[679,148894,129682],{"class":4508},[679,148896,4519],{"class":693},[679,148898,148899,148901,148903],{"class":681,"line":790},[679,148900,4524],{"class":693},[679,148902,119838],{"class":4508},[679,148904,4519],{"class":693},[679,148906,148907,148909,148911,148913,148915],{"class":681,"line":892},[679,148908,4904],{"class":693},[679,148910,119847],{"class":4508},[679,148912,119850],{"class":693},[679,148914,119847],{"class":4508},[679,148916,4519],{"class":693},[679,148918,148919,148921,148923,148926,148928],{"class":681,"line":901},[679,148920,4904],{"class":693},[679,148922,119861],{"class":4508},[679,148924,148925],{"class":693},">spring-boot-starter-graphql\u003C/",[679,148927,119861],{"class":4508},[679,148929,4519],{"class":693},[679,148931,148932,148934,148936],{"class":681,"line":909},[679,148933,4577],{"class":693},[679,148935,119838],{"class":4508},[679,148937,4519],{"class":693},[679,148939,148940,148942,148944],{"class":681,"line":918},[679,148941,4524],{"class":693},[679,148943,119838],{"class":4508},[679,148945,4519],{"class":693},[679,148947,148948,148950,148952,148954,148956],{"class":681,"line":935},[679,148949,4904],{"class":693},[679,148951,119847],{"class":4508},[679,148953,119850],{"class":693},[679,148955,119847],{"class":4508},[679,148957,4519],{"class":693},[679,148959,148960,148962,148964,148967,148969],{"class":681,"line":944},[679,148961,4904],{"class":693},[679,148963,119861],{"class":4508},[679,148965,148966],{"class":693},">spring-boot-starter-data-jpa\u003C/",[679,148968,119861],{"class":4508},[679,148970,4519],{"class":693},[679,148972,148973,148975,148977],{"class":681,"line":959},[679,148974,4577],{"class":693},[679,148976,119838],{"class":4508},[679,148978,4519],{"class":693},[679,148980,148981,148983,148985],{"class":681,"line":964},[679,148982,4586],{"class":693},[679,148984,129682],{"class":4508},[679,148986,4519],{"class":693},[651,148988,148989,148990,2391],{},"Define your GraphQL schema in ",[676,148991,148992],{},"src/main/resources/graphql/schema.graphqls",[669,148994,148996],{"className":66259,"code":148995,"language":66261,"meta":674,"style":674},"type Book {\n id: ID!\n title: String\n author: String\n publishedYear: Int\n}\n\ninput BookInput {\n title: String\n author: String\n publishedYear: Int\n}\n\ntype Query {\n books(book: BookInput): [Book]\n book(id: ID!): Book\n}\n",[676,148997,148998,149006,149016,149026,149035,149045,149049,149053,149061,149069,149077,149085,149089,149093,149101,149121,149142],{"__ignoreMap":674},[679,148999,149000,149002,149004],{"class":681,"line":682},[679,149001,121968],{"class":685},[679,149003,86931],{"class":931},[679,149005,884],{"class":693},[679,149007,149008,149010,149012,149014],{"class":681,"line":790},[679,149009,122038],{"class":2099},[679,149011,4282],{"class":693},[679,149013,22662],{"class":931},[679,149015,83719],{"class":685},[679,149017,149018,149021,149023],{"class":681,"line":892},[679,149019,149020],{"class":2099}," title",[679,149022,4282],{"class":693},[679,149024,149025],{"class":931},"String\n",[679,149027,149028,149031,149033],{"class":681,"line":901},[679,149029,149030],{"class":2099}," author",[679,149032,4282],{"class":693},[679,149034,149025],{"class":931},[679,149036,149037,149040,149042],{"class":681,"line":909},[679,149038,149039],{"class":2099}," publishedYear",[679,149041,4282],{"class":693},[679,149043,149044],{"class":931},"Int\n",[679,149046,149047],{"class":681,"line":918},[679,149048,996],{"class":693},[679,149050,149051],{"class":681,"line":935},[679,149052,889],{"emptyLinePlaceholder":797},[679,149054,149055,149057,149059],{"class":681,"line":944},[679,149056,27722],{"class":685},[679,149058,103928],{"class":931},[679,149060,884],{"class":693},[679,149062,149063,149065,149067],{"class":681,"line":959},[679,149064,149020],{"class":2099},[679,149066,4282],{"class":693},[679,149068,149025],{"class":931},[679,149070,149071,149073,149075],{"class":681,"line":964},[679,149072,149030],{"class":2099},[679,149074,4282],{"class":693},[679,149076,149025],{"class":931},[679,149078,149079,149081,149083],{"class":681,"line":977},[679,149080,149039],{"class":2099},[679,149082,4282],{"class":693},[679,149084,149044],{"class":931},[679,149086,149087],{"class":681,"line":982},[679,149088,996],{"class":693},[679,149090,149091],{"class":681,"line":988},[679,149092,889],{"emptyLinePlaceholder":797},[679,149094,149095,149097,149099],{"class":681,"line":993},[679,149096,121968],{"class":685},[679,149098,121971],{"class":931},[679,149100,884],{"class":693},[679,149102,149103,149106,149108,149111,149113,149115,149117,149119],{"class":681,"line":2129},[679,149104,149105],{"class":2099}," books",[679,149107,745],{"class":693},[679,149109,149110],{"class":2099},"book",[679,149112,4282],{"class":693},[679,149114,103907],{"class":931},[679,149116,122008],{"class":693},[679,149118,86678],{"class":931},[679,149120,67550],{"class":693},[679,149122,149123,149126,149128,149130,149132,149134,149136,149139],{"class":681,"line":2140},[679,149124,149125],{"class":2099}," book",[679,149127,745],{"class":693},[679,149129,11341],{"class":2099},[679,149131,4282],{"class":693},[679,149133,22662],{"class":931},[679,149135,1223],{"class":685},[679,149137,149138],{"class":693},"): ",[679,149140,149141],{"class":931},"Book\n",[679,149143,149144],{"class":681,"line":2145},[679,149145,996],{"class":693},[651,149147,149148],{},"Create your entity and repository:",[669,149150,149152],{"className":4107,"code":149151,"language":4109,"meta":674,"style":674},"@Entity\npublic class Book {\n @Id @GeneratedValue\n private Long id;\n private String title;\n private String author;\n private Integer publishedYear;\n \n // getters, setters, constructors\n}\n\n@GraphQLRepository\npublic interface BookRepository extends JpaRepository\u003CBook, Long>, \n QueryByExampleExecutor\u003CBook> {\n}\n",[676,149153,149154,149160,149170,149180,149186,149192,149198,149205,149209,149214,149218,149222,149229,149252,149262],{"__ignoreMap":674},[679,149155,149156,149158],{"class":681,"line":682},[679,149157,4116],{"class":693},[679,149159,11234],{"class":685},[679,149161,149162,149164,149166,149168],{"class":681,"line":790},[679,149163,6073],{"class":685},[679,149165,4512],{"class":685},[679,149167,86931],{"class":880},[679,149169,884],{"class":693},[679,149171,149172,149174,149176,149178],{"class":681,"line":892},[679,149173,6872],{"class":693},[679,149175,11256],{"class":685},[679,149177,6475],{"class":693},[679,149179,11261],{"class":685},[679,149181,149182,149184],{"class":681,"line":901},[679,149183,9232],{"class":685},[679,149185,11268],{"class":693},[679,149187,149188,149190],{"class":681,"line":909},[679,149189,9232],{"class":685},[679,149191,93032],{"class":693},[679,149193,149194,149196],{"class":681,"line":918},[679,149195,9232],{"class":685},[679,149197,103016],{"class":693},[679,149199,149200,149202],{"class":681,"line":935},[679,149201,9232],{"class":685},[679,149203,149204],{"class":693}," Integer publishedYear;\n",[679,149206,149207],{"class":681,"line":944},[679,149208,119642],{"class":693},[679,149210,149211],{"class":681,"line":959},[679,149212,149213],{"class":1400}," // getters, setters, constructors\n",[679,149215,149216],{"class":681,"line":964},[679,149217,996],{"class":693},[679,149219,149220],{"class":681,"line":977},[679,149221,889],{"emptyLinePlaceholder":797},[679,149223,149224,149226],{"class":681,"line":982},[679,149225,4116],{"class":693},[679,149227,149228],{"class":685},"GraphQLRepository\n",[679,149230,149231,149233,149235,149237,149239,149241,149243,149245,149247,149249],{"class":681,"line":988},[679,149232,6073],{"class":685},[679,149234,6994],{"class":685},[679,149236,87191],{"class":880},[679,149238,2767],{"class":685},[679,149240,103199],{"class":880},[679,149242,4505],{"class":693},[679,149244,86678],{"class":685},[679,149246,2797],{"class":693},[679,149248,1094],{"class":685},[679,149250,149251],{"class":693},">, \n",[679,149253,149254,149256,149258,149260],{"class":681,"line":993},[679,149255,147262],{"class":880},[679,149257,4505],{"class":693},[679,149259,86678],{"class":685},[679,149261,16397],{"class":693},[679,149263,149264],{"class":681,"line":2129},[679,149265,996],{"class":693},[651,149267,149268,149269,149272,149273,149275],{},"The magic happens with the ",[676,149270,149271],{},"@GraphQLRepository"," annotation, which automatically creates data fetchers for your GraphQL queries based on the repository methods. Combined with ",[676,149274,147222],{},", it enables dynamic querying without additional code.",[4542,149277,149279],{"id":149278},"testing-the-api","Testing the API",[651,149281,149282],{},"With GraphiQL enabled in your application.properties:",[669,149284,149285],{"className":76589,"code":88441,"language":35538,"meta":674,"style":674},[676,149286,149287],{"__ignoreMap":674},[679,149288,149289,149291],{"class":681,"line":682},[679,149290,88448],{"class":685},[679,149292,93485],{"class":693},[651,149294,149295],{},"You can now execute flexible queries:",[669,149297,149299],{"className":66259,"code":149298,"language":66261,"meta":674,"style":674},"query {\n books(book: {\n author: \"Martin Fowler\"\n publishedYear: 2023\n }) {\n id\n title\n author\n publishedYear\n }\n}\n",[676,149300,149301,149307,149317,149327,149337,149342,149346,149350,149355,149360,149364],{"__ignoreMap":674},[679,149302,149303,149305],{"class":681,"line":682},[679,149304,115846],{"class":685},[679,149306,884],{"class":693},[679,149308,149309,149311,149313,149315],{"class":681,"line":790},[679,149310,149105],{"class":2099},[679,149312,745],{"class":693},[679,149314,149110],{"class":2099},[679,149316,28468],{"class":693},[679,149318,149319,149322,149324],{"class":681,"line":892},[679,149320,149321],{"class":689}," author",[679,149323,4282],{"class":693},[679,149325,149326],{"class":689},"\"Martin Fowler\"\n",[679,149328,149329,149332,149334],{"class":681,"line":901},[679,149330,149331],{"class":689}," publishedYear",[679,149333,4282],{"class":693},[679,149335,149336],{"class":931},"2023\n",[679,149338,149339],{"class":681,"line":909},[679,149340,149341],{"class":693}," }) {\n",[679,149343,149344],{"class":681,"line":918},[679,149345,66293],{"class":2099},[679,149347,149348],{"class":681,"line":935},[679,149349,66298],{"class":2099},[679,149351,149352],{"class":681,"line":944},[679,149353,149354],{"class":2099}," author\n",[679,149356,149357],{"class":681,"line":959},[679,149358,149359],{"class":2099}," publishedYear\n",[679,149361,149362],{"class":681,"line":964},[679,149363,985],{"class":693},[679,149365,149366],{"class":681,"line":977},[679,149367,996],{"class":693},[651,149369,149370],{},"This query will match books that have both the specified author and publication year. The power of this approach becomes evident when you need to search with different combinations of criteria - no additional repository methods needed!",[4542,149372,139990],{"id":139989},[651,149374,149375],{},"When implementing GraphQL with Query by Example, keep these points in mind:",[27665,149377,149378,149381,149384,149387],{},[5332,149379,149380],{},"Use nullable fields in your input types to make them optional for searching",[5332,149382,149383],{},"Consider adding match modes (exact, contains, starts with) for string fields",[5332,149385,149386],{},"Implement pagination for large result sets",[5332,149388,149389],{},"Add proper validation and error handling",[4542,149391,9042],{"id":9041},[651,149393,149394],{},"The combination of GraphQL and Query by Example in Spring Boot provides a powerful solution for building flexible search APIs. This approach significantly reduces boilerplate code while maintaining clean, maintainable implementations. The result is a more elegant and developer-friendly way to handle dynamic queries in your applications.",[651,149396,149397],{},"Ready to try it yourself? Start with a simple entity and gradually add more complex search capabilities. The flexibility of this approach will make future extensions straightforward and keep your codebase clean.",[651,149399,78024],{},[786,149401,149402],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}",{"title":674,"searchDepth":790,"depth":790,"links":149404},[149405,149406,149407,149408,149409],{"id":148699,"depth":790,"text":148700},{"id":148879,"depth":790,"text":148880},{"id":149278,"depth":790,"text":149279},{"id":139989,"depth":790,"text":139990},{"id":9041,"depth":790,"text":9042},"Learn how to build flexible and maintainable GraphQL APIs using Spring Boot's Query by Example support, reducing boilerplate code while enabling powerful search capabilities.",{"slug":149412,"date":149413,"published":797,"author":798,"tags":149414,"video":149415,"keywords":149416,"github":149417},"spring-boot-graphql-query-by-example","2024-12-02T09:00:00.000Z",[99135,7077],"https://www.youtube.com/embed/J8vC8RflPPY","Spring Boot, GraphQL, Query by Example, QBE, API Development, Java, Spring Data JPA, Dynamic Queries, Spring for GraphQL","https://github.com/danvega/graphql-qbe",{"title":24,"description":149410},"blog/2024/12/02/spring-boot-graphql-query-by-example","ek9lBj_PPFvCS6D4qncKh6U5DnlXDZ11t0nwxHbXvcM",{"id":149422,"title":21,"body":149423,"description":149648,"extension":793,"meta":149649,"navigation":797,"path":22,"seo":149654,"stem":149655,"__hash__":149656},"content/blog/2025/02/27/ai-powered-dev-hacks-confoo-2025.md",{"type":648,"value":149424,"toc":149631},[149425,149428,149431,149437,149443,149447,149450,149453,149464,149467,149471,149474,149478,149481,149484,149488,149491,149495,149498,149502,149505,149509,149512,149516,149519,149523,149526,149530,149533,149537,149540,149566,149570,149573,149587,149589,149595,149599,149603,149606,149609,149612,149615,149617],[651,149426,149427],{},"Recently, I had the pleasure of presenting on a topic that I'm pretty passionate about: how artificial intelligence can amplify developer productivity and creativity. In my talk \"Code Smarter, Not Harder: AI-Powered Dev Hacks for All,\" I explored how AI isn't replacing developers but rather making us more effective and efficient.",[651,149429,149430],{},"As I shared in the presentation, AI won't replace developers, but developers who use AI will replace those who don't. This sentiment captures the essence of what I believe is happening in our industry right now — we're witnessing a fundamental shift in how software gets built, and those who adapt will thrive.",[651,149432,149433],{},[660,149434],{"alt":149435,"src":149436},"Show Time","/images/blog/2025/02/27/confoo_ai-hacks_01.jpg",[651,149438,149439],{},[660,149440],{"alt":149441,"src":149442},"What a Crowd","/images/blog/2025/02/27/confoo_ai-hacks_02.png",[4542,149444,149446],{"id":149445},"why-ai-matters-for-developers","Why AI Matters for Developers",[651,149448,149449],{},"The statistics speak volumes: 72% of developers are confident in AI tools, with 92% of Fortune 500 companies already using AI technologies in some capacity. Among developers, 75% are either using or planning to use AI tools, with an average productivity increase of 26%.",[651,149451,149452],{},"How are developers using these tools? According to recent surveys:",[5316,149454,149455,149458,149461],{},[5332,149456,149457],{},"85% use AI for code generation",[5332,149459,149460],{},"77% for research and learning",[5332,149462,149463],{},"54% for debugging and testing",[651,149465,149466],{},"These numbers reflect what we're all experiencing: AI is becoming an essential part of the modern developer's toolkit.",[4542,149468,149470],{"id":149469},"_8-ai-powered-dev-hacks-to-transform-your-workflow","8 AI-Powered Dev Hacks to Transform Your Workflow",[651,149472,149473],{},"Throughout the presentation, I shared eight practical ways to leverage AI in your development workflow. Here's a recap of these game-changing approaches:",[5909,149475,149477],{"id":149476},"_1-learning-how-to-talk-to-robots-prompt-engineering","#1 - Learning How to Talk to Robots (Prompt Engineering)",[651,149479,149480],{},"The foundation of effective AI use is learning how to communicate with these systems. Clear, structured prompts determine the quality of AI output. I demonstrated how to craft prompts that include context, examples, and specific instructions to get the best results.",[651,149482,149483],{},"Instead of vague requests like \"Write a blog post about AI,\" try detailed prompts such as \"Write a technical blog post explaining neural networks to junior developers, focusing on practical examples. Include code samples in Python and keep it under 1,000 words.\"",[5909,149485,149487],{"id":149486},"_2-prompting-with-your-voice","#2 - Prompting with Your Voice",[651,149489,149490],{},"Voice-based AI interaction is becoming increasingly powerful. By dictating your requirements rather than typing them, you can often articulate more complex ideas and save time. This approach is particularly useful when brainstorming or working through programming challenges.",[5909,149492,149494],{"id":149493},"_3-learning-software-development","#3 - Learning Software Development",[651,149496,149497],{},"AI excels as a personalized learning companion. For junior developers, AI can break down complex concepts into simpler terms with customized explanations that match your learning style. Senior developers can use AI to quickly understand new technologies and frameworks or dive deep into advanced concepts.",[5909,149499,149501],{"id":149500},"_4-reading-code","#4 - Reading Code",[651,149503,149504],{},"When faced with unfamiliar or legacy code, AI can provide remarkable assistance. By asking AI to explain code's purpose, identify design patterns, or break down complex functions, you gain insights that would otherwise require significant time to discover. This is particularly valuable when onboarding to new projects or maintaining legacy systems.",[5909,149506,149508],{"id":149507},"_5-documentation","#5 - Documentation",[651,149510,149511],{},"Documentation is crucial but often neglected due to time constraints. AI can generate class/method documentation, explain complex algorithms, create usage examples, and document API endpoints. It can also improve existing documentation by enhancing clarity and consistency or converting between formats.",[5909,149513,149515],{"id":149514},"_6-building-tools","#6 - Building Tools",[651,149517,149518],{},"We're naturally tool-builders, and AI amplifies this capacity. I showed how developers can create AI-powered CLI tools, IDE plugins, browser extensions, and reporting tools that automate repetitive tasks in your workflow. Building these personalized tools allows you to address specific pain points in your development process.",[5909,149520,149522],{"id":149521},"_7-working-with-data","#7 - Working with Data",[651,149524,149525],{},"AI excels at data-related tasks: transforming between formats (JSON, CSV, XML), generating realistic test data, optimizing database queries, designing schemas, and converting between SQL dialects. These capabilities save countless hours that would otherwise be spent on tedious data manipulation.",[5909,149527,149529],{"id":149528},"_8-running-models-locally","#8 - Running Models Locally",[651,149531,149532],{},"For tasks requiring privacy, enhanced performance, or offline capability, running AI models locally is becoming increasingly accessible. Tools like Ollama and Open WebUI enable developers to run powerful models on their own hardware, ensuring sensitive data stays within your infrastructure while maintaining full control.",[4542,149534,149536],{"id":149535},"best-practices-for-ai-integration","Best Practices for AI Integration",[651,149538,149539],{},"Throughout the presentation, I emphasized several key principles for effective AI use:",[27665,149541,149542,149548,149554,149560],{},[5332,149543,149544,149547],{},[2939,149545,149546],{},"Always review and understand AI-generated code"," - Never blindly trust what AI produces",[5332,149549,149550,149553],{},[2939,149551,149552],{},"Maintain strict security practices"," - Be cautious about sharing sensitive information",[5332,149555,149556,149559],{},[2939,149557,149558],{},"Use AI as a learning tool, not just a code generator"," - Understand the \"why\" behind solutions",[5332,149561,149562,149565],{},[2939,149563,149564],{},"Start small and scale gradually"," - Begin with well-defined tasks before tackling more complex ones",[4542,149567,149569],{"id":149568},"getting-started-today","Getting Started Today",[651,149571,149572],{},"If you're looking to incorporate AI into your workflow, I recommend these practical steps:",[27665,149574,149575,149578,149581,149584],{},[5332,149576,149577],{},"Begin with documentation and code review tasks",[5332,149579,149580],{},"Use AI to learn new technologies and concepts",[5332,149582,149583],{},"Integrate AI tools into your existing workflow gradually",[5332,149585,149586],{},"Develop a collection of reliable prompts for common tasks",[4542,149588,69830],{"id":69829},[651,149590,149591,149592,664],{},"You can find the slides from my talk below or download them ",[812,149593,18263],{"href":149594},"/documents/ai-powered-dev-hacks-confoo-2025.pdf",[149596,149597],"pdf-embed",{"height":149598,"src":149594},"635px",[4542,149600,149602],{"id":149601},"final-thoughts","Final Thoughts",[651,149604,149605],{},"As software developers, we're entering an era where AI can handle many of the tedious aspects of coding, allowing us to focus on what truly matters — creativity, problem-solving, and delivering value. The key isn't to replace human judgment but to enhance it.",[651,149607,149608],{},"In the words of Satya Nadella, \"I think what AI does quite frankly is reduce the floor and raise the ceiling for all of us.\" This perfectly captures the potential of AI in development: lowering barriers to entry while expanding what's possible.",[651,149610,149611],{},"AI doesn't diminish the importance of strong engineering principles or deep technical knowledge — it amplifies them. By embracing these tools, we can focus on higher-level thinking and more meaningful work while letting AI handle the repetitive tasks.",[651,149613,149614],{},"What AI hacks have you incorporated into your workflow? I'd love to hear about your experiences in the comments below!",[25332,149616],{},[651,149618,149619],{},[7300,149620,149621,149622,149625,149626,664],{},"For more content on AI and software development, visit ",[812,149623,53863],{"href":53869,"rel":149624},[816]," or check out my AI-focused site at ",[812,149627,149630],{"href":149628,"rel":149629},"https://www.bytesizedai.dev",[816],"bytesizedai.dev",{"title":674,"searchDepth":790,"depth":790,"links":149632},[149633,149634,149644,149645,149646,149647],{"id":149445,"depth":790,"text":149446},{"id":149469,"depth":790,"text":149470,"children":149635},[149636,149637,149638,149639,149640,149641,149642,149643],{"id":149476,"depth":892,"text":149477},{"id":149486,"depth":892,"text":149487},{"id":149493,"depth":892,"text":149494},{"id":149500,"depth":892,"text":149501},{"id":149507,"depth":892,"text":149508},{"id":149514,"depth":892,"text":149515},{"id":149521,"depth":892,"text":149522},{"id":149528,"depth":892,"text":149529},{"id":149535,"depth":790,"text":149536},{"id":149568,"depth":790,"text":149569},{"id":69829,"depth":790,"text":69830},{"id":149601,"depth":790,"text":149602},"Recently, I had the pleasure of presenting on a topic that I'm pretty passionate about: how artificial intelligence can amplify developer productivity and creativity.",{"slug":149650,"date":149651,"published":797,"author":798,"tags":149652,"keywords":149653},"ai-powered-dev-hacks-confoo-2025","2025-02-27T09:00:00.000Z",[126904],"AI, Developer Productivity",{"title":21,"description":149648},"blog/2025/02/27/ai-powered-dev-hacks-confoo-2025","Nwn5ZImckX9QxL6d259U4YlSD69xCZglxtJE4UHXqFc",{"id":149658,"title":18,"body":149659,"description":150566,"extension":793,"meta":150567,"navigation":797,"path":19,"seo":150574,"stem":150575,"__hash__":150576},"content/blog/2025/03/11/model-context-protocol-introduction.md",{"type":648,"value":149660,"toc":150534},[149661,149664,149671,149675,149678,149704,149707,149711,149717,149720,149723,149726,149752,149756,149761,149764,149768,149771,149797,149801,149804,149830,149833,149853,149856,149860,149863,149866,149892,149895,149899,149902,149906,149914,149918,149921,149946,149975,149982,149986,149989,149992,150029,150033,150036,150047,150050,150054,150057,150070,150105,150111,150115,150118,150129,150132,150136,150139,150143,150165,150169,150188,150192,150211,150215,150232,150236,150252,150256,150272,150281,150285,150288,150326,150330,150333,150347,150350,150364,150367,150371,150374,150416,150420,150423,150455,150458,150460,150463,150466,150470,150523,150526,150529,150531],[651,149662,149663],{},"Have you ever been frustrated by the limitations of large language models? Despite their impressive capabilities, LLMs are confined by their training data and knowledge cutoff dates. They can't access real-time information, interact with your local files, or connect to your organization's internal systems - until now.",[651,149665,149666,149667,149670],{},"Enter ",[2939,149668,149669],{},"Model Context Protocol (MCP)",", a framework that bridges the gap between AI models and external data sources. In this comprehensive guide, we'll explore what MCP is, why it matters for Java and Spring developers, and provide detailed instructions on how you can start using it in your applications today.",[4542,149672,149674],{"id":149673},"the-problem-llm-limitations","The Problem: LLM Limitations",[651,149676,149677],{},"Before diving into MCP, let's understand the core problem it solves. Large language models have revolutionized how we interact with AI, but they face significant limitations:",[27665,149679,149680,149686,149692,149698],{},[5332,149681,149682,149685],{},[2939,149683,149684],{},"Knowledge Cutoffs",": Models like Claude and ChatGPT only have information up to their training cutoff date. Ask Claude about yesterday's news or a recent software release, and you'll likely get an apologetic response about its knowledge limitations.",[5332,149687,149688,149691],{},[2939,149689,149690],{},"No Access to Real-Time Data",": Weather conditions, market prices, sports scores, or current events remain unknown to LLMs without external help.",[5332,149693,149694,149697],{},[2939,149695,149696],{},"No Visibility into Your Systems",": LLMs can't access your files, databases, or internal tools by default, limiting their usefulness in enterprise settings.",[5332,149699,149700,149703],{},[2939,149701,149702],{},"Integration Complexity",": Building custom connectors for each external service is time-consuming and leads to duplication of effort across teams and organizations.",[651,149705,149706],{},"These limitations create a significant gap between what LLMs can do in theory and what they can deliver in practice, especially in business applications where access to current and private data is essential.",[4542,149708,149710],{"id":149709},"what-is-model-context-protocol","What is Model Context Protocol?",[651,149712,149713],{},[660,149714],{"alt":149715,"src":149716},"What is MCP?","/images/blog/2025/03/11/what_is_mcp.jpeg",[651,149718,149719],{},"Model Context Protocol (MCP) is a standardized framework developed by Anthropic (the makers of Claude) that enables large language models to interact with external tools and data sources. It defines a consistent way for LLMs to communicate with servers that provide access to various capabilities.",[651,149721,149722],{},"Think of MCP as the \"USB standard\" for AI integrations. Before USB, every device would need a different connector type, creating chaos for users and manufacturers. Similarly, MCP establishes a common protocol for LLMs to connect with external services, eliminating the need for custom integrations for each service and model combination.",[651,149724,149725],{},"The protocol is:",[5316,149727,149728,149734,149740,149746],{},[5332,149729,149730,149733],{},[2939,149731,149732],{},"Open and standardized",": Any LLM can implement it, not just Claude",[5332,149735,149736,149739],{},[2939,149737,149738],{},"Secure",": It includes built-in security measures like access controls and permission systems",[5332,149741,149742,149745],{},[2939,149743,149744],{},"Extensible",": Developers can create servers for any type of data source or tool",[5332,149747,149748,149751],{},[2939,149749,149750],{},"Easy to implement",": With SDKs available for multiple languages, including Java",[4542,149753,149755],{"id":149754},"how-mcp-works-the-architecture","How MCP Works: The Architecture",[651,149757,149758],{},[660,149759],{"alt":149715,"src":149760},"/images/blog/2025/03/11/how_mcp_works.jpeg",[651,149762,149763],{},"MCP follows a client-server architecture:",[5909,149765,149767],{"id":149766},"mcp-clients","MCP Clients",[651,149769,149770],{},"These are applications that can connect to MCP servers and include:",[5316,149772,149773,149779,149785,149791],{},[5332,149774,149775,149778],{},[2939,149776,149777],{},"Claude Desktop",": Anthropic's desktop application for Claude",[5332,149780,149781,149784],{},[2939,149782,149783],{},"Claude Code",": A command-line tool for developers",[5332,149786,149787,149790],{},[2939,149788,149789],{},"IDE integrations",": Like Cursor and WinSurf",[5332,149792,149793,149796],{},[2939,149794,149795],{},"Custom applications",": Your own applications that implement the MCP client protocol",[5909,149798,149800],{"id":149799},"mcp-servers","MCP Servers",[651,149802,149803],{},"These are services that implement the MCP protocol to provide specific functionality:",[5316,149805,149806,149812,149818,149824],{},[5332,149807,149808,149811],{},[2939,149809,149810],{},"File system servers",": Access to read and write files",[5332,149813,149814,149817],{},[2939,149815,149816],{},"Web search servers",": Like Brave Search for accessing current web information",[5332,149819,149820,149823],{},[2939,149821,149822],{},"Database connectors",": For querying your organization's data",[5332,149825,149826,149829],{},[2939,149827,149828],{},"API integrations",": For connecting to services like GitHub, Slack, or your internal microservices",[651,149831,149832],{},"When a user asks a question that requires external information, the process works like this:",[27665,149834,149835,149838,149841,149844,149847,149850],{},[5332,149836,149837],{},"The user submits a query to the LLM through an MCP-enabled client",[5332,149839,149840],{},"The LLM recognizes it needs external data to answer the query",[5332,149842,149843],{},"The client routes the request to the appropriate MCP server",[5332,149845,149846],{},"The MCP server executes the required operation (file read, web search, API call, etc.)",[5332,149848,149849],{},"The server returns the data to the LLM",[5332,149851,149852],{},"The LLM uses this data to generate a response",[651,149854,149855],{},"This architecture allows LLMs to maintain their core functionality while seamlessly extending their capabilities through the MCP ecosystem.",[4542,149857,149859],{"id":149858},"why-mcp-matters-for-java-and-spring-developers","Why MCP Matters for Java and Spring Developers",[651,149861,149862],{},"What makes MCP particularly exciting for the Java ecosystem is that the official Java SDK for MCP was contributed by the Spring team! This means Java developers can easily build and integrate MCP servers into their Spring applications.",[651,149864,149865],{},"For Spring developers, MCP offers:",[27665,149867,149868,149874,149880,149886],{},[5332,149869,149870,149873],{},[2939,149871,149872],{},"Integration with existing applications",": Connect your Spring Boot services to LLMs without major architectural changes",[5332,149875,149876,149879],{},[2939,149877,149878],{},"Familiar development paradigm",": Use Spring's dependency injection, configuration, and security features",[5332,149881,149882,149885],{},[2939,149883,149884],{},"Enterprise-grade capabilities",": Build robust, scalable MCP servers suitable for production use",[5332,149887,149888,149891],{},[2939,149889,149890],{},"Spring AI integration",": Work seamlessly with the Spring AI project for comprehensive AI solutions",[651,149893,149894],{},"The Spring team's involvement in MCP signals the importance of this protocol in the future of enterprise AI integration. By learning MCP now, you're preparing for a significant shift in how businesses will leverage AI capabilities.",[4542,149896,149898],{"id":149897},"getting-started-with-mcp-step-by-step-guide","Getting Started with MCP: Step-by-Step Guide",[651,149900,149901],{},"Let's walk through a complete example of setting up and using MCP with Claude Desktop. This will give you a practical understanding of how MCP works before we dive into Java-specific implementations in a future tutorial.",[5909,149903,149905],{"id":149904},"step-1-install-an-mcp-client","Step 1: Install an MCP Client",[651,149907,149908,149909,664],{},"For this example, we'll use Claude Desktop, which you can download from ",[812,149910,149913],{"href":149911,"rel":149912},"https://claude.ai/desktop",[816],"claude.ai/desktop",[5909,149915,149917],{"id":149916},"step-2-configure-an-mcp-file-system-server","Step 2: Configure an MCP File System Server",[651,149919,149920],{},"The file system server allows Claude to access files on your local machine (with appropriate permissions). Here's how to set it up:",[27665,149922,149923,149926,149943],{},[5332,149924,149925],{},"Ensure you have Node.js installed on your system",[5332,149927,149928,149929],{},"Create a configuration file for Claude Desktop:",[5316,149930,149931,149937],{},[5332,149932,149933,149934],{},"On macOS: ",[676,149935,149936],{},"~/Library/Application Support/Claude/claude_desktop_config.json",[5332,149938,149939,149940],{},"On Windows: ",[676,149941,149942],{},"%APPDATA%\\Claude\\claude_desktop_config.json",[5332,149944,149945],{},"Add the following configuration to the file:",[669,149947,149949],{"className":28439,"code":149948,"language":28441,"meta":674,"style":674},"{\n \"mcp_servers\": [\n \"npx @anthropic-ai/mcp-fs --allow-paths=/your/allowed/path\"\n ]\n}\n",[676,149950,149951,149955,149962,149967,149971],{"__ignoreMap":674},[679,149952,149953],{"class":681,"line":682},[679,149954,28448],{"class":693},[679,149956,149957,149960],{"class":681,"line":790},[679,149958,149959],{"class":931}," \"mcp_servers\"",[679,149961,28491],{"class":693},[679,149963,149964],{"class":681,"line":892},[679,149965,149966],{"class":689}," \"npx @anthropic-ai/mcp-fs --allow-paths=/your/allowed/path\"\n",[679,149968,149969],{"class":681,"line":901},[679,149970,44050],{"class":693},[679,149972,149973],{"class":681,"line":909},[679,149974,996],{"class":693},[651,149976,149977,149978,149981],{},"Replace ",[676,149979,149980],{},"/your/allowed/path"," with the path you want to give Claude access to, such as your Downloads folder.",[5909,149983,149985],{"id":149984},"step-3-launch-claude-desktop","Step 3: Launch Claude Desktop",[651,149987,149988],{},"Start (or restart) Claude Desktop. If configured correctly, you should see a small plug icon in the interface indicating that MCP servers are connected. Clicking this icon will show you which servers are available.",[651,149990,149991],{},"You'll also see a hammer icon that shows all the MCP tools available to you. The file system server provides tools like:",[5316,149993,149994,149999,150004,150009,150014,150019,150024],{},[5332,149995,149996],{},[676,149997,149998],{},"createDirectory",[5332,150000,150001],{},[676,150002,150003],{},"deleteDirectory",[5332,150005,150006],{},[676,150007,150008],{},"getFileInfo",[5332,150010,150011],{},[676,150012,150013],{},"listDirectory",[5332,150015,150016],{},[676,150017,150018],{},"moveFile",[5332,150020,150021],{},[676,150022,150023],{},"readFile",[5332,150025,150026],{},[676,150027,150028],{},"writeFile",[5909,150030,150032],{"id":150031},"step-4-test-basic-file-access","Step 4: Test Basic File Access",[651,150034,150035],{},"Try asking Claude questions about your files:",[5316,150037,150038,150041,150044],{},[5332,150039,150040],{},"\"How many files are in my Downloads folder?\"",[5332,150042,150043],{},"\"What's the largest file in my Downloads folder?\"",[5332,150045,150046],{},"\"List all PDF files in my Downloads folder\"",[651,150048,150049],{},"Claude should now be able to access your file system within the allowed paths and answer these questions.",[5909,150051,150053],{"id":150052},"step-5-add-a-web-search-server","Step 5: Add a Web Search Server",[651,150055,150056],{},"Now, let's add web search capabilities to Claude Desktop:",[27665,150058,150059,150067],{},[5332,150060,150061,150062],{},"Sign up for a Brave API key at ",[812,150063,150066],{"href":150064,"rel":150065},"https://brave.com/search/api/",[816],"brave.com/search/api/",[5332,150068,150069],{},"Update your Claude Desktop configuration file:",[669,150071,150073],{"className":28439,"code":150072,"language":28441,"meta":674,"style":674},"{\n \"mcp_servers\": [\n \"npx @anthropic-ai/mcp-fs --allow-paths=/your/allowed/path\",\n \"npx @anthropic-ai/mcp-brave --api-key=YOUR_BRAVE_API_KEY\"\n ]\n}\n",[676,150074,150075,150079,150085,150092,150097,150101],{"__ignoreMap":674},[679,150076,150077],{"class":681,"line":682},[679,150078,28448],{"class":693},[679,150080,150081,150083],{"class":681,"line":790},[679,150082,149959],{"class":931},[679,150084,28491],{"class":693},[679,150086,150087,150090],{"class":681,"line":892},[679,150088,150089],{"class":689}," \"npx @anthropic-ai/mcp-fs --allow-paths=/your/allowed/path\"",[679,150091,12083],{"class":693},[679,150093,150094],{"class":681,"line":901},[679,150095,150096],{"class":689}," \"npx @anthropic-ai/mcp-brave --api-key=YOUR_BRAVE_API_KEY\"\n",[679,150098,150099],{"class":681,"line":909},[679,150100,44050],{"class":693},[679,150102,150103],{"class":681,"line":918},[679,150104,996],{"class":693},[651,150106,149977,150107,150110],{},[676,150108,150109],{},"YOUR_BRAVE_API_KEY"," with your actual API key.",[5909,150112,150114],{"id":150113},"step-6-test-web-search","Step 6: Test Web Search",[651,150116,150117],{},"After restarting Claude Desktop, you can now ask questions that require current information:",[5316,150119,150120,150123,150126],{},[5332,150121,150122],{},"\"What are the latest developments in quantum computing?\"",[5332,150124,150125],{},"\"What's the current weather in New York City?\"",[5332,150127,150128],{},"\"How many episodes of Daredevil Born Again are available now?\"",[651,150130,150131],{},"Claude should now be able to search the web and provide up-to-date answers.",[4542,150133,150135],{"id":150134},"exploring-available-mcp-servers","Exploring Available MCP Servers",[651,150137,150138],{},"There are numerous MCP servers already available that you can start using immediately. Here's a selection of popular ones:",[5909,150140,150142],{"id":150141},"_1-file-system-server","1. File System Server",[5316,150144,150145,150151,150159],{},[5332,150146,150147,150150],{},[2939,150148,150149],{},"Purpose",": Access to read and write local files",[5332,150152,150153,4282,150156],{},[2939,150154,150155],{},"Installation",[676,150157,150158],{},"npx @anthropic-ai/mcp-fs --allow-paths=/path/to/access",[5332,150160,150161,150164],{},[2939,150162,150163],{},"Use Cases",": Document analysis, local file management, working with local datasets",[5909,150166,150168],{"id":150167},"_2-brave-search","2. Brave Search",[5316,150170,150171,150176,150183],{},[5332,150172,150173,150175],{},[2939,150174,150149],{},": Web search capabilities for up-to-date information",[5332,150177,150178,4282,150180],{},[2939,150179,150155],{},[676,150181,150182],{},"npx @anthropic-ai/mcp-brave --api-key=YOUR_API_KEY",[5332,150184,150185,150187],{},[2939,150186,150163],{},": Current events, research, finding recent information outside the LLM's knowledge cutoff",[5909,150189,150191],{"id":150190},"_3-github-server","3. GitHub Server",[5316,150193,150194,150199,150206],{},[5332,150195,150196,150198],{},[2939,150197,150149],{},": Access to GitHub repositories, issues, and pull requests",[5332,150200,150201,4282,150203],{},[2939,150202,150155],{},[676,150204,150205],{},"npx @anthropic-ai/mcp-github --token=YOUR_GITHUB_TOKEN",[5332,150207,150208,150210],{},[2939,150209,150163],{},": Code analysis, repository management, issue tracking",[5909,150212,150214],{"id":150213},"_4-google-calendar","4. Google Calendar",[5316,150216,150217,150222,150227],{},[5332,150218,150219,150221],{},[2939,150220,150149],{},": Access to Google Calendar events and schedules",[5332,150223,150224,150226],{},[2939,150225,150155],{},": Available through various third-party implementations",[5332,150228,150229,150231],{},[2939,150230,150163],{},": Schedule management, meeting planning, time tracking",[5909,150233,150235],{"id":150234},"_5-slack-integration","5. Slack Integration",[5316,150237,150238,150243,150247],{},[5332,150239,150240,150242],{},[2939,150241,150149],{},": Access to Slack channels and messages",[5332,150244,150245,150226],{},[2939,150246,150155],{},[5332,150248,150249,150251],{},[2939,150250,150163],{},": Team communication, message analysis, workflow automation",[5909,150253,150255],{"id":150254},"_6-database-connectors","6. Database Connectors",[5316,150257,150258,150263,150267],{},[5332,150259,150260,150262],{},[2939,150261,150149],{},": Access to various database systems",[5332,150264,150265,150226],{},[2939,150266,150155],{},[5332,150268,150269,150271],{},[2939,150270,150163],{},": Data analysis, reporting, information retrieval",[651,150273,150274,150275,150280],{},"These are just a few examples of the growing ecosystem of MCP servers. You can find more at the ",[812,150276,150279],{"href":150277,"rel":150278},"https://modelcontextprotocol.io/docs/examples/servers",[816],"MCP Server Directory"," or through community resources like GitHub.",[4542,150282,150284],{"id":150283},"real-world-use-cases-for-mcp","Real-World Use Cases for MCP",[651,150286,150287],{},"MCP opens up numerous possibilities for applications:",[27665,150289,150290,150296,150302,150308,150314,150320],{},[5332,150291,150292,150295],{},[2939,150293,150294],{},"Customer support bots",": Access CRM data to provide personalized support",[5332,150297,150298,150301],{},[2939,150299,150300],{},"Internal knowledge bases",": Search company documentation and wikis",[5332,150303,150304,150307],{},[2939,150305,150306],{},"Development assistants",": Help developers by accessing code repositories, CI/CD systems, and issue trackers",[5332,150309,150310,150313],{},[2939,150311,150312],{},"Data analysis tools",": Access and analyze company data in real-time",[5332,150315,150316,150319],{},[2939,150317,150318],{},"Administrative assistants",": Manage calendars, emails, and internal systems",[5332,150321,150322,150325],{},[2939,150323,150324],{},"Document processing",": Extract, analyze, and process information from company documents",[5909,150327,150329],{"id":150328},"example-scenario-development-assistant","Example Scenario: Development Assistant",[651,150331,150332],{},"Imagine a development assistant powered by Claude with MCP access to:",[5316,150334,150335,150338,150341,150344],{},[5332,150336,150337],{},"Your GitHub repositories",[5332,150339,150340],{},"Your Jira issue tracker",[5332,150342,150343],{},"Your CI/CD pipeline",[5332,150345,150346],{},"Your internal documentation",[651,150348,150349],{},"With these connections, you could ask questions like:",[5316,150351,150352,150355,150358,150361],{},[5332,150353,150354],{},"\"What open issues are assigned to me?\"",[5332,150356,150357],{},"\"Show me the failed tests from the last build\"",[5332,150359,150360],{},"\"Help me understand how our authentication service works\"",[5332,150362,150363],{},"\"What code changes were made to the payment module last week?\"",[651,150365,150366],{},"The assistant could pull this information in real-time, providing you with accurate and contextual responses that would be impossible with a standard LLM.",[4542,150368,150370],{"id":150369},"best-practices-for-using-mcp","Best Practices for Using MCP",[651,150372,150373],{},"When working with MCP, consider these best practices:",[27665,150375,150376,150382,150388,150394,150400,150405,150411],{},[5332,150377,150378,150381],{},[2939,150379,150380],{},"Security First",": Only connect to trusted MCP servers and be mindful of the permissions you grant",[5332,150383,150384,150387],{},[2939,150385,150386],{},"Clear User Permissions",": Always make it clear to users what access is being granted to the LLM",[5332,150389,150390,150393],{},[2939,150391,150392],{},"Path Restrictions",": Limit file system access to specific directories to prevent accidental exposure",[5332,150395,150396,150399],{},[2939,150397,150398],{},"API Rate Limits",": Be aware of rate limits when using services like web search or GitHub",[5332,150401,150402,150404],{},[2939,150403,124060],{},": Prepare for scenarios where MCP servers might be unavailable or return errors",[5332,150406,150407,150410],{},[2939,150408,150409],{},"Data Privacy",": Be cautious about what information you allow the LLM to access",[5332,150412,150413,150415],{},[2939,150414,27574],{},": Thoroughly test MCP integrations before using them in production",[4542,150417,150419],{"id":150418},"looking-forward-the-future-of-mcp","Looking Forward: The Future of MCP",[651,150421,150422],{},"MCP is still in its early stages, but it's rapidly evolving. Here's what we can expect in the future:",[27665,150424,150425,150431,150437,150443,150449],{},[5332,150426,150427,150430],{},[2939,150428,150429],{},"Expanded Ecosystem",": More MCP servers covering additional services and data sources",[5332,150432,150433,150436],{},[2939,150434,150435],{},"Enterprise Adoption",": Increased use in business applications and workflows",[5332,150438,150439,150442],{},[2939,150440,150441],{},"Standards Development",": Further refinement of the protocol and security standards",[5332,150444,150445,150448],{},[2939,150446,150447],{},"Integration with Development Tools",": Deeper integration with IDEs and development workflows",[5332,150450,150451,150454],{},[2939,150452,150453],{},"Cross-Model Compatibility",": More LLMs supporting the MCP standard",[651,150456,150457],{},"In upcoming posts, we'll explore how to build your own MCP servers using Java and Spring, diving deep into the implementation details and best practices.",[4542,150459,9042],{"id":9041},[651,150461,150462],{},"Model Context Protocol represents a significant advancement in making AI more integrated and useful within our software systems. For Java and Spring developers, MCP offers a standardized way to connect LLMs with our applications, data sources, and services.",[651,150464,150465],{},"By learning and implementing MCP today, you'll be positioned to create more capable and context-aware AI-powered applications. The protocol is rapidly becoming the standard for AI integration, and its adoption will only accelerate.",[4542,150467,150469],{"id":150468},"resources-to-continue-your-mcp-journey","Resources to Continue Your MCP Journey",[5316,150471,150472,150484,150496,150505,150511],{},[5332,150473,150474,150477,150478,150483],{},[2939,150475,150476],{},"Official MCP Documentation",": Visit ",[812,150479,150482],{"href":150480,"rel":150481},"https://modelcontextprotocol.io",[816],"modelcontextprotocol.io"," for comprehensive documentation.",[5332,150485,150486,150489,150490,150495],{},[2939,150487,150488],{},"Spring AI GitHub Repository",": Check out the ",[812,150491,150494],{"href":150492,"rel":150493},"https://github.com/spring-projects-experimental/spring-ai",[816],"Spring AI project"," which includes MCP support.",[5332,150497,150498,150500,150501,664],{},[2939,150499,150279],{},": Browse available MCP servers at ",[812,150502,150504],{"href":150277,"rel":150503},[816],"modelcontextprotocol.io/docs/examples/servers",[5332,150506,150507,150510],{},[2939,150508,150509],{},"Java SDK Documentation",": Review the Java SDK for MCP contributed by the Spring team.",[5332,150512,150513,150516,150517,150522],{},[2939,150514,150515],{},"Claude Developer Documentation",": Explore the ",[812,150518,150521],{"href":150519,"rel":150520},"https://docs.anthropic.com/",[816],"Claude API docs"," for more information on Claude integration.",[651,150524,150525],{},"In my next tutorial, we'll show you how to build your own MCP servers using Java and Spring, allowing you to create custom integrations for your specific use cases. Stay tuned!",[651,150527,150528],{},"Are you excited about implementing MCP in your projects? What types of integrations would be most valuable for your use cases? Let me know in the comments below!",[651,150530,78024],{},[786,150532,150533],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":150535},[150536,150537,150538,150542,150543,150551,150559,150562,150563,150564,150565],{"id":149673,"depth":790,"text":149674},{"id":149709,"depth":790,"text":149710},{"id":149754,"depth":790,"text":149755,"children":150539},[150540,150541],{"id":149766,"depth":892,"text":149767},{"id":149799,"depth":892,"text":149800},{"id":149858,"depth":790,"text":149859},{"id":149897,"depth":790,"text":149898,"children":150544},[150545,150546,150547,150548,150549,150550],{"id":149904,"depth":892,"text":149905},{"id":149916,"depth":892,"text":149917},{"id":149984,"depth":892,"text":149985},{"id":150031,"depth":892,"text":150032},{"id":150052,"depth":892,"text":150053},{"id":150113,"depth":892,"text":150114},{"id":150134,"depth":790,"text":150135,"children":150552},[150553,150554,150555,150556,150557,150558],{"id":150141,"depth":892,"text":150142},{"id":150167,"depth":892,"text":150168},{"id":150190,"depth":892,"text":150191},{"id":150213,"depth":892,"text":150214},{"id":150234,"depth":892,"text":150235},{"id":150254,"depth":892,"text":150255},{"id":150283,"depth":790,"text":150284,"children":150560},[150561],{"id":150328,"depth":892,"text":150329},{"id":150369,"depth":790,"text":150370},{"id":150418,"depth":790,"text":150419},{"id":9041,"depth":790,"text":9042},{"id":150468,"depth":790,"text":150469},"Learn how Model Context Protocol (MCP) enables LLMs to access external data sources and tools, with practical implementation guides for getting started today.",{"slug":150568,"date":150569,"published":797,"author":798,"tags":150570,"video":150572,"keywords":150573},"model-context-protocol-introduction","2025-03-11T17:00:00.000Z",[123549,150571],"MCP","https://www.youtube.com/embed/nNLshWCoe0o","model context protocol, mcp, spring ai, llm, claude, anthropic, ai integration, mcp server, mcp client",{"title":18,"description":150566},"blog/2025/03/11/model-context-protocol-introduction","L1b15kMgoYet4F_ihihEg2m0zj8stPwy6fsrQx6GiJ8",{"id":150578,"title":15,"body":150579,"description":153671,"extension":793,"meta":153672,"navigation":797,"path":16,"seo":153677,"stem":153678,"__hash__":153679},"content/blog/2025/03/21/stream-gatherers.md",{"type":648,"value":150580,"toc":153649},[150581,150585,150588,150591,150595,150598,150601,150629,150697,150700,150704,150707,150714,150762,150765,150919,150926,150930,150933,150937,151124,151128,151324,151327,151331,151334,151337,151341,151344,151494,151497,151503,151507,151510,151659,151661,151667,151671,151674,151803,151805,151811,151815,151818,151948,151950,151956,151960,151963,152333,152336,152483,152485,152491,152494,152524,152528,152534,152538,152541,152863,152867,152870,153103,153107,153110,153371,153375,153378,153597,153599,153602,153605,153630,153633,153641,153644,153646],[11859,150582,150584],{"id":150583},"stream-gatherers-in-jdk-24-building-custom-stream-operations-for-data-processing","Stream Gatherers in JDK 24: Building Custom Stream Operations for Data Processing",[651,150586,150587],{},"I just got back from JavaOne and everyone was talking about the newest JDK 24 features. One that particularly caught my attention was Stream Gatherers (JEP 485), a powerful enhancement to the Stream API that lets you build custom intermediate operations.",[651,150589,150590],{},"If you've ever found yourself writing complex nested collectors or multi-step stream operations, you're going to appreciate this new capability. Let's walk through Stream Gatherers step by step with practical examples from blog content processing.",[4542,150592,150594],{"id":150593},"what-is-a-stream","What is a Stream?",[651,150596,150597],{},"Before diving into Gatherers, let's quickly refresh our understanding of Java Streams. Introduced in Java 8, the Stream API provides a functional approach to processing collections of objects.",[651,150599,150600],{},"Stream operations are divided into two categories:",[5316,150602,150603,150617],{},[5332,150604,150605,150608,150609,2797,150611,48406,150613,150616],{},[2939,150606,150607],{},"Intermediate operations"," (like ",[676,150610,65029],{},[676,150612,51904],{},[676,150614,150615],{},"sorted",") that return a new stream",[5332,150618,150619,150608,150622,2797,150624,48406,150626,150628],{},[2939,150620,150621],{},"Terminal operations",[676,150623,90908],{},[676,150625,46928],{},[676,150627,52081],{},") that produce a result",[669,150630,150632],{"className":4107,"code":150631,"language":4109,"meta":674,"style":674},"List\u003CString> filtered = names.stream() // Create a stream from names collection\n .filter(name -> name.startsWith(\"J\")) // Intermediate operation\n .collect(Collectors.toList()); // Terminal operation\n",[676,150633,150634,150655,150681],{"__ignoreMap":674},[679,150635,150636,150638,150640,150643,150645,150647,150649,150652],{"class":681,"line":682},[679,150637,125875],{"class":693},[679,150639,4758],{"class":685},[679,150641,150642],{"class":693},"> filtered ",[679,150644,686],{"class":685},[679,150646,126317],{"class":693},[679,150648,87323],{"class":880},[679,150650,150651],{"class":693},"() ",[679,150653,150654],{"class":1400},"// Create a stream from names collection\n",[679,150656,150657,150659,150661,150663,150665,150668,150670,150672,150675,150678],{"class":681,"line":790},[679,150658,40107],{"class":693},[679,150660,65029],{"class":880},[679,150662,126088],{"class":693},[679,150664,16955],{"class":685},[679,150666,150667],{"class":693}," name.",[679,150669,65048],{"class":880},[679,150671,745],{"class":693},[679,150673,150674],{"class":689},"\"J\"",[679,150676,150677],{"class":693},")) ",[679,150679,150680],{"class":1400},"// Intermediate operation\n",[679,150682,150683,150685,150687,150689,150691,150694],{"class":681,"line":892},[679,150684,40107],{"class":693},[679,150686,90908],{"class":880},[679,150688,90911],{"class":693},[679,150690,85083],{"class":880},[679,150692,150693],{"class":693},"()); ",[679,150695,150696],{"class":1400},"// Terminal operation\n",[651,150698,150699],{},"While the Stream API is powerful, sometimes we need custom intermediate operations that aren't provided out-of-the-box. That's exactly what Stream Gatherers address.",[4542,150701,150703],{"id":150702},"posts-by-category-a-real-world-example","Posts by Category: A Real-World Example",[651,150705,150706],{},"Let's start with a common blog application use case. Given a collection of blog posts, we want to find all posts in a specific category, sorted by publication date.",[651,150708,150709,150710,150713],{},"Here's our ",[676,150711,150712],{},"BlogPost"," model:",[669,150715,150717],{"className":4107,"code":150716,"language":4109,"meta":674,"style":674},"public record BlogPost(\n Long id,\n String title,\n String author,\n String content,\n String category,\n LocalDateTime publishedDate\n) {}\n",[676,150718,150719,150730,150735,150739,150743,150748,150753,150758],{"__ignoreMap":674},[679,150720,150721,150723,150725,150728],{"class":681,"line":682},[679,150722,6073],{"class":685},[679,150724,86928],{"class":685},[679,150726,150727],{"class":880}," BlogPost",[679,150729,21337],{"class":693},[679,150731,150732],{"class":681,"line":790},[679,150733,150734],{"class":693}," Long id,\n",[679,150736,150737],{"class":681,"line":892},[679,150738,139802],{"class":693},[679,150740,150741],{"class":681,"line":901},[679,150742,139807],{"class":693},[679,150744,150745],{"class":681,"line":909},[679,150746,150747],{"class":693}," String content,\n",[679,150749,150750],{"class":681,"line":918},[679,150751,150752],{"class":693}," String category,\n",[679,150754,150755],{"class":681,"line":935},[679,150756,150757],{"class":693}," LocalDateTime publishedDate\n",[679,150759,150760],{"class":681,"line":944},[679,150761,25357],{"class":693},[651,150763,150764],{},"Using the current Stream API, getting posts by category is straightforward:",[669,150766,150768],{"className":4107,"code":150767,"language":4109,"meta":674,"style":674},"public static void postsByCategory(List\u003CBlogPost> posts, String category) {\n List\u003CBlogPost> postsByCategory = posts.stream()\n .filter(p -> p.category().equals(category))\n .sorted(Comparator.comparing(BlogPost::publishedDate).reversed())\n .limit(3)\n .toList();\n \n System.out.println(\"\\nPosts by Category: \" + category);\n postsByCategory.forEach(System.out::println);\n}\n",[676,150769,150770,150793,150810,150832,150857,150870,150878,150882,150902,150915],{"__ignoreMap":674},[679,150771,150772,150774,150776,150778,150781,150784,150786,150788,150790],{"class":681,"line":682},[679,150773,6073],{"class":685},[679,150775,6092],{"class":685},[679,150777,6095],{"class":685},[679,150779,150780],{"class":880}," postsByCategory",[679,150782,150783],{"class":693},"(List",[679,150785,4505],{"class":685},[679,150787,150712],{"class":693},[679,150789,5860],{"class":685},[679,150791,150792],{"class":693}," posts, String category) {\n",[679,150794,150795,150797,150799,150802,150804,150806,150808],{"class":681,"line":790},[679,150796,84917],{"class":693},[679,150798,150712],{"class":685},[679,150800,150801],{"class":693},"> postsByCategory ",[679,150803,686],{"class":685},[679,150805,93398],{"class":693},[679,150807,87323],{"class":880},[679,150809,17545],{"class":693},[679,150811,150812,150814,150816,150818,150820,150822,150825,150827,150829],{"class":681,"line":892},[679,150813,40148],{"class":693},[679,150815,65029],{"class":880},[679,150817,110740],{"class":693},[679,150819,16955],{"class":685},[679,150821,110745],{"class":693},[679,150823,150824],{"class":880},"category",[679,150826,10541],{"class":693},[679,150828,14592],{"class":880},[679,150830,150831],{"class":693},"(category))\n",[679,150833,150834,150836,150838,150841,150844,150847,150849,150852,150855],{"class":681,"line":901},[679,150835,40148],{"class":693},[679,150837,150615],{"class":880},[679,150839,150840],{"class":693},"(Comparator.",[679,150842,150843],{"class":880},"comparing",[679,150845,150846],{"class":693},"(BlogPost",[679,150848,90007],{"class":685},[679,150850,150851],{"class":693},"publishedDate).",[679,150853,150854],{"class":880},"reversed",[679,150856,40172],{"class":693},[679,150858,150859,150861,150864,150866,150868],{"class":681,"line":909},[679,150860,40148],{"class":693},[679,150862,150863],{"class":880},"limit",[679,150865,745],{"class":693},[679,150867,66599],{"class":931},[679,150869,1339],{"class":693},[679,150871,150872,150874,150876],{"class":681,"line":918},[679,150873,40148],{"class":693},[679,150875,85083],{"class":880},[679,150877,9317],{"class":693},[679,150879,150880],{"class":681,"line":935},[679,150881,145630],{"class":693},[679,150883,150884,150886,150888,150890,150892,150894,150897,150899],{"class":681,"line":944},[679,150885,15746],{"class":693},[679,150887,1729],{"class":880},[679,150889,745],{"class":693},[679,150891,4857],{"class":689},[679,150893,56302],{"class":931},[679,150895,150896],{"class":689},"Posts by Category: \"",[679,150898,3059],{"class":685},[679,150900,150901],{"class":693}," category);\n",[679,150903,150904,150907,150909,150911,150913],{"class":681,"line":959},[679,150905,150906],{"class":693}," postsByCategory.",[679,150908,46928],{"class":880},[679,150910,97984],{"class":693},[679,150912,90007],{"class":685},[679,150914,97989],{"class":693},[679,150916,150917],{"class":681,"line":964},[679,150918,996],{"class":693},[651,150920,150921,150922,150925],{},"This works well when we're looking for posts in a single category. But what if we want to display the three most recent posts from ",[7300,150923,150924],{},"each"," category?",[4542,150927,150929],{"id":150928},"group-by-with-limit-before-jdk-24","Group By with Limit - Before JDK 24",[651,150931,150932],{},"Before JDK 24, implementing \"recent posts by category\" required more complex approaches.",[5909,150934,150936],{"id":150935},"approach-1-nested-collectors","Approach 1: Nested Collectors",[669,150938,150940],{"className":4107,"code":150939,"language":4109,"meta":674,"style":674},"public static void nestedCollectors(List\u003CBlogPost> posts) {\n Map\u003CString, List\u003CBlogPost>> recentPostsByCategory = posts.stream()\n // First, group all posts by category\n .collect(Collectors.groupingBy(\n BlogPost::category,\n Collectors.collectingAndThen(\n // Collect posts into a list\n Collectors.toList(),\n // Then transform each list by sorting and limiting\n categoryPosts -> categoryPosts.stream()\n .sorted(Comparator.comparing(BlogPost::publishedDate).reversed())\n .limit(3)\n .toList()\n )\n ));\n\n printRecentPostsByCategory(recentPostsByCategory);\n}\n",[676,150941,150942,150964,150987,150992,151005,151015,151025,151030,151039,151044,151058,151079,151091,151099,151103,151108,151112,151120],{"__ignoreMap":674},[679,150943,150944,150946,150948,150950,150953,150955,150957,150959,150961],{"class":681,"line":682},[679,150945,6073],{"class":685},[679,150947,6092],{"class":685},[679,150949,6095],{"class":685},[679,150951,150952],{"class":880}," nestedCollectors",[679,150954,150783],{"class":693},[679,150956,4505],{"class":685},[679,150958,150712],{"class":693},[679,150960,5860],{"class":685},[679,150962,150963],{"class":693}," posts) {\n",[679,150965,150966,150969,150971,150974,150976,150979,150981,150983,150985],{"class":681,"line":790},[679,150967,150968],{"class":693}," Map\u003C",[679,150970,4758],{"class":685},[679,150972,150973],{"class":693},", List\u003C",[679,150975,150712],{"class":685},[679,150977,150978],{"class":693},">> recentPostsByCategory ",[679,150980,686],{"class":685},[679,150982,93398],{"class":693},[679,150984,87323],{"class":880},[679,150986,17545],{"class":693},[679,150988,150989],{"class":681,"line":892},[679,150990,150991],{"class":1400}," // First, group all posts by category\n",[679,150993,150994,150996,150998,151000,151003],{"class":681,"line":901},[679,150995,40148],{"class":693},[679,150997,90908],{"class":880},[679,150999,90911],{"class":693},[679,151001,151002],{"class":880},"groupingBy",[679,151004,21337],{"class":693},[679,151006,151007,151010,151012],{"class":681,"line":909},[679,151008,151009],{"class":693}," BlogPost",[679,151011,90007],{"class":685},[679,151013,151014],{"class":693},"category,\n",[679,151016,151017,151020,151023],{"class":681,"line":918},[679,151018,151019],{"class":693}," Collectors.",[679,151021,151022],{"class":880},"collectingAndThen",[679,151024,21337],{"class":693},[679,151026,151027],{"class":681,"line":935},[679,151028,151029],{"class":1400}," // Collect posts into a list\n",[679,151031,151032,151035,151037],{"class":681,"line":944},[679,151033,151034],{"class":693}," Collectors.",[679,151036,85083],{"class":880},[679,151038,56208],{"class":693},[679,151040,151041],{"class":681,"line":959},[679,151042,151043],{"class":1400}," // Then transform each list by sorting and limiting\n",[679,151045,151046,151049,151051,151054,151056],{"class":681,"line":964},[679,151047,151048],{"class":693}," categoryPosts ",[679,151050,16955],{"class":685},[679,151052,151053],{"class":693}," categoryPosts.",[679,151055,87323],{"class":880},[679,151057,17545],{"class":693},[679,151059,151060,151063,151065,151067,151069,151071,151073,151075,151077],{"class":681,"line":977},[679,151061,151062],{"class":693}," .",[679,151064,150615],{"class":880},[679,151066,150840],{"class":693},[679,151068,150843],{"class":880},[679,151070,150846],{"class":693},[679,151072,90007],{"class":685},[679,151074,150851],{"class":693},[679,151076,150854],{"class":880},[679,151078,40172],{"class":693},[679,151080,151081,151083,151085,151087,151089],{"class":681,"line":982},[679,151082,151062],{"class":693},[679,151084,150863],{"class":880},[679,151086,745],{"class":693},[679,151088,66599],{"class":931},[679,151090,1339],{"class":693},[679,151092,151093,151095,151097],{"class":681,"line":988},[679,151094,151062],{"class":693},[679,151096,85083],{"class":880},[679,151098,17545],{"class":693},[679,151100,151101],{"class":681,"line":993},[679,151102,85075],{"class":693},[679,151104,151105],{"class":681,"line":2129},[679,151106,151107],{"class":693}," ));\n",[679,151109,151110],{"class":681,"line":2140},[679,151111,889],{"emptyLinePlaceholder":797},[679,151113,151114,151117],{"class":681,"line":2145},[679,151115,151116],{"class":880}," printRecentPostsByCategory",[679,151118,151119],{"class":693},"(recentPostsByCategory);\n",[679,151121,151122],{"class":681,"line":2154},[679,151123,996],{"class":693},[5909,151125,151127],{"id":151126},"approach-2-map-then-transform","Approach 2: Map Then Transform",[669,151129,151131],{"className":4107,"code":151130,"language":4109,"meta":674,"style":674},"public static void mapThenTransform(List\u003CBlogPost> posts) {\n Map\u003CString, List\u003CBlogPost>> recentPostsByCategory = posts.stream()\n // Group by category\n .collect(Collectors.groupingBy(BlogPost::category))\n // Convert to a stream of map entries\n .entrySet().stream()\n // Convert each entry to a new entry with sorted and limited values\n .collect(Collectors.toMap(\n Map.Entry::getKey,\n entry -> entry.getValue().stream()\n .sorted(Comparator.comparing(BlogPost::publishedDate).reversed())\n .limit(3)\n .collect(Collectors.toList())\n ));\n\n printRecentPostsByCategory(recentPostsByCategory);\n}\n",[676,151132,151133,151154,151174,151179,151196,151201,151214,151219,151232,151242,151261,151282,151294,151306,151310,151314,151320],{"__ignoreMap":674},[679,151134,151135,151137,151139,151141,151144,151146,151148,151150,151152],{"class":681,"line":682},[679,151136,6073],{"class":685},[679,151138,6092],{"class":685},[679,151140,6095],{"class":685},[679,151142,151143],{"class":880}," mapThenTransform",[679,151145,150783],{"class":693},[679,151147,4505],{"class":685},[679,151149,150712],{"class":693},[679,151151,5860],{"class":685},[679,151153,150963],{"class":693},[679,151155,151156,151158,151160,151162,151164,151166,151168,151170,151172],{"class":681,"line":790},[679,151157,150968],{"class":693},[679,151159,4758],{"class":685},[679,151161,150973],{"class":693},[679,151163,150712],{"class":685},[679,151165,150978],{"class":693},[679,151167,686],{"class":685},[679,151169,93398],{"class":693},[679,151171,87323],{"class":880},[679,151173,17545],{"class":693},[679,151175,151176],{"class":681,"line":892},[679,151177,151178],{"class":1400}," // Group by category\n",[679,151180,151181,151183,151185,151187,151189,151191,151193],{"class":681,"line":901},[679,151182,40148],{"class":693},[679,151184,90908],{"class":880},[679,151186,90911],{"class":693},[679,151188,151002],{"class":880},[679,151190,150846],{"class":693},[679,151192,90007],{"class":685},[679,151194,151195],{"class":693},"category))\n",[679,151197,151198],{"class":681,"line":909},[679,151199,151200],{"class":1400}," // Convert to a stream of map entries\n",[679,151202,151203,151205,151208,151210,151212],{"class":681,"line":918},[679,151204,40148],{"class":693},[679,151206,151207],{"class":880},"entrySet",[679,151209,10541],{"class":693},[679,151211,87323],{"class":880},[679,151213,17545],{"class":693},[679,151215,151216],{"class":681,"line":935},[679,151217,151218],{"class":1400}," // Convert each entry to a new entry with sorted and limited values\n",[679,151220,151221,151223,151225,151227,151230],{"class":681,"line":944},[679,151222,40148],{"class":693},[679,151224,90908],{"class":880},[679,151226,90911],{"class":693},[679,151228,151229],{"class":880},"toMap",[679,151231,21337],{"class":693},[679,151233,151234,151237,151239],{"class":681,"line":959},[679,151235,151236],{"class":693}," Map.Entry",[679,151238,90007],{"class":685},[679,151240,151241],{"class":693},"getKey,\n",[679,151243,151244,151247,151249,151252,151255,151257,151259],{"class":681,"line":964},[679,151245,151246],{"class":693}," entry ",[679,151248,16955],{"class":685},[679,151250,151251],{"class":693}," entry.",[679,151253,151254],{"class":880},"getValue",[679,151256,10541],{"class":693},[679,151258,87323],{"class":880},[679,151260,17545],{"class":693},[679,151262,151263,151266,151268,151270,151272,151274,151276,151278,151280],{"class":681,"line":977},[679,151264,151265],{"class":693}," .",[679,151267,150615],{"class":880},[679,151269,150840],{"class":693},[679,151271,150843],{"class":880},[679,151273,150846],{"class":693},[679,151275,90007],{"class":685},[679,151277,150851],{"class":693},[679,151279,150854],{"class":880},[679,151281,40172],{"class":693},[679,151283,151284,151286,151288,151290,151292],{"class":681,"line":982},[679,151285,151265],{"class":693},[679,151287,150863],{"class":880},[679,151289,745],{"class":693},[679,151291,66599],{"class":931},[679,151293,1339],{"class":693},[679,151295,151296,151298,151300,151302,151304],{"class":681,"line":988},[679,151297,151265],{"class":693},[679,151299,90908],{"class":880},[679,151301,90911],{"class":693},[679,151303,85083],{"class":880},[679,151305,40172],{"class":693},[679,151307,151308],{"class":681,"line":993},[679,151309,151107],{"class":693},[679,151311,151312],{"class":681,"line":2129},[679,151313,889],{"emptyLinePlaceholder":797},[679,151315,151316,151318],{"class":681,"line":2140},[679,151317,151116],{"class":880},[679,151319,151119],{"class":693},[679,151321,151322],{"class":681,"line":2145},[679,151323,996],{"class":693},[651,151325,151326],{},"Both approaches work, but they're complex, verbose, and require multiple stream operations or nested collectors.",[4542,151328,151330],{"id":151329},"introduction-to-stream-gatherers","Introduction to Stream Gatherers",[651,151332,151333],{},"JDK 24 introduces Stream Gatherers, which let us create custom intermediate operations to handle complex transformations more elegantly.",[651,151335,151336],{},"Let's look at some fundamental Gatherer patterns that come built-in with JDK 24:",[5909,151338,151340],{"id":151339},"fixed-window-example","Fixed Window Example",[651,151342,151343],{},"A fixed window gatherer processes elements in non-overlapping groups of a fixed size:",[669,151345,151347],{"className":4107,"code":151346,"language":4109,"meta":674,"style":674},"public static void fixedWindowExample(List\u003CBlogPost> posts) {\n // Group posts into batches of 3\n System.out.println(\"Posts in batches of 3:\");\n posts.stream()\n .limit(9) // Limit to 9 posts for clarity\n .gather(Gatherers.windowFixed(3))\n .forEach(batch -> {\n System.out.println(\"\\nBatch:\");\n batch.forEach(post -> System.out.println(\" - \" + post.title()));\n });\n}\n",[676,151348,151349,151370,151375,151388,151396,151411,151427,151440,151457,151485,151490],{"__ignoreMap":674},[679,151350,151351,151353,151355,151357,151360,151362,151364,151366,151368],{"class":681,"line":682},[679,151352,6073],{"class":685},[679,151354,6092],{"class":685},[679,151356,6095],{"class":685},[679,151358,151359],{"class":880}," fixedWindowExample",[679,151361,150783],{"class":693},[679,151363,4505],{"class":685},[679,151365,150712],{"class":693},[679,151367,5860],{"class":685},[679,151369,150963],{"class":693},[679,151371,151372],{"class":681,"line":790},[679,151373,151374],{"class":1400}," // Group posts into batches of 3\n",[679,151376,151377,151379,151381,151383,151386],{"class":681,"line":892},[679,151378,15746],{"class":693},[679,151380,1729],{"class":880},[679,151382,745],{"class":693},[679,151384,151385],{"class":689},"\"Posts in batches of 3:\"",[679,151387,1208],{"class":693},[679,151389,151390,151392,151394],{"class":681,"line":901},[679,151391,93593],{"class":693},[679,151393,87323],{"class":880},[679,151395,17545],{"class":693},[679,151397,151398,151400,151402,151404,151406,151408],{"class":681,"line":909},[679,151399,40148],{"class":693},[679,151401,150863],{"class":880},[679,151403,745],{"class":693},[679,151405,66844],{"class":931},[679,151407,2378],{"class":693},[679,151409,151410],{"class":1400},"// Limit to 9 posts for clarity\n",[679,151412,151413,151415,151417,151419,151421,151423,151425],{"class":681,"line":918},[679,151414,40148],{"class":693},[679,151416,131171],{"class":880},[679,151418,131174],{"class":693},[679,151420,131177],{"class":880},[679,151422,745],{"class":693},[679,151424,66599],{"class":931},[679,151426,40143],{"class":693},[679,151428,151429,151431,151433,151436,151438],{"class":681,"line":935},[679,151430,40148],{"class":693},[679,151432,46928],{"class":880},[679,151434,151435],{"class":693},"(batch ",[679,151437,16955],{"class":685},[679,151439,884],{"class":693},[679,151441,151442,151444,151446,151448,151450,151452,151455],{"class":681,"line":944},[679,151443,34451],{"class":693},[679,151445,1729],{"class":880},[679,151447,745],{"class":693},[679,151449,4857],{"class":689},[679,151451,56302],{"class":931},[679,151453,151454],{"class":689},"Batch:\"",[679,151456,1208],{"class":693},[679,151458,151459,151462,151464,151466,151468,151470,151472,151474,151477,151479,151481,151483],{"class":681,"line":959},[679,151460,151461],{"class":693}," batch.",[679,151463,46928],{"class":880},[679,151465,110585],{"class":693},[679,151467,16955],{"class":685},[679,151469,125923],{"class":693},[679,151471,1729],{"class":880},[679,151473,745],{"class":693},[679,151475,151476],{"class":689},"\" - \"",[679,151478,3059],{"class":685},[679,151480,110590],{"class":693},[679,151482,11750],{"class":880},[679,151484,111968],{"class":693},[679,151486,151487],{"class":681,"line":964},[679,151488,151489],{"class":693}," });\n",[679,151491,151492],{"class":681,"line":977},[679,151493,996],{"class":693},[651,151495,151496],{},"Output:",[669,151498,151501],{"className":151499,"code":151500,"language":11464},[16247],"Posts in batches of 3:\n\nBatch:\n - Getting Started with JDK 24 Stream Gatherers\n - Virtual Threads in JDK 24: Performance Analysis\n - Mastering Java Records for Clean Code\n\nBatch:\n - Pattern Matching for Switch: JDK 24 Updates\n - Java Memory Management Tuning Tips\n - Building Reactive APIs with Spring WebFlux\n...\n",[676,151502,151500],{"__ignoreMap":674},[5909,151504,151506],{"id":151505},"sliding-window-example","Sliding Window Example",[651,151508,151509],{},"A sliding window moves through the elements one at a time:",[669,151511,151513],{"className":4107,"code":151512,"language":4109,"meta":674,"style":674},"public static void slidingWindowExample(List\u003CBlogPost> posts) {\n // Show posts in sliding windows of size 2\n System.out.println(\"Posts in sliding windows of size 2:\");\n posts.stream()\n .limit(5) // Limit to 5 posts for clarity\n .gather(Gatherers.windowSliding(2))\n .forEach(window -> {\n System.out.println(\"\\nWindow:\");\n window.forEach(post -> System.out.println(\" - \" + post.title()));\n });\n}\n",[676,151514,151515,151536,151541,151554,151562,151577,151594,151607,151624,151651,151655],{"__ignoreMap":674},[679,151516,151517,151519,151521,151523,151526,151528,151530,151532,151534],{"class":681,"line":682},[679,151518,6073],{"class":685},[679,151520,6092],{"class":685},[679,151522,6095],{"class":685},[679,151524,151525],{"class":880}," slidingWindowExample",[679,151527,150783],{"class":693},[679,151529,4505],{"class":685},[679,151531,150712],{"class":693},[679,151533,5860],{"class":685},[679,151535,150963],{"class":693},[679,151537,151538],{"class":681,"line":790},[679,151539,151540],{"class":1400}," // Show posts in sliding windows of size 2\n",[679,151542,151543,151545,151547,151549,151552],{"class":681,"line":892},[679,151544,15746],{"class":693},[679,151546,1729],{"class":880},[679,151548,745],{"class":693},[679,151550,151551],{"class":689},"\"Posts in sliding windows of size 2:\"",[679,151553,1208],{"class":693},[679,151555,151556,151558,151560],{"class":681,"line":901},[679,151557,93593],{"class":693},[679,151559,87323],{"class":880},[679,151561,17545],{"class":693},[679,151563,151564,151566,151568,151570,151572,151574],{"class":681,"line":909},[679,151565,40148],{"class":693},[679,151567,150863],{"class":880},[679,151569,745],{"class":693},[679,151571,66775],{"class":931},[679,151573,2378],{"class":693},[679,151575,151576],{"class":1400},"// Limit to 5 posts for clarity\n",[679,151578,151579,151581,151583,151585,151588,151590,151592],{"class":681,"line":918},[679,151580,40148],{"class":693},[679,151582,131171],{"class":880},[679,151584,131174],{"class":693},[679,151586,151587],{"class":880},"windowSliding",[679,151589,745],{"class":693},[679,151591,17262],{"class":931},[679,151593,40143],{"class":693},[679,151595,151596,151598,151600,151603,151605],{"class":681,"line":935},[679,151597,40148],{"class":693},[679,151599,46928],{"class":880},[679,151601,151602],{"class":693},"(window ",[679,151604,16955],{"class":685},[679,151606,884],{"class":693},[679,151608,151609,151611,151613,151615,151617,151619,151622],{"class":681,"line":944},[679,151610,34451],{"class":693},[679,151612,1729],{"class":880},[679,151614,745],{"class":693},[679,151616,4857],{"class":689},[679,151618,56302],{"class":931},[679,151620,151621],{"class":689},"Window:\"",[679,151623,1208],{"class":693},[679,151625,151626,151629,151631,151633,151635,151637,151639,151641,151643,151645,151647,151649],{"class":681,"line":959},[679,151627,151628],{"class":693}," window.",[679,151630,46928],{"class":880},[679,151632,110585],{"class":693},[679,151634,16955],{"class":685},[679,151636,125923],{"class":693},[679,151638,1729],{"class":880},[679,151640,745],{"class":693},[679,151642,151476],{"class":689},[679,151644,3059],{"class":685},[679,151646,110590],{"class":693},[679,151648,11750],{"class":880},[679,151650,111968],{"class":693},[679,151652,151653],{"class":681,"line":964},[679,151654,151489],{"class":693},[679,151656,151657],{"class":681,"line":977},[679,151658,996],{"class":693},[651,151660,151496],{},[669,151662,151665],{"className":151663,"code":151664,"language":11464},[16247],"Posts in sliding windows of size 2:\n\nWindow:\n - Getting Started with JDK 24 Stream Gatherers\n - Virtual Threads in JDK 24: Performance Analysis\n\nWindow:\n - Virtual Threads in JDK 24: Performance Analysis\n - Mastering Java Records for Clean Code\n...\n",[676,151666,151664],{"__ignoreMap":674},[5909,151668,151670],{"id":151669},"fold-example","Fold Example",[651,151672,151673],{},"The fold operation accumulates results:",[669,151675,151677],{"className":4107,"code":151676,"language":4109,"meta":674,"style":674},"public static void foldExample(List\u003CBlogPost> posts) {\n // Concatenate all blog post titles\n posts.stream()\n .limit(5) // Limit to 5 posts for clarity\n .gather(Gatherers.fold(\n () -> \"All titles: \",\n (result, post) -> result + post.title() + \", \"\n ))\n .findFirst()\n .ifPresent(System.out::println);\n}\n",[676,151678,151679,151700,151705,151713,151727,151740,151752,151774,151779,151787,151799],{"__ignoreMap":674},[679,151680,151681,151683,151685,151687,151690,151692,151694,151696,151698],{"class":681,"line":682},[679,151682,6073],{"class":685},[679,151684,6092],{"class":685},[679,151686,6095],{"class":685},[679,151688,151689],{"class":880}," foldExample",[679,151691,150783],{"class":693},[679,151693,4505],{"class":685},[679,151695,150712],{"class":693},[679,151697,5860],{"class":685},[679,151699,150963],{"class":693},[679,151701,151702],{"class":681,"line":790},[679,151703,151704],{"class":1400}," // Concatenate all blog post titles\n",[679,151706,151707,151709,151711],{"class":681,"line":892},[679,151708,93593],{"class":693},[679,151710,87323],{"class":880},[679,151712,17545],{"class":693},[679,151714,151715,151717,151719,151721,151723,151725],{"class":681,"line":901},[679,151716,40148],{"class":693},[679,151718,150863],{"class":880},[679,151720,745],{"class":693},[679,151722,66775],{"class":931},[679,151724,2378],{"class":693},[679,151726,151576],{"class":1400},[679,151728,151729,151731,151733,151735,151738],{"class":681,"line":909},[679,151730,40148],{"class":693},[679,151732,131171],{"class":880},[679,151734,131174],{"class":693},[679,151736,151737],{"class":880},"fold",[679,151739,21337],{"class":693},[679,151741,151742,151745,151747,151750],{"class":681,"line":918},[679,151743,151744],{"class":693}," () ",[679,151746,16955],{"class":685},[679,151748,151749],{"class":689}," \"All titles: \"",[679,151751,12083],{"class":693},[679,151753,151754,151757,151759,151761,151763,151765,151767,151769,151771],{"class":681,"line":935},[679,151755,151756],{"class":693}," (result, post) ",[679,151758,16955],{"class":685},[679,151760,14939],{"class":693},[679,151762,3065],{"class":685},[679,151764,110590],{"class":693},[679,151766,11750],{"class":880},[679,151768,6700],{"class":693},[679,151770,3065],{"class":685},[679,151772,151773],{"class":689}," \", \"\n",[679,151775,151776],{"class":681,"line":944},[679,151777,151778],{"class":693}," ))\n",[679,151780,151781,151783,151785],{"class":681,"line":959},[679,151782,40148],{"class":693},[679,151784,87355],{"class":880},[679,151786,17545],{"class":693},[679,151788,151789,151791,151793,151795,151797],{"class":681,"line":964},[679,151790,40148],{"class":693},[679,151792,109723],{"class":880},[679,151794,97984],{"class":693},[679,151796,90007],{"class":685},[679,151798,97989],{"class":693},[679,151800,151801],{"class":681,"line":977},[679,151802,996],{"class":693},[651,151804,151496],{},[669,151806,151809],{"className":151807,"code":151808,"language":11464},[16247],"All titles: Getting Started with JDK 24 Stream Gatherers, Virtual Threads in JDK 24: Performance Analysis, Mastering Java Records for Clean Code, Pattern Matching for Switch: JDK 24 Updates, Java Memory Management Tuning Tips, \n",[676,151810,151808],{"__ignoreMap":674},[5909,151812,151814],{"id":151813},"scan-example","Scan Example",[651,151816,151817],{},"The scan operation is similar to fold but emits intermediate results:",[669,151819,151821],{"className":4107,"code":151820,"language":4109,"meta":674,"style":674},"public static void scanExample(List\u003CBlogPost> posts) {\n // Build a progressive summary of post titles\n System.out.println(\"Progressive title concatenation:\");\n posts.stream()\n .limit(5) // Limit to 5 posts for clarity\n .gather(Gatherers.scan(\n () -> \"Titles so far: \",\n (result, post) -> result + post.title() + \", \"\n ))\n .forEach(System.out::println);\n}\n",[676,151822,151823,151844,151849,151862,151870,151884,151897,151908,151928,151932,151944],{"__ignoreMap":674},[679,151824,151825,151827,151829,151831,151834,151836,151838,151840,151842],{"class":681,"line":682},[679,151826,6073],{"class":685},[679,151828,6092],{"class":685},[679,151830,6095],{"class":685},[679,151832,151833],{"class":880}," scanExample",[679,151835,150783],{"class":693},[679,151837,4505],{"class":685},[679,151839,150712],{"class":693},[679,151841,5860],{"class":685},[679,151843,150963],{"class":693},[679,151845,151846],{"class":681,"line":790},[679,151847,151848],{"class":1400}," // Build a progressive summary of post titles\n",[679,151850,151851,151853,151855,151857,151860],{"class":681,"line":892},[679,151852,15746],{"class":693},[679,151854,1729],{"class":880},[679,151856,745],{"class":693},[679,151858,151859],{"class":689},"\"Progressive title concatenation:\"",[679,151861,1208],{"class":693},[679,151863,151864,151866,151868],{"class":681,"line":901},[679,151865,93593],{"class":693},[679,151867,87323],{"class":880},[679,151869,17545],{"class":693},[679,151871,151872,151874,151876,151878,151880,151882],{"class":681,"line":909},[679,151873,40148],{"class":693},[679,151875,150863],{"class":880},[679,151877,745],{"class":693},[679,151879,66775],{"class":931},[679,151881,2378],{"class":693},[679,151883,151576],{"class":1400},[679,151885,151886,151888,151890,151892,151895],{"class":681,"line":918},[679,151887,40148],{"class":693},[679,151889,131171],{"class":880},[679,151891,131174],{"class":693},[679,151893,151894],{"class":880},"scan",[679,151896,21337],{"class":693},[679,151898,151899,151901,151903,151906],{"class":681,"line":935},[679,151900,151744],{"class":693},[679,151902,16955],{"class":685},[679,151904,151905],{"class":689}," \"Titles so far: \"",[679,151907,12083],{"class":693},[679,151909,151910,151912,151914,151916,151918,151920,151922,151924,151926],{"class":681,"line":944},[679,151911,151756],{"class":693},[679,151913,16955],{"class":685},[679,151915,14939],{"class":693},[679,151917,3065],{"class":685},[679,151919,110590],{"class":693},[679,151921,11750],{"class":880},[679,151923,6700],{"class":693},[679,151925,3065],{"class":685},[679,151927,151773],{"class":689},[679,151929,151930],{"class":681,"line":959},[679,151931,151778],{"class":693},[679,151933,151934,151936,151938,151940,151942],{"class":681,"line":964},[679,151935,40148],{"class":693},[679,151937,46928],{"class":880},[679,151939,97984],{"class":693},[679,151941,90007],{"class":685},[679,151943,97989],{"class":693},[679,151945,151946],{"class":681,"line":977},[679,151947,996],{"class":693},[651,151949,151496],{},[669,151951,151954],{"className":151952,"code":151953,"language":11464},[16247],"Progressive title concatenation:\nTitles so far: Getting Started with JDK 24 Stream Gatherers, \nTitles so far: Getting Started with JDK 24 Stream Gatherers, Virtual Threads in JDK 24: Performance Analysis, \nTitles so far: Getting Started with JDK 24 Stream Gatherers, Virtual Threads in JDK 24: Performance Analysis, Mastering Java Records for Clean Code, \n...\n",[676,151955,151953],{"__ignoreMap":674},[4542,151957,151959],{"id":151958},"group-by-limit-using-custom-gatherer","Group By Limit Using Custom Gatherer",[651,151961,151962],{},"Now let's solve our \"recent posts by category\" problem using a custom Gatherer:",[669,151964,151966],{"className":4107,"code":151965,"language":4109,"meta":674,"style":674},"public static \u003CK> Gatherer\u003CBlogPost, Map\u003CK, List\u003CBlogPost>>, Map.Entry\u003CK, List\u003CBlogPost>>> groupByWithLimit(\n Function\u003C? super BlogPost, ? extends K> keyExtractor,\n int limit,\n Comparator\u003C? super BlogPost> comparator) {\n\n return Gatherer.of(\n // Initialize with an empty map to store our grouped items\n HashMap\u003CK, List\u003CBlogPost>>::new,\n\n // Process each blog post\n (map, post, downstream) -> {\n // Get the key for this blog post (e.g., the category)\n K key = keyExtractor.apply(post);\n\n // Add this post to its group (creating the group if needed)\n map.computeIfAbsent(key, k -> new ArrayList\u003C>()).add(post);\n\n // Continue processing the stream\n return true;\n },\n\n // Combiner for parallel streams - just use the first map in this simple case\n (map1, map2) -> map1,\n\n // When all posts have been processed, emit the results\n (map, downstream) -> {\n map.forEach((key, posts) -> {\n // Sort the posts and limit to the specified number\n List\u003CBlogPost> limitedPosts = posts.stream()\n .sorted(comparator)\n .limit(limit)\n .toList();\n\n // Emit a Map.Entry with the key and limited posts\n downstream.push(Map.entry(key, limitedPosts));\n });\n }\n );\n}\n",[676,151967,151968,152019,152042,152049,152065,152069,152080,152085,152103,152107,152112,152121,152126,152140,152144,152149,152171,152175,152180,152188,152193,152197,152202,152212,152216,152221,152230,152243,152248,152266,152275,152284,152292,152296,152301,152316,152321,152325,152329],{"__ignoreMap":674},[679,151969,151970,151972,151974,151976,151979,151981,151984,151986,151989,151991,151994,151996,151998,152000,152003,152005,152007,152009,152011,152014,152017],{"class":681,"line":682},[679,151971,6073],{"class":685},[679,151973,6092],{"class":685},[679,151975,48609],{"class":685},[679,151977,151978],{"class":693},"K",[679,151980,5860],{"class":685},[679,151982,151983],{"class":693}," Gatherer",[679,151985,4505],{"class":685},[679,151987,151988],{"class":693},"BlogPost, Map",[679,151990,4505],{"class":685},[679,151992,151993],{"class":693},"K, List",[679,151995,4505],{"class":685},[679,151997,150712],{"class":693},[679,151999,96110],{"class":685},[679,152001,152002],{"class":693},", Map.Entry",[679,152004,4505],{"class":685},[679,152006,151993],{"class":693},[679,152008,4505],{"class":685},[679,152010,150712],{"class":693},[679,152012,152013],{"class":685},">>>",[679,152015,152016],{"class":880}," groupByWithLimit",[679,152018,21337],{"class":693},[679,152020,152021,152024,152026,152029,152032,152034,152037,152039],{"class":681,"line":790},[679,152022,152023],{"class":693}," Function",[679,152025,20231],{"class":685},[679,152027,152028],{"class":931}," super",[679,152030,152031],{"class":693}," BlogPost, ",[679,152033,2381],{"class":685},[679,152035,152036],{"class":693}," extends K",[679,152038,5860],{"class":685},[679,152040,152041],{"class":693}," keyExtractor,\n",[679,152043,152044,152046],{"class":681,"line":892},[679,152045,14936],{"class":685},[679,152047,152048],{"class":693}," limit,\n",[679,152050,152051,152054,152056,152058,152060,152062],{"class":681,"line":901},[679,152052,152053],{"class":693}," Comparator",[679,152055,20231],{"class":685},[679,152057,152028],{"class":931},[679,152059,150727],{"class":693},[679,152061,5860],{"class":685},[679,152063,152064],{"class":693}," comparator) {\n",[679,152066,152067],{"class":681,"line":909},[679,152068,889],{"emptyLinePlaceholder":797},[679,152070,152071,152073,152076,152078],{"class":681,"line":918},[679,152072,21478],{"class":685},[679,152074,152075],{"class":693}," Gatherer.",[679,152077,16672],{"class":880},[679,152079,21337],{"class":693},[679,152081,152082],{"class":681,"line":935},[679,152083,152084],{"class":1400}," // Initialize with an empty map to store our grouped items\n",[679,152086,152087,152090,152092,152094,152096,152098,152101],{"class":681,"line":944},[679,152088,152089],{"class":693}," HashMap",[679,152091,4505],{"class":685},[679,152093,151993],{"class":693},[679,152095,4505],{"class":685},[679,152097,150712],{"class":693},[679,152099,152100],{"class":685},">>::new",[679,152102,12083],{"class":693},[679,152104,152105],{"class":681,"line":959},[679,152106,889],{"emptyLinePlaceholder":797},[679,152108,152109],{"class":681,"line":964},[679,152110,152111],{"class":1400}," // Process each blog post\n",[679,152113,152114,152117,152119],{"class":681,"line":977},[679,152115,152116],{"class":693}," (map, post, downstream) ",[679,152118,16955],{"class":685},[679,152120,884],{"class":693},[679,152122,152123],{"class":681,"line":982},[679,152124,152125],{"class":1400}," // Get the key for this blog post (e.g., the category)\n",[679,152127,152128,152131,152133,152136,152138],{"class":681,"line":988},[679,152129,152130],{"class":693}," K key ",[679,152132,686],{"class":685},[679,152134,152135],{"class":693}," keyExtractor.",[679,152137,140469],{"class":880},[679,152139,110679],{"class":693},[679,152141,152142],{"class":681,"line":993},[679,152143,889],{"emptyLinePlaceholder":797},[679,152145,152146],{"class":681,"line":2129},[679,152147,152148],{"class":1400}," // Add this post to its group (creating the group if needed)\n",[679,152150,152151,152154,152157,152160,152162,152164,152167,152169],{"class":681,"line":2140},[679,152152,152153],{"class":693}," map.",[679,152155,152156],{"class":880},"computeIfAbsent",[679,152158,152159],{"class":693},"(key, k ",[679,152161,16955],{"class":685},[679,152163,2054],{"class":685},[679,152165,152166],{"class":693}," ArrayList\u003C>()).",[679,152168,12952],{"class":880},[679,152170,110679],{"class":693},[679,152172,152173],{"class":681,"line":2145},[679,152174,889],{"emptyLinePlaceholder":797},[679,152176,152177],{"class":681,"line":2154},[679,152178,152179],{"class":1400}," // Continue processing the stream\n",[679,152181,152182,152184,152186],{"class":681,"line":2159},[679,152183,143712],{"class":685},[679,152185,14523],{"class":931},[679,152187,1186],{"class":693},[679,152189,152190],{"class":681,"line":2164},[679,152191,152192],{"class":693}," },\n",[679,152194,152195],{"class":681,"line":3134},[679,152196,889],{"emptyLinePlaceholder":797},[679,152198,152199],{"class":681,"line":3139},[679,152200,152201],{"class":1400}," // Combiner for parallel streams - just use the first map in this simple case\n",[679,152203,152204,152207,152209],{"class":681,"line":3144},[679,152205,152206],{"class":693}," (map1, map2) ",[679,152208,16955],{"class":685},[679,152210,152211],{"class":693}," map1,\n",[679,152213,152214],{"class":681,"line":3149},[679,152215,889],{"emptyLinePlaceholder":797},[679,152217,152218],{"class":681,"line":3169},[679,152219,152220],{"class":1400}," // When all posts have been processed, emit the results\n",[679,152222,152223,152226,152228],{"class":681,"line":3185},[679,152224,152225],{"class":693}," (map, downstream) ",[679,152227,16955],{"class":685},[679,152229,884],{"class":693},[679,152231,152232,152234,152236,152239,152241],{"class":681,"line":3194},[679,152233,152153],{"class":693},[679,152235,46928],{"class":880},[679,152237,152238],{"class":693},"((key, posts) ",[679,152240,16955],{"class":685},[679,152242,884],{"class":693},[679,152244,152245],{"class":681,"line":3199},[679,152246,152247],{"class":1400}," // Sort the posts and limit to the specified number\n",[679,152249,152250,152253,152255,152258,152260,152262,152264],{"class":681,"line":3212},[679,152251,152252],{"class":693}," List\u003C",[679,152254,150712],{"class":685},[679,152256,152257],{"class":693},"> limitedPosts ",[679,152259,686],{"class":685},[679,152261,93398],{"class":693},[679,152263,87323],{"class":880},[679,152265,17545],{"class":693},[679,152267,152268,152270,152272],{"class":681,"line":3217},[679,152269,151265],{"class":693},[679,152271,150615],{"class":880},[679,152273,152274],{"class":693},"(comparator)\n",[679,152276,152277,152279,152281],{"class":681,"line":3222},[679,152278,151265],{"class":693},[679,152280,150863],{"class":880},[679,152282,152283],{"class":693},"(limit)\n",[679,152285,152286,152288,152290],{"class":681,"line":3227},[679,152287,151265],{"class":693},[679,152289,85083],{"class":880},[679,152291,9317],{"class":693},[679,152293,152294],{"class":681,"line":3232},[679,152295,889],{"emptyLinePlaceholder":797},[679,152297,152298],{"class":681,"line":3499},[679,152299,152300],{"class":1400}," // Emit a Map.Entry with the key and limited posts\n",[679,152302,152303,152306,152308,152311,152313],{"class":681,"line":3509},[679,152304,152305],{"class":693}," downstream.",[679,152307,59269],{"class":880},[679,152309,152310],{"class":693},"(Map.",[679,152312,44518],{"class":880},[679,152314,152315],{"class":693},"(key, limitedPosts));\n",[679,152317,152318],{"class":681,"line":3516},[679,152319,152320],{"class":693}," });\n",[679,152322,152323],{"class":681,"line":3531},[679,152324,25517],{"class":693},[679,152326,152327],{"class":681,"line":3536},[679,152328,89766],{"class":693},[679,152330,152331],{"class":681,"line":3541},[679,152332,996],{"class":693},[651,152334,152335],{},"Using this custom Gatherer, our solution becomes much cleaner:",[669,152337,152339],{"className":4107,"code":152338,"language":4109,"meta":674,"style":674},"public static void groupByWithLimit(List\u003CBlogPost> posts) {\n // Use our custom gatherer to create a \"Recent Posts By Category\" view\n Map\u003CString, List\u003CBlogPost>> recentPostsByCategory = posts.stream()\n .gather(groupByWithLimit(\n BlogPost::category, // Group by category\n 3, // Show only 3 recent posts per category\n Comparator.comparing(BlogPost::publishedDate).reversed() // Newest first\n ))\n .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n\n printRecentPostsByCategory(recentPostsByCategory);\n}\n",[676,152340,152341,152361,152366,152386,152399,152411,152422,152442,152446,152469,152473,152479],{"__ignoreMap":674},[679,152342,152343,152345,152347,152349,152351,152353,152355,152357,152359],{"class":681,"line":682},[679,152344,6073],{"class":685},[679,152346,6092],{"class":685},[679,152348,6095],{"class":685},[679,152350,152016],{"class":880},[679,152352,150783],{"class":693},[679,152354,4505],{"class":685},[679,152356,150712],{"class":693},[679,152358,5860],{"class":685},[679,152360,150963],{"class":693},[679,152362,152363],{"class":681,"line":790},[679,152364,152365],{"class":1400}," // Use our custom gatherer to create a \"Recent Posts By Category\" view\n",[679,152367,152368,152370,152372,152374,152376,152378,152380,152382,152384],{"class":681,"line":892},[679,152369,150968],{"class":693},[679,152371,4758],{"class":685},[679,152373,150973],{"class":693},[679,152375,150712],{"class":685},[679,152377,150978],{"class":693},[679,152379,686],{"class":685},[679,152381,93398],{"class":693},[679,152383,87323],{"class":880},[679,152385,17545],{"class":693},[679,152387,152388,152390,152392,152394,152397],{"class":681,"line":901},[679,152389,40148],{"class":693},[679,152391,131171],{"class":880},[679,152393,745],{"class":693},[679,152395,152396],{"class":880},"groupByWithLimit",[679,152398,21337],{"class":693},[679,152400,152401,152403,152405,152408],{"class":681,"line":909},[679,152402,151009],{"class":693},[679,152404,90007],{"class":685},[679,152406,152407],{"class":693},"category, ",[679,152409,152410],{"class":1400},"// Group by category\n",[679,152412,152413,152416,152419],{"class":681,"line":918},[679,152414,152415],{"class":931}," 3",[679,152417,152418],{"class":693},", ",[679,152420,152421],{"class":1400},"// Show only 3 recent posts per category\n",[679,152423,152424,152427,152429,152431,152433,152435,152437,152439],{"class":681,"line":935},[679,152425,152426],{"class":693}," Comparator.",[679,152428,150843],{"class":880},[679,152430,150846],{"class":693},[679,152432,90007],{"class":685},[679,152434,150851],{"class":693},[679,152436,150854],{"class":880},[679,152438,6700],{"class":693},[679,152440,152441],{"class":1400},"// Newest first\n",[679,152443,152444],{"class":681,"line":944},[679,152445,151778],{"class":693},[679,152447,152448,152450,152452,152454,152456,152459,152461,152464,152466],{"class":681,"line":959},[679,152449,40148],{"class":693},[679,152451,90908],{"class":880},[679,152453,90911],{"class":693},[679,152455,151229],{"class":880},[679,152457,152458],{"class":693},"(Map.Entry",[679,152460,90007],{"class":685},[679,152462,152463],{"class":693},"getKey, Map.Entry",[679,152465,90007],{"class":685},[679,152467,152468],{"class":693},"getValue));\n",[679,152470,152471],{"class":681,"line":964},[679,152472,889],{"emptyLinePlaceholder":797},[679,152474,152475,152477],{"class":681,"line":977},[679,152476,151116],{"class":880},[679,152478,151119],{"class":693},[679,152480,152481],{"class":681,"line":982},[679,152482,996],{"class":693},[651,152484,151496],{},[669,152486,152489],{"className":152487,"code":152488,"language":11464},[16247],"Recent Posts By Category:\n\nCategory: Java\n - Stream Gatherers: A Complete Tutorial (Published: 2025-03-19T09:30)\n - Getting Started with JDK 24 Stream Gatherers (Published: 2025-03-18T10:15)\n - Comparing Stream Collectors and Gatherers (Published: 2025-03-16T14:15)\n\nCategory: Spring\n - Building Reactive APIs with Spring WebFlux (Published: 2025-03-17T11:05)\n - Spring Boot 3.2 New Features Overview (Published: 2025-03-12T15:40)\n - Secure Authentication with Spring Security 7 (Published: 2025-03-08T10:00)\n...\n",[676,152490,152488],{"__ignoreMap":674},[651,152492,152493],{},"A Gatherer consists of four key components:",[27665,152495,152496,152506,152512,152518],{},[5332,152497,152498,152501,152502,152505],{},[2939,152499,152500],{},"Initializer",": Creates the initial state (",[676,152503,152504],{},"HashMap"," in our example)",[5332,152507,152508,152511],{},[2939,152509,152510],{},"Integrator",": Processes each element and updates the state",[5332,152513,152514,152517],{},[2939,152515,152516],{},"Combiner",": Combines states when processing in parallel",[5332,152519,152520,152523],{},[2939,152521,152522],{},"Finisher",": Processes the final state and emits results",[4542,152525,152527],{"id":152526},"more-custom-gatherer-examples","More Custom Gatherer Examples",[651,152529,152530,152531,72934],{},"Let's explore more useful gatherers for blog content processing from our ",[676,152532,152533],{},"BlogGatherers",[5909,152535,152537],{"id":152536},"related-posts-finder","Related Posts Finder",[651,152539,152540],{},"This gatherer finds posts related to a target post based on category and content similarity:",[669,152542,152544],{"className":4107,"code":152543,"language":4109,"meta":674,"style":674},"public static Gatherer\u003CBlogPost, ?, List\u003CBlogPost>> relatedPosts(BlogPost targetPost, int limit) {\n return Gatherer.ofSequential(\n () -> new HashMap\u003CString, List\u003CBlogPost>>(),\n (map, post, downstream) -> {\n // Don't include the target post itself\n if (!post.id().equals(targetPost.id())) {\n map.computeIfAbsent(post.category(), k -> new ArrayList\u003C>()).add(post);\n }\n return true;\n },\n (map, downstream) -> {\n // Get posts from the same category\n List\u003CBlogPost> sameCategoryPosts = map.getOrDefault(targetPost.category(), List.of());\n\n // Calculate similarity score and find most similar posts\n List\u003CBlogPost> relatedPosts = sameCategoryPosts.stream()\n .map(post -> Map.entry(post, calculateSimilarity(targetPost, post)))\n .sorted(Map.Entry.\u003CBlogPost, Double>comparingByValue().reversed())\n .limit(limit)\n .map(Map.Entry::getKey)\n .toList();\n\n downstream.push(relatedPosts);\n }\n );\n}\n",[676,152545,152546,152581,152592,152612,152620,152625,152651,152675,152679,152687,152691,152699,152704,152732,152736,152741,152759,152783,152808,152816,152829,152837,152841,152851,152855,152859],{"__ignoreMap":674},[679,152547,152548,152550,152552,152554,152556,152559,152561,152564,152566,152568,152570,152573,152576,152578],{"class":681,"line":682},[679,152549,6073],{"class":685},[679,152551,6092],{"class":685},[679,152553,151983],{"class":693},[679,152555,4505],{"class":685},[679,152557,152558],{"class":693},"BlogPost, ",[679,152560,2381],{"class":685},[679,152562,152563],{"class":693},", List",[679,152565,4505],{"class":685},[679,152567,150712],{"class":693},[679,152569,96110],{"class":685},[679,152571,152572],{"class":880}," relatedPosts",[679,152574,152575],{"class":693},"(BlogPost targetPost, ",[679,152577,1078],{"class":685},[679,152579,152580],{"class":693}," limit) {\n",[679,152582,152583,152585,152587,152590],{"class":681,"line":790},[679,152584,21478],{"class":685},[679,152586,152075],{"class":693},[679,152588,152589],{"class":880},"ofSequential",[679,152591,21337],{"class":693},[679,152593,152594,152596,152598,152600,152603,152605,152607,152609],{"class":681,"line":892},[679,152595,21382],{"class":693},[679,152597,16955],{"class":685},[679,152599,2054],{"class":685},[679,152601,152602],{"class":693}," HashMap\u003C",[679,152604,4758],{"class":685},[679,152606,150973],{"class":693},[679,152608,150712],{"class":685},[679,152610,152611],{"class":693},">>(),\n",[679,152613,152614,152616,152618],{"class":681,"line":901},[679,152615,152116],{"class":693},[679,152617,16955],{"class":685},[679,152619,884],{"class":693},[679,152621,152622],{"class":681,"line":909},[679,152623,152624],{"class":1400}," // Don't include the target post itself\n",[679,152626,152627,152630,152632,152634,152637,152639,152641,152643,152646,152648],{"class":681,"line":918},[679,152628,152629],{"class":685}," if",[679,152631,4193],{"class":693},[679,152633,1223],{"class":685},[679,152635,152636],{"class":693},"post.",[679,152638,11341],{"class":880},[679,152640,10541],{"class":693},[679,152642,14592],{"class":880},[679,152644,152645],{"class":693},"(targetPost.",[679,152647,11341],{"class":880},[679,152649,152650],{"class":693},"())) {\n",[679,152652,152653,152656,152658,152660,152662,152665,152667,152669,152671,152673],{"class":681,"line":935},[679,152654,152655],{"class":693}," map.",[679,152657,152156],{"class":880},[679,152659,116598],{"class":693},[679,152661,150824],{"class":880},[679,152663,152664],{"class":693},"(), k ",[679,152666,16955],{"class":685},[679,152668,2054],{"class":685},[679,152670,152166],{"class":693},[679,152672,12952],{"class":880},[679,152674,110679],{"class":693},[679,152676,152677],{"class":681,"line":944},[679,152678,106697],{"class":693},[679,152680,152681,152683,152685],{"class":681,"line":959},[679,152682,143712],{"class":685},[679,152684,14523],{"class":931},[679,152686,1186],{"class":693},[679,152688,152689],{"class":681,"line":964},[679,152690,152192],{"class":693},[679,152692,152693,152695,152697],{"class":681,"line":977},[679,152694,152225],{"class":693},[679,152696,16955],{"class":685},[679,152698,884],{"class":693},[679,152700,152701],{"class":681,"line":982},[679,152702,152703],{"class":1400}," // Get posts from the same category\n",[679,152705,152706,152708,152710,152713,152715,152718,152721,152723,152725,152728,152730],{"class":681,"line":988},[679,152707,34421],{"class":693},[679,152709,150712],{"class":685},[679,152711,152712],{"class":693},"> sameCategoryPosts ",[679,152714,686],{"class":685},[679,152716,152717],{"class":693}," map.",[679,152719,152720],{"class":880},"getOrDefault",[679,152722,152645],{"class":693},[679,152724,150824],{"class":880},[679,152726,152727],{"class":693},"(), List.",[679,152729,16672],{"class":880},[679,152731,9431],{"class":693},[679,152733,152734],{"class":681,"line":993},[679,152735,889],{"emptyLinePlaceholder":797},[679,152737,152738],{"class":681,"line":2129},[679,152739,152740],{"class":1400}," // Calculate similarity score and find most similar posts\n",[679,152742,152743,152745,152747,152750,152752,152755,152757],{"class":681,"line":2140},[679,152744,34421],{"class":693},[679,152746,150712],{"class":685},[679,152748,152749],{"class":693},"> relatedPosts ",[679,152751,686],{"class":685},[679,152753,152754],{"class":693}," sameCategoryPosts.",[679,152756,87323],{"class":880},[679,152758,17545],{"class":693},[679,152760,152761,152763,152765,152767,152769,152772,152774,152777,152780],{"class":681,"line":2145},[679,152762,89566],{"class":693},[679,152764,51904],{"class":880},[679,152766,110585],{"class":693},[679,152768,16955],{"class":685},[679,152770,152771],{"class":693}," Map.",[679,152773,44518],{"class":880},[679,152775,152776],{"class":693},"(post, ",[679,152778,152779],{"class":880},"calculateSimilarity",[679,152781,152782],{"class":693},"(targetPost, post)))\n",[679,152784,152785,152787,152789,152792,152794,152797,152799,152802,152804,152806],{"class":681,"line":2154},[679,152786,89566],{"class":693},[679,152788,150615],{"class":880},[679,152790,152791],{"class":693},"(Map.Entry.",[679,152793,4505],{"class":685},[679,152795,152796],{"class":693},"BlogPost, Double",[679,152798,5860],{"class":685},[679,152800,152801],{"class":880},"comparingByValue",[679,152803,10541],{"class":693},[679,152805,150854],{"class":880},[679,152807,40172],{"class":693},[679,152809,152810,152812,152814],{"class":681,"line":2159},[679,152811,89566],{"class":693},[679,152813,150863],{"class":880},[679,152815,152283],{"class":693},[679,152817,152818,152820,152822,152824,152826],{"class":681,"line":2164},[679,152819,89566],{"class":693},[679,152821,51904],{"class":880},[679,152823,152458],{"class":693},[679,152825,90007],{"class":685},[679,152827,152828],{"class":693},"getKey)\n",[679,152830,152831,152833,152835],{"class":681,"line":3134},[679,152832,89566],{"class":693},[679,152834,85083],{"class":880},[679,152836,9317],{"class":693},[679,152838,152839],{"class":681,"line":3139},[679,152840,889],{"emptyLinePlaceholder":797},[679,152842,152843,152846,152848],{"class":681,"line":3144},[679,152844,152845],{"class":693}," downstream.",[679,152847,59269],{"class":880},[679,152849,152850],{"class":693},"(relatedPosts);\n",[679,152852,152853],{"class":681,"line":3149},[679,152854,25517],{"class":693},[679,152856,152857],{"class":681,"line":3169},[679,152858,89766],{"class":693},[679,152860,152861],{"class":681,"line":3185},[679,152862,996],{"class":693},[5909,152864,152866],{"id":152865},"tag-extractor","Tag Extractor",[651,152868,152869],{},"This gatherer extracts and counts hashtags from blog post content:",[669,152871,152873],{"className":4107,"code":152872,"language":4109,"meta":674,"style":674},"public static Gatherer\u003CBlogPost, ?, Map\u003CString, Integer>> extractTags() {\n return Gatherer.ofSequential(\n () -> new HashMap\u003CString, Integer>(),\n (tagCounts, post, downstream) -> {\n // Extract hashtags from content\n String content = post.content().toLowerCase();\n Pattern pattern = Pattern.compile(\"#(\\\\w+)\");\n Matcher matcher = pattern.matcher(content);\n\n while (matcher.find()) {\n String tag = matcher.group(1);\n tagCounts.merge(tag, 1, Integer::sum);\n }\n\n return true;\n },\n (tagCounts, downstream) -> {\n downstream.push(tagCounts);\n }\n );\n}\n",[676,152874,152875,152904,152914,152933,152942,152947,152965,152987,153001,153005,153015,153032,153053,153057,153061,153069,153073,153082,153091,153095,153099],{"__ignoreMap":674},[679,152876,152877,152879,152881,152883,152885,152887,152889,152892,152894,152897,152899,152902],{"class":681,"line":682},[679,152878,6073],{"class":685},[679,152880,6092],{"class":685},[679,152882,151983],{"class":693},[679,152884,4505],{"class":685},[679,152886,152558],{"class":693},[679,152888,2381],{"class":685},[679,152890,152891],{"class":693},", Map",[679,152893,4505],{"class":685},[679,152895,152896],{"class":693},"String, Integer",[679,152898,96110],{"class":685},[679,152900,152901],{"class":880}," extractTags",[679,152903,2667],{"class":693},[679,152905,152906,152908,152910,152912],{"class":681,"line":790},[679,152907,21478],{"class":685},[679,152909,152075],{"class":693},[679,152911,152589],{"class":880},[679,152913,21337],{"class":693},[679,152915,152916,152918,152920,152922,152924,152926,152928,152930],{"class":681,"line":892},[679,152917,21382],{"class":693},[679,152919,16955],{"class":685},[679,152921,2054],{"class":685},[679,152923,152602],{"class":693},[679,152925,4758],{"class":685},[679,152927,2797],{"class":693},[679,152929,1083],{"class":685},[679,152931,152932],{"class":693},">(),\n",[679,152934,152935,152938,152940],{"class":681,"line":901},[679,152936,152937],{"class":693}," (tagCounts, post, downstream) ",[679,152939,16955],{"class":685},[679,152941,884],{"class":693},[679,152943,152944],{"class":681,"line":909},[679,152945,152946],{"class":1400}," // Extract hashtags from content\n",[679,152948,152949,152952,152954,152956,152958,152960,152963],{"class":681,"line":918},[679,152950,152951],{"class":693}," String content ",[679,152953,686],{"class":685},[679,152955,110590],{"class":693},[679,152957,47833],{"class":880},[679,152959,10541],{"class":693},[679,152961,152962],{"class":880},"toLowerCase",[679,152964,9317],{"class":693},[679,152966,152967,152970,152972,152974,152976,152978,152981,152983,152985],{"class":681,"line":935},[679,152968,152969],{"class":693}," Pattern pattern ",[679,152971,686],{"class":685},[679,152973,130470],{"class":693},[679,152975,7134],{"class":880},[679,152977,745],{"class":693},[679,152979,152980],{"class":689},"\"#(",[679,152982,4412],{"class":931},[679,152984,130487],{"class":689},[679,152986,1208],{"class":693},[679,152988,152989,152992,152994,152996,152998],{"class":681,"line":944},[679,152990,152991],{"class":693}," Matcher matcher ",[679,152993,686],{"class":685},[679,152995,130499],{"class":693},[679,152997,130502],{"class":880},[679,152999,153000],{"class":693},"(content);\n",[679,153002,153003],{"class":681,"line":959},[679,153004,889],{"emptyLinePlaceholder":797},[679,153006,153007,153009,153011,153013],{"class":681,"line":964},[679,153008,137590],{"class":685},[679,153010,130511],{"class":693},[679,153012,130514],{"class":880},[679,153014,111224],{"class":693},[679,153016,153017,153020,153022,153024,153026,153028,153030],{"class":681,"line":977},[679,153018,153019],{"class":693}," String tag ",[679,153021,686],{"class":685},[679,153023,130523],{"class":693},[679,153025,130526],{"class":880},[679,153027,745],{"class":693},[679,153029,1557],{"class":931},[679,153031,1208],{"class":693},[679,153033,153034,153037,153040,153043,153045,153048,153050],{"class":681,"line":982},[679,153035,153036],{"class":693}," tagCounts.",[679,153038,153039],{"class":880},"merge",[679,153041,153042],{"class":693},"(tag, ",[679,153044,1557],{"class":931},[679,153046,153047],{"class":693},", Integer",[679,153049,90007],{"class":685},[679,153051,153052],{"class":693},"sum);\n",[679,153054,153055],{"class":681,"line":988},[679,153056,106697],{"class":693},[679,153058,153059],{"class":681,"line":993},[679,153060,889],{"emptyLinePlaceholder":797},[679,153062,153063,153065,153067],{"class":681,"line":2129},[679,153064,143712],{"class":685},[679,153066,14523],{"class":931},[679,153068,1186],{"class":693},[679,153070,153071],{"class":681,"line":2140},[679,153072,152192],{"class":693},[679,153074,153075,153078,153080],{"class":681,"line":2145},[679,153076,153077],{"class":693}," (tagCounts, downstream) ",[679,153079,16955],{"class":685},[679,153081,884],{"class":693},[679,153083,153084,153086,153088],{"class":681,"line":2154},[679,153085,152845],{"class":693},[679,153087,59269],{"class":880},[679,153089,153090],{"class":693},"(tagCounts);\n",[679,153092,153093],{"class":681,"line":2159},[679,153094,25517],{"class":693},[679,153096,153097],{"class":681,"line":2164},[679,153098,89766],{"class":693},[679,153100,153101],{"class":681,"line":3134},[679,153102,996],{"class":693},[5909,153104,153106],{"id":153105},"reading-time-calculator","Reading Time Calculator",[651,153108,153109],{},"This gatherer calculates estimated reading times for blog posts:",[669,153111,153113],{"className":4107,"code":153112,"language":4109,"meta":674,"style":674},"public static Gatherer\u003CBlogPost, ?, Map\u003CLong, Duration>> calculateReadingTimes() {\n // Assume average reading speed of 200 words per minute\n final int WORDS_PER_MINUTE = 200;\n\n return Gatherer.ofSequential(\n () -> new HashMap\u003CLong, Duration>(),\n (readingTimes, post, downstream) -> {\n String content = post.content();\n int wordCount = content.split(\"\\\\s+\").length;\n\n // Calculate reading time in minutes\n double minutes = (double) wordCount / WORDS_PER_MINUTE;\n\n // Convert to Duration\n Duration readingTime = Duration.ofSeconds(Math.round(minutes * 60));\n readingTimes.put(post.id(), readingTime);\n\n return true;\n },\n (readingTimes, downstream) -> {\n downstream.push(readingTimes);\n }\n );\n}\n",[676,153114,153115,153143,153148,153164,153168,153178,153197,153206,153218,153244,153248,153253,153275,153279,153284,153311,153325,153329,153337,153341,153350,153359,153363,153367],{"__ignoreMap":674},[679,153116,153117,153119,153121,153123,153125,153127,153129,153131,153133,153136,153138,153141],{"class":681,"line":682},[679,153118,6073],{"class":685},[679,153120,6092],{"class":685},[679,153122,151983],{"class":693},[679,153124,4505],{"class":685},[679,153126,152558],{"class":693},[679,153128,2381],{"class":685},[679,153130,152891],{"class":693},[679,153132,4505],{"class":685},[679,153134,153135],{"class":693},"Long, Duration",[679,153137,96110],{"class":685},[679,153139,153140],{"class":880}," calculateReadingTimes",[679,153142,2667],{"class":693},[679,153144,153145],{"class":681,"line":790},[679,153146,153147],{"class":1400}," // Assume average reading speed of 200 words per minute\n",[679,153149,153150,153153,153155,153158,153160,153162],{"class":681,"line":892},[679,153151,153152],{"class":685}," final",[679,153154,14925],{"class":685},[679,153156,153157],{"class":693}," WORDS_PER_MINUTE ",[679,153159,686],{"class":685},[679,153161,67965],{"class":931},[679,153163,1186],{"class":693},[679,153165,153166],{"class":681,"line":901},[679,153167,889],{"emptyLinePlaceholder":797},[679,153169,153170,153172,153174,153176],{"class":681,"line":909},[679,153171,21478],{"class":685},[679,153173,152075],{"class":693},[679,153175,152589],{"class":880},[679,153177,21337],{"class":693},[679,153179,153180,153182,153184,153186,153188,153190,153192,153195],{"class":681,"line":918},[679,153181,21382],{"class":693},[679,153183,16955],{"class":685},[679,153185,2054],{"class":685},[679,153187,152602],{"class":693},[679,153189,1094],{"class":685},[679,153191,2797],{"class":693},[679,153193,153194],{"class":685},"Duration",[679,153196,152932],{"class":693},[679,153198,153199,153202,153204],{"class":681,"line":935},[679,153200,153201],{"class":693}," (readingTimes, post, downstream) ",[679,153203,16955],{"class":685},[679,153205,884],{"class":693},[679,153207,153208,153210,153212,153214,153216],{"class":681,"line":944},[679,153209,152951],{"class":693},[679,153211,686],{"class":685},[679,153213,110590],{"class":693},[679,153215,47833],{"class":880},[679,153217,9317],{"class":693},[679,153219,153220,153223,153226,153228,153230,153232,153234,153236,153238,153241],{"class":681,"line":959},[679,153221,153222],{"class":685}," int",[679,153224,153225],{"class":693}," wordCount ",[679,153227,686],{"class":685},[679,153229,144687],{"class":693},[679,153231,55948],{"class":880},[679,153233,745],{"class":693},[679,153235,4857],{"class":689},[679,153237,4412],{"class":931},[679,153239,153240],{"class":689},"s+\"",[679,153242,153243],{"class":693},").length;\n",[679,153245,153246],{"class":681,"line":964},[679,153247,889],{"emptyLinePlaceholder":797},[679,153249,153250],{"class":681,"line":977},[679,153251,153252],{"class":1400}," // Calculate reading time in minutes\n",[679,153254,153255,153258,153261,153263,153265,153267,153270,153272],{"class":681,"line":982},[679,153256,153257],{"class":685}," double",[679,153259,153260],{"class":693}," minutes ",[679,153262,686],{"class":685},[679,153264,4193],{"class":693},[679,153266,1110],{"class":685},[679,153268,153269],{"class":693},") wordCount ",[679,153271,4408],{"class":685},[679,153273,153274],{"class":693}," WORDS_PER_MINUTE;\n",[679,153276,153277],{"class":681,"line":988},[679,153278,889],{"emptyLinePlaceholder":797},[679,153280,153281],{"class":681,"line":993},[679,153282,153283],{"class":1400}," // Convert to Duration\n",[679,153285,153286,153289,153291,153294,153297,153299,153302,153305,153307,153309],{"class":681,"line":2129},[679,153287,153288],{"class":693}," Duration readingTime ",[679,153290,686],{"class":685},[679,153292,153293],{"class":693}," Duration.",[679,153295,153296],{"class":880},"ofSeconds",[679,153298,52183],{"class":693},[679,153300,153301],{"class":880},"round",[679,153303,153304],{"class":693},"(minutes ",[679,153306,4150],{"class":685},[679,153308,118250],{"class":931},[679,153310,1669],{"class":693},[679,153312,153313,153316,153318,153320,153322],{"class":681,"line":2140},[679,153314,153315],{"class":693}," readingTimes.",[679,153317,114147],{"class":880},[679,153319,116598],{"class":693},[679,153321,11341],{"class":880},[679,153323,153324],{"class":693},"(), readingTime);\n",[679,153326,153327],{"class":681,"line":2145},[679,153328,889],{"emptyLinePlaceholder":797},[679,153330,153331,153333,153335],{"class":681,"line":2154},[679,153332,143712],{"class":685},[679,153334,14523],{"class":931},[679,153336,1186],{"class":693},[679,153338,153339],{"class":681,"line":2159},[679,153340,152192],{"class":693},[679,153342,153343,153346,153348],{"class":681,"line":2164},[679,153344,153345],{"class":693}," (readingTimes, downstream) ",[679,153347,16955],{"class":685},[679,153349,884],{"class":693},[679,153351,153352,153354,153356],{"class":681,"line":3134},[679,153353,152845],{"class":693},[679,153355,59269],{"class":880},[679,153357,153358],{"class":693},"(readingTimes);\n",[679,153360,153361],{"class":681,"line":3139},[679,153362,25517],{"class":693},[679,153364,153365],{"class":681,"line":3144},[679,153366,89766],{"class":693},[679,153368,153369],{"class":681,"line":3149},[679,153370,996],{"class":693},[5909,153372,153374],{"id":153373},"monthly-archive-builder","Monthly Archive Builder",[651,153376,153377],{},"This gatherer groups posts by month for an archive view:",[669,153379,153381],{"className":4107,"code":153380,"language":4109,"meta":674,"style":674},"public static Gatherer\u003CBlogPost, ?, Map\u003CYearMonth, List\u003CBlogPost>>> monthlyArchive() {\n return Gatherer.ofSequential(\n () -> new TreeMap\u003CYearMonth, List\u003CBlogPost>>(Comparator.reverseOrder()),\n (archive, post, downstream) -> {\n LocalDateTime publishDate = post.publishedDate();\n YearMonth yearMonth = YearMonth.from(publishDate);\n\n archive.computeIfAbsent(yearMonth, k -> new ArrayList\u003C>()).add(post);\n return true;\n },\n (archive, downstream) -> {\n // Sort posts within each month by publish date (newest first)\n archive.forEach((month, posts) ->\n posts.sort(Comparator.comparing(BlogPost::publishedDate).reversed()));\n\n downstream.push(archive);\n }\n );\n}\n",[676,153382,153383,153415,153425,153451,153460,153474,153489,153493,153513,153521,153525,153534,153539,153551,153572,153576,153585,153589,153593],{"__ignoreMap":674},[679,153384,153385,153387,153389,153391,153393,153395,153397,153399,153401,153404,153406,153408,153410,153413],{"class":681,"line":682},[679,153386,6073],{"class":685},[679,153388,6092],{"class":685},[679,153390,151983],{"class":693},[679,153392,4505],{"class":685},[679,153394,152558],{"class":693},[679,153396,2381],{"class":685},[679,153398,152891],{"class":693},[679,153400,4505],{"class":685},[679,153402,153403],{"class":693},"YearMonth, List",[679,153405,4505],{"class":685},[679,153407,150712],{"class":693},[679,153409,152013],{"class":685},[679,153411,153412],{"class":880}," monthlyArchive",[679,153414,2667],{"class":693},[679,153416,153417,153419,153421,153423],{"class":681,"line":790},[679,153418,21478],{"class":685},[679,153420,152075],{"class":693},[679,153422,152589],{"class":880},[679,153424,21337],{"class":693},[679,153426,153427,153429,153431,153433,153436,153439,153441,153443,153446,153449],{"class":681,"line":892},[679,153428,21382],{"class":693},[679,153430,16955],{"class":685},[679,153432,2054],{"class":685},[679,153434,153435],{"class":693}," TreeMap\u003C",[679,153437,153438],{"class":685},"YearMonth",[679,153440,150973],{"class":693},[679,153442,150712],{"class":685},[679,153444,153445],{"class":693},">>(Comparator.",[679,153447,153448],{"class":880},"reverseOrder",[679,153450,98399],{"class":693},[679,153452,153453,153456,153458],{"class":681,"line":901},[679,153454,153455],{"class":693}," (archive, post, downstream) ",[679,153457,16955],{"class":685},[679,153459,884],{"class":693},[679,153461,153462,153465,153467,153469,153472],{"class":681,"line":909},[679,153463,153464],{"class":693}," LocalDateTime publishDate ",[679,153466,686],{"class":685},[679,153468,110590],{"class":693},[679,153470,153471],{"class":880},"publishedDate",[679,153473,9317],{"class":693},[679,153475,153476,153479,153481,153484,153486],{"class":681,"line":918},[679,153477,153478],{"class":693}," YearMonth yearMonth ",[679,153480,686],{"class":685},[679,153482,153483],{"class":693}," YearMonth.",[679,153485,28887],{"class":880},[679,153487,153488],{"class":693},"(publishDate);\n",[679,153490,153491],{"class":681,"line":935},[679,153492,889],{"emptyLinePlaceholder":797},[679,153494,153495,153498,153500,153503,153505,153507,153509,153511],{"class":681,"line":944},[679,153496,153497],{"class":693}," archive.",[679,153499,152156],{"class":880},[679,153501,153502],{"class":693},"(yearMonth, k ",[679,153504,16955],{"class":685},[679,153506,2054],{"class":685},[679,153508,152166],{"class":693},[679,153510,12952],{"class":880},[679,153512,110679],{"class":693},[679,153514,153515,153517,153519],{"class":681,"line":959},[679,153516,143712],{"class":685},[679,153518,14523],{"class":931},[679,153520,1186],{"class":693},[679,153522,153523],{"class":681,"line":964},[679,153524,152192],{"class":693},[679,153526,153527,153530,153532],{"class":681,"line":977},[679,153528,153529],{"class":693}," (archive, downstream) ",[679,153531,16955],{"class":685},[679,153533,884],{"class":693},[679,153535,153536],{"class":681,"line":982},[679,153537,153538],{"class":1400}," // Sort posts within each month by publish date (newest first)\n",[679,153540,153541,153543,153545,153548],{"class":681,"line":988},[679,153542,153497],{"class":693},[679,153544,46928],{"class":880},[679,153546,153547],{"class":693},"((month, posts) ",[679,153549,153550],{"class":685},"->\n",[679,153552,153553,153556,153558,153560,153562,153564,153566,153568,153570],{"class":681,"line":993},[679,153554,153555],{"class":693}," posts.",[679,153557,51928],{"class":880},[679,153559,150840],{"class":693},[679,153561,150843],{"class":880},[679,153563,150846],{"class":693},[679,153565,90007],{"class":685},[679,153567,150851],{"class":693},[679,153569,150854],{"class":880},[679,153571,111968],{"class":693},[679,153573,153574],{"class":681,"line":2129},[679,153575,889],{"emptyLinePlaceholder":797},[679,153577,153578,153580,153582],{"class":681,"line":2140},[679,153579,152845],{"class":693},[679,153581,59269],{"class":880},[679,153583,153584],{"class":693},"(archive);\n",[679,153586,153587],{"class":681,"line":2145},[679,153588,25517],{"class":693},[679,153590,153591],{"class":681,"line":2154},[679,153592,89766],{"class":693},[679,153594,153595],{"class":681,"line":2159},[679,153596,996],{"class":693},[4542,153598,9042],{"id":9041},[651,153600,153601],{},"Stream Gatherers in JDK 24 provide a powerful way to extend Java's Stream API with custom intermediate operations. They simplify complex data transformations, improve code readability, and offer better reusability.",[651,153603,153604],{},"The benefits of Stream Gatherers include:",[27665,153606,153607,153613,153619,153625],{},[5332,153608,153609,153612],{},[2939,153610,153611],{},"More readable code"," with a natural flow",[5332,153614,153615,153618],{},[2939,153616,153617],{},"Reusable solutions"," for similar problems",[5332,153620,153621,153624],{},[2939,153622,153623],{},"Encapsulated logic"," in a single operation",[5332,153626,153627],{},[2939,153628,153629],{},"Support for parallel processing",[651,153631,153632],{},"By encapsulating complex logic into custom operations, we can write more expressive and maintainable code for processing blog content and collections in general.",[651,153634,153635,153636,153640],{},"If you'd like to experiment with these examples, check out my GitHub repository at ",[812,153637,153638],{"href":153638,"rel":153639},"https://github.com/danvega/gatherer",[816],", which includes all the code we've discussed here.",[651,153642,153643],{},"Have you started using Stream Gatherers in your projects? What custom operations have you implemented? Let me know in the comments!",[651,153645,78024],{},[786,153647,153648],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":153650},[153651,153652,153653,153657,153663,153664,153670],{"id":150593,"depth":790,"text":150594},{"id":150702,"depth":790,"text":150703},{"id":150928,"depth":790,"text":150929,"children":153654},[153655,153656],{"id":150935,"depth":892,"text":150936},{"id":151126,"depth":892,"text":151127},{"id":151329,"depth":790,"text":151330,"children":153658},[153659,153660,153661,153662],{"id":151339,"depth":892,"text":151340},{"id":151505,"depth":892,"text":151506},{"id":151669,"depth":892,"text":151670},{"id":151813,"depth":892,"text":151814},{"id":151958,"depth":790,"text":151959},{"id":152526,"depth":790,"text":152527,"children":153665},[153666,153667,153668,153669],{"id":152536,"depth":892,"text":152537},{"id":152865,"depth":892,"text":152866},{"id":153105,"depth":892,"text":153106},{"id":153373,"depth":892,"text":153374},{"id":9041,"depth":790,"text":9042},"Discover how the new Stream Gatherers feature in JDK 24 provides a more elegant solution for processing blog content compared to traditional stream operations.",{"slug":153673,"date":153674,"published":797,"author":798,"tags":153675,"keywords":153676},"stream-gatherers","2025-03-21T09:00:00.000Z",[36422],"Java, JDK 24, Stream Gatherers, Java Streams, Blog Processing, Java Records",{"title":15,"description":153671},"blog/2025/03/21/stream-gatherers","fS1rrPj5ATpAhbr09WlAVjOnS7SbNunEyoIlF2MzRWc",{"id":153681,"title":12,"body":153682,"description":154969,"extension":793,"meta":154970,"navigation":797,"path":13,"seo":154977,"stem":154978,"__hash__":154979},"content/blog/2025/03/26/creating-your-first-mcp-server-java.md",{"type":648,"value":153683,"toc":154947},[153684,153687,153689,153692,153706,153709,153713,153716,153741,153745,153748,153756,153759,153761,153764,153778,153781,153787,153790,153810,153816,153819,153823,153826,153851,153855,153861,153882,153885,153889,153899,154210,154213,154239,154245,154249,154252,154367,154376,154380,154385,154441,154444,154464,154467,154471,154474,154486,154491,154495,154498,154504,154507,154566,154569,154583,154586,154590,154593,154599,154602,154610,154613,154617,154620,154624,154627,154732,154736,154739,154786,154789,154793,154796,154901,154903,154906,154909,154912,154914,154942,154944],[651,153685,153686],{},"Are you looking to extend the capabilities of AI models like Claude by connecting them to your custom data and services? Model Context Protocol (MCP) provides a standardized way to do exactly that, and Spring AI makes implementing your own MCP server surprisingly straightforward. In this tutorial, I'll walk you through creating your first MCP server from scratch using Spring Boot and Spring AI.",[4542,153688,149710],{"id":149709},[651,153690,153691],{},"Model Context Protocol (MCP) is an open standard that defines how AI models communicate with external tools and data sources. It enables AI models to interact with your custom services through a standardized interface, making it possible for models to:",[5316,153693,153694,153697,153700,153703],{},[5332,153695,153696],{},"Access private or proprietary data",[5332,153698,153699],{},"Perform specialized calculations",[5332,153701,153702],{},"Execute custom business logic",[5332,153704,153705],{},"Interact with external systems",[651,153707,153708],{},"Think of MCP as a bridge between AI models and the broader digital ecosystem, allowing models to go beyond their training data and interact with real-time information.",[4542,153710,153712],{"id":153711},"why-build-an-mcp-server-with-spring-ai","Why Build an MCP Server with Spring AI?",[651,153714,153715],{},"If you're a Java developer already familiar with the Spring ecosystem, building MCP servers with Spring AI offers several advantages:",[5316,153717,153718,153723,153729,153735],{},[5332,153719,153720,153722],{},[2939,153721,138502],{},": Spring AI abstracts away much of the complexity of implementing the MCP specification",[5332,153724,153725,153728],{},[2939,153726,153727],{},"Familiar Programming Model",": Use standard Spring concepts like dependency injection and component scanning",[5332,153730,153731,153734],{},[2939,153732,153733],{},"Integration with Spring Boot",": Leverage the full power of the Spring Boot ecosystem",[5332,153736,153737,153740],{},[2939,153738,153739],{},"Multiple Transport Options",": Support for STDIO, HTTP, WebSocket, and RSocket",[4542,153742,153744],{"id":153743},"project-overview","Project Overview",[651,153746,153747],{},"In this tutorial, we'll build a simple MCP server that provides information about programming courses. Our server will expose two tools:",[27665,153749,153750,153753],{},[5332,153751,153752],{},"A tool to retrieve all available courses",[5332,153754,153755],{},"A tool to find a specific course by title",[651,153757,153758],{},"This straightforward example will demonstrate the core concepts of MCP server development while giving you a foundation you can extend for your own needs.",[4542,153760,44199],{"id":44198},[651,153762,153763],{},"Before we begin, make sure you have:",[5316,153765,153766,153769,153772,153775],{},[5332,153767,153768],{},"Java Development Kit (JDK) 17 or higher",[5332,153770,153771],{},"Maven or Gradle for dependency management",[5332,153773,153774],{},"A basic understanding of Spring Boot",[5332,153776,153777],{},"An MCP client for testing (we'll use Claude Desktop in this tutorial)",[4542,153779,153780],{"id":51522},"Setting Up Your Project",[651,153782,153783,153784,664],{},"Let's start by creating a new Spring Boot project. The easiest way is to use the Spring Initializer at ",[812,153785,77478],{"href":7115,"rel":153786},[816],[651,153788,153789],{},"Configure your project with:",[5316,153791,153792,153796,153800,153805],{},[5332,153793,153794,117666],{},[2939,153795,36767],{},[5332,153797,153798,117672],{},[2939,153799,117671],{},[5332,153801,153802,153804],{},[2939,153803,7077],{},": Latest version (3.4.x at the time of writing)",[5332,153806,153807,153809],{},[2939,153808,51430],{},": Spring AI MCP Server",[651,153811,153812],{},[660,153813],{"alt":153814,"src":153815},"Spring Initializer Configuration","/images/blog/2025/03/26/spring-init-mcp.png",[651,153817,153818],{},"Once you've generated and downloaded the project, open it in your favorite IDE.",[4542,153820,153822],{"id":153821},"understanding-the-project-structure","Understanding the Project Structure",[651,153824,153825],{},"Let's examine the key components we'll need to create:",[27665,153827,153828,153834,153840,153846],{},[5332,153829,153830,153833],{},[2939,153831,153832],{},"Course.java",": A simple record to represent our course data",[5332,153835,153836,153839],{},[2939,153837,153838],{},"CourseService.java",": A service class that will expose our course data through MCP tools",[5332,153841,153842,153845],{},[2939,153843,153844],{},"CoursesApplication.java",": Our main application class with tool registration",[5332,153847,153848,153850],{},[2939,153849,16242],{},": Configuration for our MCP server",[4542,153852,153854],{"id":153853},"step-1-creating-the-data-model","Step 1: Creating the Data Model",[651,153856,153857,153858,153860],{},"First, let's create our ",[676,153859,32709],{}," class. Since we're using Java 17+, we can use records for a concise, immutable data structure:",[669,153862,153864],{"className":4107,"code":153863,"language":4109,"meta":674,"style":674},"public record Course(String title, String url) {\n}\n",[676,153865,153866,153878],{"__ignoreMap":674},[679,153867,153868,153870,153872,153875],{"class":681,"line":682},[679,153869,6073],{"class":685},[679,153871,86928],{"class":685},[679,153873,153874],{"class":880}," Course",[679,153876,153877],{"class":693},"(String title, String url) {\n",[679,153879,153880],{"class":681,"line":790},[679,153881,996],{"class":693},[651,153883,153884],{},"This simple record will store the title and URL for each course.",[4542,153886,153888],{"id":153887},"step-2-implementing-the-service-layer","Step 2: Implementing the Service Layer",[651,153890,153891,153892,153895,153896,142964],{},"Next, let's create our ",[676,153893,153894],{},"CourseService"," class. This is where we'll define our MCP tools using the ",[676,153897,153898],{},"@Tool",[669,153900,153902],{"className":4107,"code":153901,"language":4109,"meta":674,"style":674},"@Service\npublic class CourseService {\n\n private static final Logger log = LoggerFactory.getLogger(CourseService.class);\n private List\u003CCourse> courses = new ArrayList\u003C>();\n\n @Tool(name = \"dv_get_courses\", description = \"Get a list of courses from Dan Vega\")\n public List\u003CCourse> getCourses() {\n return courses;\n }\n\n @Tool(name = \"dv_get_course\", description = \"Get a single course from Dan Vega by title\")\n public Course getCourse(String title) {\n return courses.stream()\n .filter(course -> course.title().equals(title))\n .findFirst()\n .orElse(null);\n }\n\n @PostConstruct\n public void init() {\n courses.addAll(List.of(\n new Course(\"Building Web Applications with Spring Boot (FreeCodeCamp)\", \n \"https://youtu.be/31KTdfRH6nY\"),\n new Course(\"Spring Boot Tutorial for Beginners - 2023 Crash Course using Spring Boot 3\",\n \"https://youtu.be/UgX5lgv4uVM\")\n ));\n }\n}\n",[676,153903,153904,153910,153921,153925,153944,153961,153965,153992,154007,154014,154018,154022,154048,154064,154075,154098,154106,154119,154123,154127,154133,154143,154157,154170,154177,154190,154197,154202,154206],{"__ignoreMap":674},[679,153905,153906,153908],{"class":681,"line":682},[679,153907,4116],{"class":693},[679,153909,9486],{"class":685},[679,153911,153912,153914,153916,153919],{"class":681,"line":790},[679,153913,6073],{"class":685},[679,153915,4512],{"class":685},[679,153917,153918],{"class":880}," CourseService",[679,153920,884],{"class":693},[679,153922,153923],{"class":681,"line":892},[679,153924,889],{"emptyLinePlaceholder":797},[679,153926,153927,153929,153931,153933,153935,153937,153939,153941],{"class":681,"line":901},[679,153928,9232],{"class":685},[679,153930,6092],{"class":685},[679,153932,12768],{"class":685},[679,153934,111111],{"class":693},[679,153936,686],{"class":685},[679,153938,9240],{"class":693},[679,153940,9243],{"class":880},[679,153942,153943],{"class":693},"(CourseService.class);\n",[679,153945,153946,153948,153950,153952,153955,153957,153959],{"class":681,"line":909},[679,153947,9232],{"class":685},[679,153949,87217],{"class":693},[679,153951,32709],{"class":685},[679,153953,153954],{"class":693},"> courses ",[679,153956,686],{"class":685},[679,153958,2054],{"class":685},[679,153960,87229],{"class":693},[679,153962,153963],{"class":681,"line":918},[679,153964,889],{"emptyLinePlaceholder":797},[679,153966,153967,153969,153972,153974,153976,153978,153981,153983,153985,153987,153990],{"class":681,"line":935},[679,153968,6872],{"class":693},[679,153970,153971],{"class":685},"Tool",[679,153973,745],{"class":693},[679,153975,16334],{"class":931},[679,153977,6883],{"class":685},[679,153979,153980],{"class":689}," \"dv_get_courses\"",[679,153982,2797],{"class":693},[679,153984,47867],{"class":931},[679,153986,6883],{"class":685},[679,153988,153989],{"class":689}," \"Get a list of courses from Dan Vega\"",[679,153991,1339],{"class":693},[679,153993,153994,153996,153998,154000,154002,154005],{"class":681,"line":944},[679,153995,6089],{"class":685},[679,153997,87217],{"class":693},[679,153999,32709],{"class":685},[679,154001,20881],{"class":693},[679,154003,154004],{"class":880},"getCourses",[679,154006,2667],{"class":693},[679,154008,154009,154011],{"class":681,"line":959},[679,154010,9444],{"class":685},[679,154012,154013],{"class":693}," courses;\n",[679,154015,154016],{"class":681,"line":964},[679,154017,985],{"class":693},[679,154019,154020],{"class":681,"line":977},[679,154021,889],{"emptyLinePlaceholder":797},[679,154023,154024,154026,154028,154030,154032,154034,154037,154039,154041,154043,154046],{"class":681,"line":982},[679,154025,6872],{"class":693},[679,154027,153971],{"class":685},[679,154029,745],{"class":693},[679,154031,16334],{"class":931},[679,154033,6883],{"class":685},[679,154035,154036],{"class":689}," \"dv_get_course\"",[679,154038,2797],{"class":693},[679,154040,47867],{"class":931},[679,154042,6883],{"class":685},[679,154044,154045],{"class":689}," \"Get a single course from Dan Vega by title\"",[679,154047,1339],{"class":693},[679,154049,154050,154052,154055,154058,154060,154062],{"class":681,"line":988},[679,154051,6089],{"class":685},[679,154053,154054],{"class":693}," Course ",[679,154056,154057],{"class":880},"getCourse",[679,154059,11400],{"class":693},[679,154061,11750],{"class":2099},[679,154063,4390],{"class":693},[679,154065,154066,154068,154071,154073],{"class":681,"line":993},[679,154067,9444],{"class":685},[679,154069,154070],{"class":693}," courses.",[679,154072,87323],{"class":880},[679,154074,17545],{"class":693},[679,154076,154077,154079,154081,154084,154086,154089,154091,154093,154095],{"class":681,"line":2129},[679,154078,40148],{"class":693},[679,154080,65029],{"class":880},[679,154082,154083],{"class":693},"(course ",[679,154085,16955],{"class":685},[679,154087,154088],{"class":693}," course.",[679,154090,11750],{"class":880},[679,154092,10541],{"class":693},[679,154094,14592],{"class":880},[679,154096,154097],{"class":693},"(title))\n",[679,154099,154100,154102,154104],{"class":681,"line":2140},[679,154101,40148],{"class":693},[679,154103,87355],{"class":880},[679,154105,17545],{"class":693},[679,154107,154108,154110,154113,154115,154117],{"class":681,"line":2145},[679,154109,40148],{"class":693},[679,154111,154112],{"class":880},"orElse",[679,154114,745],{"class":693},[679,154116,1146],{"class":931},[679,154118,1208],{"class":693},[679,154120,154121],{"class":681,"line":2154},[679,154122,985],{"class":693},[679,154124,154125],{"class":681,"line":2159},[679,154126,889],{"emptyLinePlaceholder":797},[679,154128,154129,154131],{"class":681,"line":2164},[679,154130,6872],{"class":693},[679,154132,87391],{"class":685},[679,154134,154135,154137,154139,154141],{"class":681,"line":3134},[679,154136,6089],{"class":685},[679,154138,6095],{"class":685},[679,154140,36742],{"class":880},[679,154142,2667],{"class":693},[679,154144,154145,154148,154151,154153,154155],{"class":681,"line":3139},[679,154146,154147],{"class":693}," courses.",[679,154149,154150],{"class":880},"addAll",[679,154152,116593],{"class":693},[679,154154,16672],{"class":880},[679,154156,21337],{"class":693},[679,154158,154159,154161,154163,154165,154168],{"class":681,"line":3144},[679,154160,84746],{"class":685},[679,154162,153874],{"class":880},[679,154164,745],{"class":693},[679,154166,154167],{"class":689},"\"Building Web Applications with Spring Boot (FreeCodeCamp)\"",[679,154169,144058],{"class":693},[679,154171,154172,154175],{"class":681,"line":3149},[679,154173,154174],{"class":689}," \"https://youtu.be/31KTdfRH6nY\"",[679,154176,66689],{"class":693},[679,154178,154179,154181,154183,154185,154188],{"class":681,"line":3169},[679,154180,84746],{"class":685},[679,154182,153874],{"class":880},[679,154184,745],{"class":693},[679,154186,154187],{"class":689},"\"Spring Boot Tutorial for Beginners - 2023 Crash Course using Spring Boot 3\"",[679,154189,12083],{"class":693},[679,154191,154192,154195],{"class":681,"line":3185},[679,154193,154194],{"class":689}," \"https://youtu.be/UgX5lgv4uVM\"",[679,154196,1339],{"class":693},[679,154198,154199],{"class":681,"line":3194},[679,154200,154201],{"class":693}," ));\n",[679,154203,154204],{"class":681,"line":3199},[679,154205,985],{"class":693},[679,154207,154208],{"class":681,"line":3212},[679,154209,996],{"class":693},[651,154211,154212],{},"The key aspects of this service class are:",[5316,154214,154215,154221,154227,154233],{},[5332,154216,154217,154220],{},[2939,154218,154219],{},"@Tool Annotation",": Transforms regular methods into MCP-compatible tools",[5332,154222,154223,154226],{},[2939,154224,154225],{},"Tool Properties",": Each tool has a unique name and descriptive text to help AI models understand its purpose",[5332,154228,154229,154232],{},[2939,154230,154231],{},"Method Parameters",": Parameters become tool arguments that can be passed by the AI model",[5332,154234,154235,154238],{},[2939,154236,154237],{},"Return Types",": The data returned is automatically serialized into a format the AI model can understand",[651,154240,154241,154242,154244],{},"For this example, we're using an in-memory list to store our courses, populated in the ",[676,154243,87571],{}," method. In a real application, you might connect to a database or external API.",[4542,154246,154248],{"id":154247},"step-3-registering-tools-with-mcp","Step 3: Registering Tools with MCP",[651,154250,154251],{},"Now, let's set up our main application class to register our tools with the MCP framework:",[669,154253,154255],{"className":4107,"code":154254,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class CoursesApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(CoursesApplication.class, args);\n }\n\n @Bean\n public List\u003CToolCallback> danTools(CourseService courseService) {\n return List.of(ToolCallbacks.from(courseService));\n }\n}\n",[676,154256,154257,154263,154274,154278,154298,154307,154311,154315,154321,154343,154359,154363],{"__ignoreMap":674},[679,154258,154259,154261],{"class":681,"line":682},[679,154260,4116],{"class":693},[679,154262,6068],{"class":685},[679,154264,154265,154267,154269,154272],{"class":681,"line":790},[679,154266,6073],{"class":685},[679,154268,4512],{"class":685},[679,154270,154271],{"class":880}," CoursesApplication",[679,154273,884],{"class":693},[679,154275,154276],{"class":681,"line":892},[679,154277,889],{"emptyLinePlaceholder":797},[679,154279,154280,154282,154284,154286,154288,154290,154292,154294,154296],{"class":681,"line":901},[679,154281,6089],{"class":685},[679,154283,6092],{"class":685},[679,154285,6095],{"class":685},[679,154287,6098],{"class":880},[679,154289,745],{"class":693},[679,154291,4758],{"class":685},[679,154293,16901],{"class":693},[679,154295,6108],{"class":2099},[679,154297,4390],{"class":693},[679,154299,154300,154302,154304],{"class":681,"line":909},[679,154301,6115],{"class":693},[679,154303,6118],{"class":880},[679,154305,154306],{"class":693},"(CoursesApplication.class, args);\n",[679,154308,154309],{"class":681,"line":918},[679,154310,985],{"class":693},[679,154312,154313],{"class":681,"line":935},[679,154314,889],{"emptyLinePlaceholder":797},[679,154316,154317,154319],{"class":681,"line":944},[679,154318,6872],{"class":693},[679,154320,16929],{"class":685},[679,154322,154323,154325,154327,154330,154332,154335,154338,154341],{"class":681,"line":959},[679,154324,6089],{"class":685},[679,154326,87217],{"class":693},[679,154328,154329],{"class":685},"ToolCallback",[679,154331,20881],{"class":693},[679,154333,154334],{"class":880},"danTools",[679,154336,154337],{"class":693},"(CourseService ",[679,154339,154340],{"class":2099},"courseService",[679,154342,4390],{"class":693},[679,154344,154345,154347,154349,154351,154354,154356],{"class":681,"line":964},[679,154346,9444],{"class":685},[679,154348,16669],{"class":693},[679,154350,16672],{"class":880},[679,154352,154353],{"class":693},"(ToolCallbacks.",[679,154355,28887],{"class":880},[679,154357,154358],{"class":693},"(courseService));\n",[679,154360,154361],{"class":681,"line":977},[679,154362,985],{"class":693},[679,154364,154365],{"class":681,"line":982},[679,154366,996],{"class":693},[651,154368,40060,154369,154372,154373,154375],{},[676,154370,154371],{},"ToolCallbacks.from()"," method scans the service class for ",[676,154374,153898],{}," annotations and registers them with the MCP framework. This is where Spring's component scanning capabilities really shine - with just a few lines of code, we've registered our tools.",[4542,154377,154379],{"id":154378},"step-4-configuring-the-mcp-server","Step 4: Configuring the MCP Server",[651,154381,154382,154383,2391],{},"Finally, let's configure our MCP server through ",[676,154384,16242],{},[669,154386,154388],{"className":76589,"code":154387,"language":35538,"meta":674,"style":674},"spring.main.web-application-type=none\nspring.ai.mcp.server.name=dan-vega-mcp\nspring.ai.mcp.server.version=0.0.1\n\n# NOTE: You must disable the banner and the console logging\n# to allow the STDIO transport to work !!!\nspring.main.banner-mode=off\nlogging.pattern.console=\n",[676,154389,154390,154398,154406,154414,154418,154423,154428,154434],{"__ignoreMap":674},[679,154391,154392,154395],{"class":681,"line":682},[679,154393,154394],{"class":685},"spring.main.web-application-type",[679,154396,154397],{"class":693},"=none\n",[679,154399,154400,154403],{"class":681,"line":790},[679,154401,154402],{"class":685},"spring.ai.mcp.server.name",[679,154404,154405],{"class":693},"=dan-vega-mcp\n",[679,154407,154408,154411],{"class":681,"line":892},[679,154409,154410],{"class":685},"spring.ai.mcp.server.version",[679,154412,154413],{"class":693},"=0.0.1\n",[679,154415,154416],{"class":681,"line":901},[679,154417,889],{"emptyLinePlaceholder":797},[679,154419,154420],{"class":681,"line":909},[679,154421,154422],{"class":1400},"# NOTE: You must disable the banner and the console logging\n",[679,154424,154425],{"class":681,"line":918},[679,154426,154427],{"class":1400},"# to allow the STDIO transport to work !!!\n",[679,154429,154430,154432],{"class":681,"line":935},[679,154431,100679],{"class":685},[679,154433,100674],{"class":693},[679,154435,154436,154439],{"class":681,"line":944},[679,154437,154438],{"class":685},"logging.pattern.console",[679,154440,93517],{"class":693},[651,154442,154443],{},"This configuration does several important things:",[27665,154445,154446,154452,154458],{},[5332,154447,154448,154451],{},[2939,154449,154450],{},"Disables Web Application",": Since we're using STDIO transport for MCP, we don't need a web server",[5332,154453,154454,154457],{},[2939,154455,154456],{},"Sets Server Name and Version",": Identifies our MCP server to clients",[5332,154459,154460,154463],{},[2939,154461,154462],{},"Disables Banner and Console Logging",": Critical for STDIO transport to work correctly",[651,154465,154466],{},"The STDIO transport mechanism allows our MCP server to communicate through standard input/output streams, which is what Claude Desktop uses for local MCP servers.",[4542,154468,154470],{"id":154469},"building-and-testing-the-mcp-server","Building and Testing the MCP Server",[651,154472,154473],{},"With all components in place, let's build our application:",[669,154475,154476],{"className":5851,"code":81915,"language":5853,"meta":674,"style":674},[676,154477,154478],{"__ignoreMap":674},[679,154479,154480,154482,154484],{"class":681,"line":682},[679,154481,81922],{"class":880},[679,154483,81925],{"class":689},[679,154485,32496],{"class":689},[651,154487,94187,154488,154490],{},[676,154489,77995],{}," directory that we can use to run our MCP server.",[4542,154492,154494],{"id":154493},"connecting-to-claude-desktop","Connecting to Claude Desktop",[651,154496,154497],{},"To use our MCP server with Claude Desktop, we need to register it in the Claude Desktop configuration file. On macOS, this is located at:",[669,154499,154502],{"className":154500,"code":154501,"language":11464},[16247],"~/Library/Application Support/Claude/claude_desktop_config.json\n",[676,154503,154501],{"__ignoreMap":674},[651,154505,154506],{},"Add our MCP server to the configuration:",[669,154508,154510],{"className":28439,"code":154509,"language":28441,"meta":674,"style":674},"{\n \"dan-vega-mcp\": {\n \"command\": \"/path/to/java\",\n \"args\": [\n \"-jar\",\n \"/path/to/your/jar/courses-0.0.1-SNAPSHOT.jar\"\n ]\n }\n}\n",[676,154511,154512,154516,154523,154535,154542,154549,154554,154558,154562],{"__ignoreMap":674},[679,154513,154514],{"class":681,"line":682},[679,154515,28448],{"class":693},[679,154517,154518,154521],{"class":681,"line":790},[679,154519,154520],{"class":931}," \"dan-vega-mcp\"",[679,154522,28468],{"class":693},[679,154524,154525,154528,154530,154533],{"class":681,"line":892},[679,154526,154527],{"class":931}," \"command\"",[679,154529,4282],{"class":693},[679,154531,154532],{"class":689},"\"/path/to/java\"",[679,154534,12083],{"class":693},[679,154536,154537,154540],{"class":681,"line":901},[679,154538,154539],{"class":931}," \"args\"",[679,154541,28491],{"class":693},[679,154543,154544,154547],{"class":681,"line":909},[679,154545,154546],{"class":689}," \"-jar\"",[679,154548,12083],{"class":693},[679,154550,154551],{"class":681,"line":918},[679,154552,154553],{"class":689}," \"/path/to/your/jar/courses-0.0.1-SNAPSHOT.jar\"\n",[679,154555,154556],{"class":681,"line":935},[679,154557,52918],{"class":693},[679,154559,154560],{"class":681,"line":944},[679,154561,21405],{"class":693},[679,154563,154564],{"class":681,"line":959},[679,154565,996],{"class":693},[651,154567,154568],{},"Make sure to update the paths to match your environment:",[5316,154570,154571,154577],{},[5332,154572,149977,154573,154576],{},[676,154574,154575],{},"/path/to/java"," with the path to your Java executable",[5332,154578,149977,154579,154582],{},[676,154580,154581],{},"/path/to/your/jar/courses-0.0.1-SNAPSHOT.jar"," with the path to your built JAR file",[651,154584,154585],{},"Save the configuration file and restart Claude Desktop. You should now see your MCP server listed in the Claude Desktop interface, along with the tools it provides.",[4542,154587,154589],{"id":154588},"testing-the-mcp-server","Testing the MCP Server",[651,154591,154592],{},"To test our MCP server, simply ask Claude a question about the available courses:",[651,154594,154595],{},[660,154596],{"alt":154597,"src":154598},"Claude Desktop with MCP Tools","/images/blog/2025/03/26/claude-desktop-tools.png",[651,154600,154601],{},"You might ask:",[5316,154603,154604,154607],{},[5332,154605,154606],{},"\"What courses does Dan Vega have available?\"",[5332,154608,154609],{},"\"Can you tell me about Dan's Spring Boot courses?\"",[651,154611,154612],{},"Claude will request permission to use the appropriate tool, retrieve the course information from your MCP server, and include it in its response.",[4542,154614,154616],{"id":154615},"advanced-concepts-and-next-steps","Advanced Concepts and Next Steps",[651,154618,154619],{},"Now that you have a working MCP server, here are some ways you could extend it:",[5909,154621,154623],{"id":154622},"adding-more-sophisticated-tools","Adding More Sophisticated Tools",[651,154625,154626],{},"You can add more complex tools that accept multiple parameters or perform more advanced operations:",[669,154628,154630],{"className":4107,"code":154629,"language":4109,"meta":674,"style":674},"@Tool(name = \"dv_search_courses\", description = \"Search courses containing a keyword\")\npublic List\u003CCourse> searchCourses(String keyword) {\n return courses.stream()\n .filter(course -> course.title().toLowerCase().contains(keyword.toLowerCase()))\n .collect(Collectors.toList());\n}\n",[676,154631,154632,154658,154676,154686,154716,154728],{"__ignoreMap":674},[679,154633,154634,154636,154638,154640,154642,154644,154647,154649,154651,154653,154656],{"class":681,"line":682},[679,154635,4116],{"class":693},[679,154637,153971],{"class":685},[679,154639,745],{"class":693},[679,154641,16334],{"class":931},[679,154643,6883],{"class":685},[679,154645,154646],{"class":689}," \"dv_search_courses\"",[679,154648,2797],{"class":693},[679,154650,47867],{"class":931},[679,154652,6883],{"class":685},[679,154654,154655],{"class":689}," \"Search courses containing a keyword\"",[679,154657,1339],{"class":693},[679,154659,154660,154662,154664,154666,154668,154670,154673],{"class":681,"line":790},[679,154661,6073],{"class":685},[679,154663,96047],{"class":693},[679,154665,4505],{"class":685},[679,154667,32709],{"class":693},[679,154669,5860],{"class":685},[679,154671,154672],{"class":880}," searchCourses",[679,154674,154675],{"class":693},"(String keyword) {\n",[679,154677,154678,154680,154682,154684],{"class":681,"line":892},[679,154679,21478],{"class":685},[679,154681,154070],{"class":693},[679,154683,87323],{"class":880},[679,154685,17545],{"class":693},[679,154687,154688,154690,154692,154694,154696,154698,154700,154702,154704,154706,154709,154712,154714],{"class":681,"line":901},[679,154689,21331],{"class":693},[679,154691,65029],{"class":880},[679,154693,154083],{"class":693},[679,154695,16955],{"class":685},[679,154697,154088],{"class":693},[679,154699,11750],{"class":880},[679,154701,10541],{"class":693},[679,154703,152962],{"class":880},[679,154705,10541],{"class":693},[679,154707,154708],{"class":880},"contains",[679,154710,154711],{"class":693},"(keyword.",[679,154713,152962],{"class":880},[679,154715,104226],{"class":693},[679,154717,154718,154720,154722,154724,154726],{"class":681,"line":909},[679,154719,21331],{"class":693},[679,154721,90908],{"class":880},[679,154723,90911],{"class":693},[679,154725,85083],{"class":880},[679,154727,9431],{"class":693},[679,154729,154730],{"class":681,"line":918},[679,154731,996],{"class":693},[5909,154733,154735],{"id":154734},"creating-native-executables","Creating Native Executables",[651,154737,154738],{},"For easier distribution, you can build your MCP server as a native executable using Spring Native and GraalVM:",[669,154740,154742],{"className":9101,"code":154741,"language":9103,"meta":674,"style":674},"\u003Cplugin>\n \u003CgroupId>org.graalvm.buildtools\u003C/groupId>\n \u003CartifactId>native-maven-plugin\u003C/artifactId>\n\u003C/plugin>\n",[676,154743,154744,154752,154765,154778],{"__ignoreMap":674},[679,154745,154746,154748,154750],{"class":681,"line":682},[679,154747,4505],{"class":693},[679,154749,24405],{"class":4508},[679,154751,4519],{"class":693},[679,154753,154754,154756,154758,154761,154763],{"class":681,"line":790},[679,154755,4524],{"class":693},[679,154757,119847],{"class":4508},[679,154759,154760],{"class":693},">org.graalvm.buildtools\u003C/",[679,154762,119847],{"class":4508},[679,154764,4519],{"class":693},[679,154766,154767,154769,154771,154774,154776],{"class":681,"line":892},[679,154768,4524],{"class":693},[679,154770,119861],{"class":4508},[679,154772,154773],{"class":693},">native-maven-plugin\u003C/",[679,154775,119861],{"class":4508},[679,154777,4519],{"class":693},[679,154779,154780,154782,154784],{"class":681,"line":901},[679,154781,4586],{"class":693},[679,154783,24405],{"class":4508},[679,154785,4519],{"class":693},[651,154787,154788],{},"This creates a standalone binary that doesn't require Java to be installed on the target system.",[5909,154790,154792],{"id":154791},"integrating-with-external-data-sources","Integrating with External Data Sources",[651,154794,154795],{},"Connect your MCP server to databases, APIs, or other external systems to provide AI models with access to your organization's data:",[669,154797,154799],{"className":4107,"code":154798,"language":4109,"meta":674,"style":674},"@Service\npublic class ProductService {\n private final ProductRepository repository;\n \n @Tool(name = \"find_products\", description = \"Find products matching criteria\")\n public List\u003CProduct> findProducts(String category, Double maxPrice) {\n return repository.findByCategoryAndPriceLessThan(category, maxPrice);\n }\n}\n",[676,154800,154801,154807,154818,154826,154830,154856,154881,154893,154897],{"__ignoreMap":674},[679,154802,154803,154805],{"class":681,"line":682},[679,154804,4116],{"class":693},[679,154806,9486],{"class":685},[679,154808,154809,154811,154813,154816],{"class":681,"line":790},[679,154810,6073],{"class":685},[679,154812,4512],{"class":685},[679,154814,154815],{"class":880}," ProductService",[679,154817,884],{"class":693},[679,154819,154820,154822,154824],{"class":681,"line":892},[679,154821,9232],{"class":685},[679,154823,12768],{"class":685},[679,154825,98627],{"class":693},[679,154827,154828],{"class":681,"line":901},[679,154829,119642],{"class":693},[679,154831,154832,154834,154836,154838,154840,154842,154845,154847,154849,154851,154854],{"class":681,"line":909},[679,154833,6872],{"class":693},[679,154835,153971],{"class":685},[679,154837,745],{"class":693},[679,154839,16334],{"class":931},[679,154841,6883],{"class":685},[679,154843,154844],{"class":689}," \"find_products\"",[679,154846,2797],{"class":693},[679,154848,47867],{"class":931},[679,154850,6883],{"class":685},[679,154852,154853],{"class":689}," \"Find products matching criteria\"",[679,154855,1339],{"class":693},[679,154857,154858,154860,154862,154864,154866,154869,154871,154873,154876,154879],{"class":681,"line":918},[679,154859,6089],{"class":685},[679,154861,87217],{"class":693},[679,154863,97605],{"class":685},[679,154865,20881],{"class":693},[679,154867,154868],{"class":880},"findProducts",[679,154870,11400],{"class":693},[679,154872,150824],{"class":2099},[679,154874,154875],{"class":693},", Double ",[679,154877,154878],{"class":2099},"maxPrice",[679,154880,4390],{"class":693},[679,154882,154883,154885,154887,154890],{"class":681,"line":935},[679,154884,9444],{"class":685},[679,154886,85606],{"class":693},[679,154888,154889],{"class":880},"findByCategoryAndPriceLessThan",[679,154891,154892],{"class":693},"(category, maxPrice);\n",[679,154894,154895],{"class":681,"line":944},[679,154896,985],{"class":693},[679,154898,154899],{"class":681,"line":959},[679,154900,996],{"class":693},[4542,154902,9042],{"id":9041},[651,154904,154905],{},"Congratulations! You've built your first MCP server with Spring AI. This opens up a world of possibilities for extending AI models with your custom data and services.",[651,154907,154908],{},"The Model Context Protocol bridges the gap between powerful AI models and your specific domain knowledge, allowing you to create more useful and context-aware AI applications. With Spring AI's MCP server implementation, Java developers can easily participate in this exciting ecosystem.",[651,154910,154911],{},"What will you build with your new MCP server? Perhaps a connection to your CRM system, a gateway to your internal knowledge base, or a tool to manipulate your organization's data? The possibilities are endless!",[4542,154913,21931],{"id":21930},[5316,154915,154916,154922,154929,154935],{},[5332,154917,154918],{},[812,154919,154921],{"href":140778,"rel":154920},[816],"Spring AI Documentation",[5332,154923,154924],{},[812,154925,154928],{"href":154926,"rel":154927},"https://github.com/anthropics/anthropic-tools/blob/main/model-context-protocol.md",[816],"Model Context Protocol Specification",[5332,154930,154931],{},[812,154932,150488],{"href":154933,"rel":154934},"https://github.com/spring-projects/spring-ai",[816],[5332,154936,154937],{},[812,154938,154941],{"href":154939,"rel":154940},"https://www.danvega.dev/blog/model-context-protocol-introduction",[816],"My Introduction to Model Context Protocol",[651,154943,78024],{},[786,154945,154946],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":154948},[154949,154950,154951,154952,154953,154954,154955,154956,154957,154958,154959,154960,154961,154962,154967,154968],{"id":149709,"depth":790,"text":149710},{"id":153711,"depth":790,"text":153712},{"id":153743,"depth":790,"text":153744},{"id":44198,"depth":790,"text":44199},{"id":51522,"depth":790,"text":153780},{"id":153821,"depth":790,"text":153822},{"id":153853,"depth":790,"text":153854},{"id":153887,"depth":790,"text":153888},{"id":154247,"depth":790,"text":154248},{"id":154378,"depth":790,"text":154379},{"id":154469,"depth":790,"text":154470},{"id":154493,"depth":790,"text":154494},{"id":154588,"depth":790,"text":154589},{"id":154615,"depth":790,"text":154616,"children":154963},[154964,154965,154966],{"id":154622,"depth":892,"text":154623},{"id":154734,"depth":892,"text":154735},{"id":154791,"depth":892,"text":154792},{"id":9041,"depth":790,"text":9042},{"id":21930,"depth":790,"text":21931},"Learn how to build and deploy a custom MCP server with Java to extend AI models with your own data and services.",{"slug":154971,"date":154972,"published":797,"author":798,"tags":154973,"video":154974,"github":154975,"keywords":154976},"creating-your-first-mcp-server-java","2025-03-26T17:00:00.000Z",[36422,23816,150571],"https://www.youtube.com/embed/w5YVHG1j3Co","https://github.com/danvega/dv-courses-mcp","model context protocol, mcp, spring ai, spring boot, java, ai integration, claude, mcp server, tool integration",{"title":12,"description":154969},"blog/2025/03/26/creating-your-first-mcp-server-java","-r_NNQyjxwnJ1m1r7nwV7e8KJsqQFQzmr1ldEblqTlU",{"id":154981,"title":9,"body":154982,"description":156099,"extension":793,"meta":156100,"navigation":797,"path":10,"seo":156106,"stem":156107,"__hash__":156108},"content/blog/2025/04/07/docker-model-runner.md",{"type":648,"value":154983,"toc":156079},[154984,154987,154990,154993,154997,155000,155032,155035,155039,155042,155059,155062,155066,155069,155086,155089,155107,155110,155124,155127,155142,155145,155151,155155,155158,155177,155182,155390,155392,155397,155424,155427,155447,155451,155457,155620,155623,155643,155646,155648,155651,155661,155664,155670,155674,155677,155681,155684,155864,155866,155869,155958,155962,155965,155979,155983,155987,155994,156012,156016,156019,156036,156038,156041,156055,156058,156061,156074,156076],[651,154985,154986],{},"Are you tired of sending your data to cloud APIs just to use AI in your applications? What if you could run powerful AI models right on your machine with zero API keys, zero data sharing, and zero monthly fees?",[651,154988,154989],{},"Docker Desktop recently introduced an exciting new feature called Model Runner that allows developers to run open-source AI models locally. When combined with Spring AI, this creates a powerful platform for building AI-powered applications that respect privacy, control costs, and simplify development workflows.",[651,154991,154992],{},"In this post, I'll show you how to use Docker's Model Runner feature with Spring Boot applications to create fully local AI experiences in just 15 minutes.",[4542,154994,154996],{"id":154995},"why-run-ai-models-locally","Why Run AI Models Locally?",[651,154998,154999],{},"Before diving into the technical details, let's consider why running AI models locally matters:",[27665,155001,155002,155008,155014,155020,155026],{},[5332,155003,155004,155007],{},[2939,155005,155006],{},"Privacy"," - Your data never leaves your machine",[5332,155009,155010,155013],{},[2939,155011,155012],{},"Cost control"," - No usage-based billing or subscription fees",[5332,155015,155016,155019],{},[2939,155017,155018],{},"Reliability"," - No dependency on external API availability",[5332,155021,155022,155025],{},[2939,155023,155024],{},"Development simplicity"," - Test and iterate without API keys or quotas",[5332,155027,155028,155031],{},[2939,155029,155030],{},"Learning opportunity"," - Understand how AI models actually work",[651,155033,155034],{},"For Spring developers building modern applications, this local approach provides a compelling alternative to cloud-based AI services while maintaining all the benefits of Spring's programming model.",[4542,155036,155038],{"id":155037},"understanding-docker-model-runner","Understanding Docker Model Runner",[651,155040,155041],{},"Docker Model Runner is a plugin for Docker Desktop that allows you to:",[5316,155043,155044,155047,155050,155053,155056],{},[5332,155045,155046],{},"Pull open-source models from Docker Hub",[5332,155048,155049],{},"Run models directly from the command line",[5332,155051,155052],{},"Manage local model installations",[5332,155054,155055],{},"Interact with models via prompts or chat mode",[5332,155057,155058],{},"Access models via an OpenAI-compatible API endpoint",[651,155060,155061],{},"Currently, Docker Model Runner only works on Docker Desktop for Mac with Apple Silicon (M1/M2/M3/M4 chips), but support for other platforms is coming soon.",[4542,155063,155065],{"id":155064},"setting-up-docker-for-local-ai","Setting Up Docker for Local AI",[651,155067,155068],{},"To get started, you'll need Docker Desktop 4.40 or later. Once installed, follow these steps:",[27665,155070,155071,155074,155077,155080,155083],{},[5332,155072,155073],{},"Open Docker Desktop",[5332,155075,155076],{},"Navigate to Settings → Features in development (Beta tab)",[5332,155078,155079],{},"Enable \"Docker Model Runner\"",[5332,155081,155082],{},"Also enable \"Enable host-side TCP support\" (leave the default port of 12434)",[5332,155084,155085],{},"Apply and restart Docker Desktop",[651,155087,155088],{},"Once configured, you can pull your first model using the Docker CLI:",[669,155090,155092],{"className":5851,"code":155091,"language":5853,"meta":674,"style":674},"docker model pull ai/gemma3\n",[676,155093,155094],{"__ignoreMap":674},[679,155095,155096,155098,155101,155104],{"class":681,"line":682},[679,155097,112824],{"class":880},[679,155099,155100],{"class":689}," model",[679,155102,155103],{"class":689}," pull",[679,155105,155106],{"class":689}," ai/gemma3\n",[651,155108,155109],{},"This will download Google's Gemma 3 model, which is a good balance of capability and resource usage. You can check which models you have installed with:",[669,155111,155113],{"className":5851,"code":155112,"language":5853,"meta":674,"style":674},"docker model list\n",[676,155114,155115],{"__ignoreMap":674},[679,155116,155117,155119,155121],{"class":681,"line":682},[679,155118,112824],{"class":880},[679,155120,155100],{"class":689},[679,155122,155123],{"class":689}," list\n",[651,155125,155126],{},"To test your setup, try running the model in interactive mode:",[669,155128,155130],{"className":5851,"code":155129,"language":5853,"meta":674,"style":674},"docker model run ai/gemma3\n",[676,155131,155132],{"__ignoreMap":674},[679,155133,155134,155136,155138,155140],{"class":681,"line":682},[679,155135,112824],{"class":880},[679,155137,155100],{"class":689},[679,155139,16486],{"class":689},[679,155141,155106],{"class":689},[651,155143,155144],{},"This launches an interactive chat session where you can directly interact with the model right in your terminal:",[669,155146,155149],{"className":155147,"code":155148,"language":11464},[16247],"> What is an interesting fact about Docker?\nDocker was originally developed as an internal project at a company called dotCloud, \nwhich was a Platform-as-a-Service company. The technology was later open-sourced in 2013 \nand became immensely popular, eventually leading to dotCloud pivoting their entire \nbusiness to focus on Docker. This pivot transformed the company into Docker, Inc.\n",[676,155150,155148],{"__ignoreMap":674},[4542,155152,155154],{"id":155153},"creating-a-spring-boot-application-with-spring-ai","Creating a Spring Boot Application with Spring AI",[651,155156,155157],{},"Now let's create a Spring Boot application that connects to this locally running model. The first step is to initialize a new project with the right dependencies:",[27665,155159,155160,155165,155168],{},[5332,155161,107375,155162],{},[812,155163,51358],{"href":51358,"rel":155164},[816],[5332,155166,155167],{},"Choose Maven, Java 17+, and the latest Spring Boot version",[5332,155169,136132,155170],{},[5316,155171,155172,155174],{},[5332,155173,80827],{},[5332,155175,155176],{},"Spring AI OpenAI (we'll use the OpenAI client since Docker exposes an OpenAI-compatible API)",[651,155178,138032,155179,155181],{},[676,155180,81517],{}," should include these key dependencies:",[669,155183,155185],{"className":9101,"code":155184,"language":9103,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-web\u003C/artifactId>\n\u003C/dependency>\n\u003Cdependency>\n \u003CgroupId>org.springframework.ai\u003C/groupId>\n \u003CartifactId>spring-ai-openai-spring-boot-starter\u003C/artifactId>\n\u003C/dependency>\n\n\u003C!-- In the dependency management section -->\n\u003CdependencyManagement>\n \u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.ai\u003C/groupId>\n \u003CartifactId>spring-ai-bom\u003C/artifactId>\n \u003Cversion>${spring-ai.version}\u003C/version>\n \u003Ctype>pom\u003C/type>\n \u003Cscope>import\u003C/scope>\n \u003C/dependency>\n \u003C/dependencies>\n\u003C/dependencyManagement>\n",[676,155186,155187,155195,155207,155219,155227,155235,155247,155259,155267,155271,155276,155285,155293,155301,155313,155326,155339,155352,155366,155374,155382],{"__ignoreMap":674},[679,155188,155189,155191,155193],{"class":681,"line":682},[679,155190,4505],{"class":693},[679,155192,119838],{"class":4508},[679,155194,4519],{"class":693},[679,155196,155197,155199,155201,155203,155205],{"class":681,"line":790},[679,155198,4524],{"class":693},[679,155200,119847],{"class":4508},[679,155202,119850],{"class":693},[679,155204,119847],{"class":4508},[679,155206,4519],{"class":693},[679,155208,155209,155211,155213,155215,155217],{"class":681,"line":892},[679,155210,4524],{"class":693},[679,155212,119861],{"class":4508},[679,155214,138077],{"class":693},[679,155216,119861],{"class":4508},[679,155218,4519],{"class":693},[679,155220,155221,155223,155225],{"class":681,"line":901},[679,155222,4586],{"class":693},[679,155224,119838],{"class":4508},[679,155226,4519],{"class":693},[679,155228,155229,155231,155233],{"class":681,"line":909},[679,155230,4505],{"class":693},[679,155232,119838],{"class":4508},[679,155234,4519],{"class":693},[679,155236,155237,155239,155241,155243,155245],{"class":681,"line":918},[679,155238,4524],{"class":693},[679,155240,119847],{"class":4508},[679,155242,129701],{"class":693},[679,155244,119847],{"class":4508},[679,155246,4519],{"class":693},[679,155248,155249,155251,155253,155255,155257],{"class":681,"line":935},[679,155250,4524],{"class":693},[679,155252,119861],{"class":4508},[679,155254,138118],{"class":693},[679,155256,119861],{"class":4508},[679,155258,4519],{"class":693},[679,155260,155261,155263,155265],{"class":681,"line":944},[679,155262,4586],{"class":693},[679,155264,119838],{"class":4508},[679,155266,4519],{"class":693},[679,155268,155269],{"class":681,"line":959},[679,155270,889],{"emptyLinePlaceholder":797},[679,155272,155273],{"class":681,"line":964},[679,155274,155275],{"class":1400},"\u003C!-- In the dependency management section -->\n",[679,155277,155278,155280,155283],{"class":681,"line":977},[679,155279,4505],{"class":693},[679,155281,155282],{"class":4508},"dependencyManagement",[679,155284,4519],{"class":693},[679,155286,155287,155289,155291],{"class":681,"line":982},[679,155288,4524],{"class":693},[679,155290,129682],{"class":4508},[679,155292,4519],{"class":693},[679,155294,155295,155297,155299],{"class":681,"line":988},[679,155296,4904],{"class":693},[679,155298,119838],{"class":4508},[679,155300,4519],{"class":693},[679,155302,155303,155305,155307,155309,155311],{"class":681,"line":993},[679,155304,5392],{"class":693},[679,155306,119847],{"class":4508},[679,155308,129701],{"class":693},[679,155310,119847],{"class":4508},[679,155312,4519],{"class":693},[679,155314,155315,155317,155319,155322,155324],{"class":681,"line":2129},[679,155316,5392],{"class":693},[679,155318,119861],{"class":4508},[679,155320,155321],{"class":693},">spring-ai-bom\u003C/",[679,155323,119861],{"class":4508},[679,155325,4519],{"class":693},[679,155327,155328,155330,155332,155335,155337],{"class":681,"line":2140},[679,155329,5392],{"class":693},[679,155331,107166],{"class":4508},[679,155333,155334],{"class":693},">${spring-ai.version}\u003C/",[679,155336,107166],{"class":4508},[679,155338,4519],{"class":693},[679,155340,155341,155343,155345,155348,155350],{"class":681,"line":2145},[679,155342,5392],{"class":693},[679,155344,121968],{"class":4508},[679,155346,155347],{"class":693},">pom\u003C/",[679,155349,121968],{"class":4508},[679,155351,4519],{"class":693},[679,155353,155354,155356,155359,155362,155364],{"class":681,"line":2154},[679,155355,5392],{"class":693},[679,155357,155358],{"class":4508},"scope",[679,155360,155361],{"class":693},">import\u003C/",[679,155363,155358],{"class":4508},[679,155365,4519],{"class":693},[679,155367,155368,155370,155372],{"class":681,"line":2159},[679,155369,5480],{"class":693},[679,155371,119838],{"class":4508},[679,155373,4519],{"class":693},[679,155375,155376,155378,155380],{"class":681,"line":2164},[679,155377,4577],{"class":693},[679,155379,129682],{"class":4508},[679,155381,4519],{"class":693},[679,155383,155384,155386,155388],{"class":681,"line":3134},[679,155385,4586],{"class":693},[679,155387,155282],{"class":4508},[679,155389,4519],{"class":693},[5909,155391,6478],{"id":125381},[651,155393,155394,155395,94061],{},"The crucial step is configuring Spring AI to connect to your local Docker model instead of the actual OpenAI API. Add these properties to your ",[676,155396,16242],{},[669,155398,155400],{"className":76589,"code":155399,"language":35538,"meta":674,"style":674},"spring.ai.openai.api-key=_\nspring.ai.openai.chat.base-url=http://localhost:12434/engines/llama.cpp\nspring.ai.openai.chat.options.model=ai/gemma3\n",[676,155401,155402,155409,155417],{"__ignoreMap":674},[679,155403,155404,155406],{"class":681,"line":682},[679,155405,122493],{"class":685},[679,155407,155408],{"class":693},"=_\n",[679,155410,155411,155414],{"class":681,"line":790},[679,155412,155413],{"class":685},"spring.ai.openai.chat.base-url",[679,155415,155416],{"class":693},"=http://localhost:12434/engines/llama.cpp\n",[679,155418,155419,155421],{"class":681,"line":892},[679,155420,122501],{"class":685},[679,155422,155423],{"class":693},"=ai/gemma3\n",[651,155425,155426],{},"Let's break down what each property does:",[27665,155428,155429,155435,155441],{},[5332,155430,155431,155434],{},[676,155432,155433],{},"spring.ai.openai.api-key=_"," - We need to provide a value here because Spring AI expects it, but since we're not actually connecting to OpenAI, any value works",[5332,155436,155437,155440],{},[676,155438,155439],{},"spring.ai.openai.chat.base-url=http://localhost:12434/engines/llama.cpp"," - Points to the local Docker Model Runner API endpoint",[5332,155442,155443,155446],{},[676,155444,155445],{},"spring.ai.openai.chat.options.model=ai/gemma3"," - Specifies which model to use",[5909,155448,155450],{"id":155449},"application-code","Application Code",[651,155452,155453,155454,155456],{},"With our configuration in place, we can write a simple application to test the integration. Here's a basic example using Spring's ",[676,155455,16415],{}," to prompt the model after startup:",[669,155458,155460],{"className":4107,"code":155459,"language":4109,"meta":674,"style":674},"@SpringBootApplication\npublic class Application {\n\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n\n @Bean\n CommandLineRunner commandLineRunner(ChatClient.Builder builder) {\n return args -> {\n var client = builder.build();\n String response = client.prompt(\"When was Docker created?\")\n .call()\n .content();\n\n System.out.println(response);\n };\n }\n}\n",[676,155461,155462,155468,155478,155482,155502,155510,155514,155518,155524,155536,155546,155561,155579,155587,155595,155599,155608,155612,155616],{"__ignoreMap":674},[679,155463,155464,155466],{"class":681,"line":682},[679,155465,4116],{"class":693},[679,155467,6068],{"class":685},[679,155469,155470,155472,155474,155476],{"class":681,"line":790},[679,155471,6073],{"class":685},[679,155473,4512],{"class":685},[679,155475,16878],{"class":880},[679,155477,884],{"class":693},[679,155479,155480],{"class":681,"line":892},[679,155481,889],{"emptyLinePlaceholder":797},[679,155483,155484,155486,155488,155490,155492,155494,155496,155498,155500],{"class":681,"line":901},[679,155485,6089],{"class":685},[679,155487,6092],{"class":685},[679,155489,6095],{"class":685},[679,155491,6098],{"class":880},[679,155493,745],{"class":693},[679,155495,4758],{"class":685},[679,155497,16901],{"class":693},[679,155499,6108],{"class":2099},[679,155501,4390],{"class":693},[679,155503,155504,155506,155508],{"class":681,"line":909},[679,155505,6115],{"class":693},[679,155507,6118],{"class":880},[679,155509,16914],{"class":693},[679,155511,155512],{"class":681,"line":918},[679,155513,985],{"class":693},[679,155515,155516],{"class":681,"line":935},[679,155517,889],{"emptyLinePlaceholder":797},[679,155519,155520,155522],{"class":681,"line":944},[679,155521,6872],{"class":693},[679,155523,16929],{"class":685},[679,155525,155526,155528,155530,155532,155534],{"class":681,"line":959},[679,155527,20982],{"class":693},[679,155529,23712],{"class":880},[679,155531,121663],{"class":693},[679,155533,90934],{"class":2099},[679,155535,4390],{"class":693},[679,155537,155538,155540,155542,155544],{"class":681,"line":964},[679,155539,9444],{"class":685},[679,155541,16952],{"class":693},[679,155543,16955],{"class":685},[679,155545,884],{"class":693},[679,155547,155548,155551,155553,155555,155557,155559],{"class":681,"line":977},[679,155549,155550],{"class":685}," var",[679,155552,123983],{"class":693},[679,155554,686],{"class":685},[679,155556,136298],{"class":693},[679,155558,23612],{"class":880},[679,155560,9317],{"class":693},[679,155562,155563,155566,155568,155570,155572,155574,155577],{"class":681,"line":982},[679,155564,155565],{"class":693}," String response ",[679,155567,686],{"class":685},[679,155569,124005],{"class":693},[679,155571,55494],{"class":880},[679,155573,745],{"class":693},[679,155575,155576],{"class":689},"\"When was Docker created?\"",[679,155578,1339],{"class":693},[679,155580,155581,155583,155585],{"class":681,"line":988},[679,155582,89730],{"class":693},[679,155584,121783],{"class":880},[679,155586,17545],{"class":693},[679,155588,155589,155591,155593],{"class":681,"line":993},[679,155590,89730],{"class":693},[679,155592,47833],{"class":880},[679,155594,9317],{"class":693},[679,155596,155597],{"class":681,"line":2129},[679,155598,889],{"emptyLinePlaceholder":797},[679,155600,155601,155603,155605],{"class":681,"line":2140},[679,155602,15660],{"class":693},[679,155604,1729],{"class":880},[679,155606,155607],{"class":693},"(response);\n",[679,155609,155610],{"class":681,"line":2145},[679,155611,17018],{"class":693},[679,155613,155614],{"class":681,"line":2154},[679,155615,985],{"class":693},[679,155617,155618],{"class":681,"line":2159},[679,155619,996],{"class":693},[651,155621,155622],{},"This minimal example:",[27665,155624,155625,155628,155634,155640],{},[5332,155626,155627],{},"Creates a Spring Boot application",[5332,155629,155630,155631,155633],{},"Configures a ",[676,155632,16415],{}," that executes after startup",[5332,155635,155636,155637,155639],{},"Uses Spring AI's ",[676,155638,122385],{}," to send a prompt to the AI model",[5332,155641,155642],{},"Prints the model's response to the console",[651,155644,155645],{},"The beauty of this approach is that you're using Spring AI's abstractions, which means your code remains identical whether you're using a local model or a cloud-based one. If you later decide to switch to actual OpenAI or another provider, you only need to change your configuration, not your code.",[4542,155647,98048],{"id":7309},[651,155649,155650],{},"To run the application, make sure Docker Desktop is running with your model pulled, then use:",[669,155652,155653],{"className":5851,"code":36339,"language":5853,"meta":674,"style":674},[676,155654,155655],{"__ignoreMap":674},[679,155656,155657,155659],{"class":681,"line":682},[679,155658,32493],{"class":880},[679,155660,36348],{"class":689},[651,155662,155663],{},"You should see output similar to:",[669,155665,155668],{"className":155666,"code":155667,"language":11464},[16247],"Docker was officially created in July of 2013. Here's a breakdown of the key milestones:\n\n1. The project began as an internal project at dotCloud, a Platform-as-a-Service company\n2. Solomon Hykes presented Docker at PyCon in March 2013 with the famous \"Docker in 5 minutes\" demo\n3. The open-source release was in March 2013\n4. Docker, Inc. (the company) was officially formed in July 2013 when dotCloud pivoted to focus on Docker\n\nDocker's containerization technology quickly gained popularity because it solved many deployment challenges by packaging applications with their dependencies, making them portable across different environments.\n",[676,155669,155667],{"__ignoreMap":674},[4542,155671,155673],{"id":155672},"beyond-basic-integration","Beyond Basic Integration",[651,155675,155676],{},"Once you have the basic integration working, you can expand your application in several ways:",[5909,155678,155680],{"id":155679},"building-a-conversational-interface","Building a Conversational Interface",[651,155682,155683],{},"Instead of one-off prompts, you can create a conversational interface by adding memory:",[669,155685,155687],{"className":4107,"code":155686,"language":4109,"meta":674,"style":674},"@RestController\npublic class ChatController {\n\n private final ChatClient chatClient;\n private final InMemoryChatMemory memory = new InMemoryChatMemory();\n\n public ChatController(ChatClient.Builder builder) {\n this.chatClient = builder\n .defaultAdvisors(new MessageChatMemoryAdvisor(memory))\n .build();\n }\n\n @PostMapping(\"/chat\")\n public String chat(@RequestBody String prompt) {\n return chatClient.prompt()\n .user(prompt)\n .call()\n .content();\n }\n}\n",[676,155688,155689,155695,155705,155709,155717,155734,155738,155750,155760,155775,155783,155787,155791,155803,155821,155831,155840,155848,155856,155860],{"__ignoreMap":674},[679,155690,155691,155693],{"class":681,"line":682},[679,155692,4116],{"class":693},[679,155694,9212],{"class":685},[679,155696,155697,155699,155701,155703],{"class":681,"line":790},[679,155698,6073],{"class":685},[679,155700,4512],{"class":685},[679,155702,121635],{"class":880},[679,155704,884],{"class":693},[679,155706,155707],{"class":681,"line":892},[679,155708,889],{"emptyLinePlaceholder":797},[679,155710,155711,155713,155715],{"class":681,"line":901},[679,155712,9232],{"class":685},[679,155714,12768],{"class":685},[679,155716,121650],{"class":693},[679,155718,155719,155721,155723,155726,155728,155730,155732],{"class":681,"line":909},[679,155720,9232],{"class":685},[679,155722,12768],{"class":685},[679,155724,155725],{"class":693}," InMemoryChatMemory memory ",[679,155727,686],{"class":685},[679,155729,2054],{"class":685},[679,155731,121931],{"class":880},[679,155733,9317],{"class":693},[679,155735,155736],{"class":681,"line":918},[679,155737,889],{"emptyLinePlaceholder":797},[679,155739,155740,155742,155744,155746,155748],{"class":681,"line":935},[679,155741,6089],{"class":685},[679,155743,121635],{"class":880},[679,155745,121663],{"class":693},[679,155747,90934],{"class":2099},[679,155749,4390],{"class":693},[679,155751,155752,155754,155756,155758],{"class":681,"line":944},[679,155753,7862],{"class":931},[679,155755,121674],{"class":693},[679,155757,686],{"class":685},[679,155759,119021],{"class":693},[679,155761,155762,155764,155766,155768,155770,155772],{"class":681,"line":959},[679,155763,73482],{"class":693},[679,155765,121917],{"class":880},[679,155767,745],{"class":693},[679,155769,8930],{"class":685},[679,155771,121924],{"class":880},[679,155773,155774],{"class":693},"(memory))\n",[679,155776,155777,155779,155781],{"class":681,"line":964},[679,155778,73482],{"class":693},[679,155780,23612],{"class":880},[679,155782,9317],{"class":693},[679,155784,155785],{"class":681,"line":977},[679,155786,985],{"class":693},[679,155788,155789],{"class":681,"line":982},[679,155790,889],{"emptyLinePlaceholder":797},[679,155792,155793,155795,155797,155799,155801],{"class":681,"line":988},[679,155794,6872],{"class":693},[679,155796,91165],{"class":685},[679,155798,745],{"class":693},[679,155800,123403],{"class":689},[679,155802,1339],{"class":693},[679,155804,155805,155807,155809,155811,155813,155815,155817,155819],{"class":681,"line":993},[679,155806,6089],{"class":685},[679,155808,9289],{"class":693},[679,155810,129981],{"class":880},[679,155812,73246],{"class":693},[679,155814,96282],{"class":685},[679,155816,9289],{"class":693},[679,155818,55494],{"class":2099},[679,155820,4390],{"class":693},[679,155822,155823,155825,155827,155829],{"class":681,"line":2129},[679,155824,9444],{"class":685},[679,155826,121763],{"class":693},[679,155828,55494],{"class":880},[679,155830,17545],{"class":693},[679,155832,155833,155835,155837],{"class":681,"line":2140},[679,155834,73482],{"class":693},[679,155836,9575],{"class":880},[679,155838,155839],{"class":693},"(prompt)\n",[679,155841,155842,155844,155846],{"class":681,"line":2145},[679,155843,73482],{"class":693},[679,155845,121783],{"class":880},[679,155847,17545],{"class":693},[679,155849,155850,155852,155854],{"class":681,"line":2154},[679,155851,73482],{"class":693},[679,155853,47833],{"class":880},[679,155855,9317],{"class":693},[679,155857,155858],{"class":681,"line":2159},[679,155859,985],{"class":693},[679,155861,155862],{"class":681,"line":2164},[679,155863,996],{"class":693},[5909,155865,123145],{"id":123144},[651,155867,155868],{},"For a more interactive experience, you can stream responses as they're generated:",[669,155870,155872],{"className":4107,"code":155871,"language":4109,"meta":674,"style":674},"@GetMapping(value = \"/stream\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\npublic Flux\u003CString> stream(@RequestParam String prompt) {\n return chatClient.prompt()\n .user(prompt)\n .stream()\n .content();\n}\n",[676,155873,155874,155899,155920,155930,155938,155946,155954],{"__ignoreMap":674},[679,155875,155876,155878,155880,155882,155884,155886,155889,155891,155894,155896],{"class":681,"line":682},[679,155877,4116],{"class":693},[679,155879,20852],{"class":685},[679,155881,745],{"class":693},[679,155883,19934],{"class":931},[679,155885,6883],{"class":685},[679,155887,155888],{"class":689}," \"/stream\"",[679,155890,2797],{"class":693},[679,155892,155893],{"class":931},"produces",[679,155895,6883],{"class":685},[679,155897,155898],{"class":693}," MediaType.TEXT_EVENT_STREAM_VALUE)\n",[679,155900,155901,155903,155905,155907,155909,155911,155913,155915,155917],{"class":681,"line":790},[679,155902,6073],{"class":685},[679,155904,123176],{"class":693},[679,155906,4505],{"class":685},[679,155908,4758],{"class":693},[679,155910,5860],{"class":685},[679,155912,123185],{"class":880},[679,155914,73246],{"class":693},[679,155916,73249],{"class":685},[679,155918,155919],{"class":693}," String prompt) {\n",[679,155921,155922,155924,155926,155928],{"class":681,"line":892},[679,155923,21478],{"class":685},[679,155925,121763],{"class":693},[679,155927,55494],{"class":880},[679,155929,17545],{"class":693},[679,155931,155932,155934,155936],{"class":681,"line":901},[679,155933,40148],{"class":693},[679,155935,9575],{"class":880},[679,155937,155839],{"class":693},[679,155939,155940,155942,155944],{"class":681,"line":909},[679,155941,40148],{"class":693},[679,155943,87323],{"class":880},[679,155945,17545],{"class":693},[679,155947,155948,155950,155952],{"class":681,"line":918},[679,155949,40148],{"class":693},[679,155951,47833],{"class":880},[679,155953,9317],{"class":693},[679,155955,155956],{"class":681,"line":935},[679,155957,996],{"class":693},[4542,155959,155961],{"id":155960},"performance-considerations","Performance Considerations",[651,155963,155964],{},"Local AI performance depends heavily on your hardware. Apple Silicon Macs with 16GB+ RAM generally provide good performance with smaller models like Gemma 3. If you encounter performance issues:",[27665,155966,155967,155970,155973,155976],{},[5332,155968,155969],{},"Try smaller models (Gemma 3 is a good starting point)",[5332,155971,155972],{},"Close other memory-intensive applications",[5332,155974,155975],{},"Adjust Docker Desktop's resource allocation",[5332,155977,155978],{},"Consider using quantized models (smaller, faster, but slightly less accurate)",[4542,155980,155982],{"id":155981},"troubleshooting-common-issues","Troubleshooting Common Issues",[5909,155984,155986],{"id":155985},"missing-docker-model-command","Missing Docker Model Command",[651,155988,155989,155990,155993],{},"If your system doesn't recognize the ",[676,155991,155992],{},"docker model"," command, create a symlink:",[669,155995,155997],{"className":5851,"code":155996,"language":5853,"meta":674,"style":674},"ln -s /Applications/Docker.app/Contents/Resources/cli-plugins/docker-model ~/.docker/cli-plugins/docker-model\n",[676,155998,155999],{"__ignoreMap":674},[679,156000,156001,156004,156006,156009],{"class":681,"line":682},[679,156002,156003],{"class":880},"ln",[679,156005,42110],{"class":931},[679,156007,156008],{"class":689}," /Applications/Docker.app/Contents/Resources/cli-plugins/docker-model",[679,156010,156011],{"class":689}," ~/.docker/cli-plugins/docker-model\n",[5909,156013,156015],{"id":156014},"connection-refused-errors","Connection Refused Errors",[651,156017,156018],{},"If your Spring application can't connect to the Docker Model Runner API, check:",[27665,156020,156021,156024,156027,156033],{},[5332,156022,156023],{},"Docker Desktop is running",[5332,156025,156026],{},"The \"Enable host-side TCP support\" option is enabled",[5332,156028,156029,156030,50653],{},"The model is running (check with ",[676,156031,156032],{},"docker model list",[5332,156034,156035],{},"Your base URL configuration is correct",[4542,156037,9042],{"id":9041},[651,156039,156040],{},"Running AI models locally with Docker Model Runner and Spring AI creates a powerful combination for development. It lets you:",[5316,156042,156043,156046,156049,156052],{},[5332,156044,156045],{},"Keep sensitive data on your machine",[5332,156047,156048],{},"Develop without API keys or rate limits",[5332,156050,156051],{},"Maintain full control over your AI infrastructure",[5332,156053,156054],{},"Use Spring's programming model for AI applications",[651,156056,156057],{},"While these locally-run models may not match the capabilities of the latest cloud-based offerings, they're more than sufficient for many applications and provide an excellent development environment.",[651,156059,156060],{},"As Docker expands Model Runner support to more platforms and as open-source models continue to improve, this local approach to AI will become increasingly viable even for production use cases.",[651,156062,156063,156064,76149,156069,664],{},"Ready to try it yourself? Check out the ",[812,156065,156068],{"href":156066,"rel":156067},"https://github.com/danvega/docker-model-runner",[816],"complete example on GitHub",[812,156070,156073],{"href":156071,"rel":156072},"https://docs.docker.com/desktop/features/model-runner/",[816],"Docker Model Runner documentation",[651,156075,78024],{},[786,156077,156078],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}",{"title":674,"searchDepth":790,"depth":790,"links":156080},[156081,156082,156083,156084,156088,156089,156093,156094,156098],{"id":154995,"depth":790,"text":154996},{"id":155037,"depth":790,"text":155038},{"id":155064,"depth":790,"text":155065},{"id":155153,"depth":790,"text":155154,"children":156085},[156086,156087],{"id":125381,"depth":892,"text":6478},{"id":155449,"depth":892,"text":155450},{"id":7309,"depth":790,"text":98048},{"id":155672,"depth":790,"text":155673,"children":156090},[156091,156092],{"id":155679,"depth":892,"text":155680},{"id":123144,"depth":892,"text":123145},{"id":155960,"depth":790,"text":155961},{"id":155981,"depth":790,"text":155982,"children":156095},[156096,156097],{"id":155985,"depth":892,"text":155986},{"id":156014,"depth":892,"text":156015},{"id":9041,"depth":790,"text":9042},"Learn how to use Docker's Model Runner feature to run AI models locally with your Spring Boot applications, with zero API keys, zero data sharing, and zero monthly fees.",{"slug":156101,"date":156102,"published":797,"author":798,"tags":156103,"video":156104,"github":156066,"keywords":156105},"docker-model-runner","2025-04-07T17:00:00.000Z",[123549],"https://www.youtube.com/embed/6E6JFLMHcoQ","docker model runner, spring ai, local ai models, spring boot, ai development, gemma, no api key, privacy, docker desktop",{"title":9,"description":156099},"blog/2025/04/07/docker-model-runner","iCXMn_bROlL1SRhuecvZQIKspMfo4XD6YiKzdoQOXww",{"id":156110,"title":6,"body":156111,"description":157486,"extension":793,"meta":157487,"navigation":797,"path":7,"seo":157493,"stem":157494,"__hash__":157495},"content/blog/2025/09/jdk-24-virtual-threads-without-pinning.md",{"type":648,"value":156112,"toc":157472},[156113,156116,156120,156123,156131,156134,156139,156390,156395,156624,156627,156630,156634,156637,156640,156644,156647,156653,156659,156665,156671,156675,156684,156687,156698,156701,156705,156711,156714,156718,156721,156732,156890,156893,156901,156904,156908,156911,157199,157202,157210,157213,157217,157222,157232,157235,157246,157396,157410,157414,157417,157442,157444,157447,157450,157461,157464,157467,157469],[651,156114,156115],{},"One of the most significant improvements in JDK 24 is the enhancement to virtual threads that allows them to use synchronized methods and blocks without pinning. This change dramatically improves the scalability of Java applications using virtual threads, especially those with legacy code that relies heavily on synchronized constructs. Let's explore this feature and see how it impacts real-world Spring Boot applications.",[4542,156117,156119],{"id":156118},"understanding-synchronized-in-java","Understanding Synchronized in Java",[651,156121,156122],{},"Since its inception, Java has provided built-in support for multi-threaded programming, with the synchronized keyword being one of the core mechanisms for thread synchronization. The synchronized keyword serves two primary purposes:",[27665,156124,156125,156128],{},[5332,156126,156127],{},"It provides mutual exclusion, ensuring that only one thread can execute a section of code at a time",[5332,156129,156130],{},"It establishes a happens-before relationship, guaranteeing that changes made by one thread are visible to other threads",[651,156132,156133],{},"You can use synchronized in two ways:",[27665,156135,156136],{},[5332,156137,156138],{},"As a method modifier (the entire method becomes synchronized on the instance for instance methods, or on the class object for static methods)",[669,156140,156142],{"className":4107,"code":156141,"language":4109,"meta":674,"style":674},"package dev.danvega.threads.synchronization;\n\n/**\n * This class demonstrates the use of synchronized methods in Java.\n * The synchronized keyword on a method ensures that only one thread\n * can execute the method at a time, providing thread safety.\n */\npublic class MethodSynchronizedCounter {\n\n private int count = 0;\n\n /**\n * Increments the counter by 1.\n * The synchronized keyword ensures that only one thread can execute\n * this method at a time, preventing race conditions.\n */\n public synchronized void increment() {\n count++;\n }\n\n /**\n * Decrements the counter by 1.\n * The synchronized keyword ensures that only one thread can execute\n * this method at a time, preventing race conditions.\n */\n public synchronized void decrement() {\n count--;\n }\n\n /**\n * Returns the current value of the counter.\n * This method is also synchronized to ensure that the most up-to-date\n * value is returned and to establish a happens-before relationship with\n * other synchronized methods.\n * \n * @return the current count\n */\n public synchronized int getCount() {\n return count;\n }\n\n}\n",[676,156143,156144,156151,156155,156159,156164,156169,156174,156178,156189,156193,156208,156212,156216,156221,156226,156231,156235,156248,156257,156261,156265,156269,156274,156278,156282,156286,156299,156308,156312,156316,156320,156325,156330,156335,156340,156345,156354,156358,156371,156378,156382,156386],{"__ignoreMap":674},[679,156145,156146,156148],{"class":681,"line":682},[679,156147,2543],{"class":685},[679,156149,156150],{"class":693}," dev.danvega.threads.synchronization;\n",[679,156152,156153],{"class":681,"line":790},[679,156154,889],{"emptyLinePlaceholder":797},[679,156156,156157],{"class":681,"line":892},[679,156158,130887],{"class":1400},[679,156160,156161],{"class":681,"line":901},[679,156162,156163],{"class":1400}," * This class demonstrates the use of synchronized methods in Java.\n",[679,156165,156166],{"class":681,"line":909},[679,156167,156168],{"class":1400}," * The synchronized keyword on a method ensures that only one thread\n",[679,156170,156171],{"class":681,"line":918},[679,156172,156173],{"class":1400}," * can execute the method at a time, providing thread safety.\n",[679,156175,156176],{"class":681,"line":935},[679,156177,77179],{"class":1400},[679,156179,156180,156182,156184,156187],{"class":681,"line":944},[679,156181,6073],{"class":685},[679,156183,4512],{"class":685},[679,156185,156186],{"class":880}," MethodSynchronizedCounter",[679,156188,884],{"class":693},[679,156190,156191],{"class":681,"line":959},[679,156192,889],{"emptyLinePlaceholder":797},[679,156194,156195,156197,156199,156202,156204,156206],{"class":681,"line":964},[679,156196,9232],{"class":685},[679,156198,14925],{"class":685},[679,156200,156201],{"class":693}," count ",[679,156203,686],{"class":685},[679,156205,14987],{"class":931},[679,156207,1186],{"class":693},[679,156209,156210],{"class":681,"line":977},[679,156211,889],{"emptyLinePlaceholder":797},[679,156213,156214],{"class":681,"line":982},[679,156215,16789],{"class":1400},[679,156217,156218],{"class":681,"line":988},[679,156219,156220],{"class":1400}," * Increments the counter by 1.\n",[679,156222,156223],{"class":681,"line":993},[679,156224,156225],{"class":1400}," * The synchronized keyword ensures that only one thread can execute\n",[679,156227,156228],{"class":681,"line":2129},[679,156229,156230],{"class":1400}," * this method at a time, preventing race conditions.\n",[679,156232,156233],{"class":681,"line":2140},[679,156234,16826],{"class":1400},[679,156236,156237,156239,156242,156244,156246],{"class":681,"line":2145},[679,156238,6089],{"class":685},[679,156240,156241],{"class":685}," synchronized",[679,156243,6095],{"class":685},[679,156245,70467],{"class":880},[679,156247,2667],{"class":693},[679,156249,156250,156253,156255],{"class":681,"line":2154},[679,156251,156252],{"class":693}," count",[679,156254,1569],{"class":685},[679,156256,1186],{"class":693},[679,156258,156259],{"class":681,"line":2159},[679,156260,985],{"class":693},[679,156262,156263],{"class":681,"line":2164},[679,156264,889],{"emptyLinePlaceholder":797},[679,156266,156267],{"class":681,"line":3134},[679,156268,16789],{"class":1400},[679,156270,156271],{"class":681,"line":3139},[679,156272,156273],{"class":1400}," * Decrements the counter by 1.\n",[679,156275,156276],{"class":681,"line":3144},[679,156277,156225],{"class":1400},[679,156279,156280],{"class":681,"line":3149},[679,156281,156230],{"class":1400},[679,156283,156284],{"class":681,"line":3169},[679,156285,16826],{"class":1400},[679,156287,156288,156290,156292,156294,156297],{"class":681,"line":3185},[679,156289,6089],{"class":685},[679,156291,156241],{"class":685},[679,156293,6095],{"class":685},[679,156295,156296],{"class":880}," decrement",[679,156298,2667],{"class":693},[679,156300,156301,156303,156306],{"class":681,"line":3194},[679,156302,156252],{"class":693},[679,156304,156305],{"class":685},"--",[679,156307,1186],{"class":693},[679,156309,156310],{"class":681,"line":3199},[679,156311,985],{"class":693},[679,156313,156314],{"class":681,"line":3212},[679,156315,889],{"emptyLinePlaceholder":797},[679,156317,156318],{"class":681,"line":3217},[679,156319,16789],{"class":1400},[679,156321,156322],{"class":681,"line":3222},[679,156323,156324],{"class":1400}," * Returns the current value of the counter.\n",[679,156326,156327],{"class":681,"line":3227},[679,156328,156329],{"class":1400}," * This method is also synchronized to ensure that the most up-to-date\n",[679,156331,156332],{"class":681,"line":3232},[679,156333,156334],{"class":1400}," * value is returned and to establish a happens-before relationship with\n",[679,156336,156337],{"class":681,"line":3499},[679,156338,156339],{"class":1400}," * other synchronized methods.\n",[679,156341,156342],{"class":681,"line":3509},[679,156343,156344],{"class":1400}," * \n",[679,156346,156347,156349,156351],{"class":681,"line":3516},[679,156348,16799],{"class":1400},[679,156350,85334],{"class":685},[679,156352,156353],{"class":1400}," the current count\n",[679,156355,156356],{"class":681,"line":3531},[679,156357,16826],{"class":1400},[679,156359,156360,156362,156364,156366,156369],{"class":681,"line":3536},[679,156361,6089],{"class":685},[679,156363,156241],{"class":685},[679,156365,14925],{"class":685},[679,156367,156368],{"class":880}," getCount",[679,156370,2667],{"class":693},[679,156372,156373,156375],{"class":681,"line":3541},[679,156374,9444],{"class":685},[679,156376,156377],{"class":693}," count;\n",[679,156379,156380],{"class":681,"line":3546},[679,156381,985],{"class":693},[679,156383,156384],{"class":681,"line":3551},[679,156385,889],{"emptyLinePlaceholder":797},[679,156387,156388],{"class":681,"line":3557},[679,156389,996],{"class":693},[27665,156391,156392],{"start":790},[5332,156393,156394],{},"As a block, where you specify which object to synchronize on",[669,156396,156398],{"className":4107,"code":156397,"language":4109,"meta":674,"style":674},"package dev.danvega.threads.synchronization;\n\n/**\n * This class demonstrates the use of synchronized blocks in Java.\n * The synchronized block ensures that only one thread can execute\n * the block at a time, providing thread safety.\n */\npublic class BlockSynchronizedCounter {\n\n private int count = 0;\n private final Object lock = new Object(); // Object used for synchronization\n\n /**\n * Increments the counter by 1.\n * The synchronized block ensures that only one thread can execute\n * the critical section at a time, preventing race conditions.\n */\n public void increment() {\n synchronized (lock) {\n count++;\n }\n }\n\n /**\n * Returns the current value of the counter.\n * This method also uses a synchronized block to ensure that the most up-to-date\n * value is returned and to establish a happens-before relationship with\n * other synchronized blocks.\n * \n * @return the current count\n */\n public int getCount() {\n synchronized (lock) {\n return count;\n }\n }\n\n}\n",[676,156399,156400,156406,156410,156414,156419,156424,156429,156433,156444,156448,156462,156483,156487,156491,156495,156500,156505,156509,156519,156527,156536,156540,156544,156548,156552,156556,156561,156565,156570,156574,156582,156586,156596,156602,156608,156612,156616,156620],{"__ignoreMap":674},[679,156401,156402,156404],{"class":681,"line":682},[679,156403,2543],{"class":685},[679,156405,156150],{"class":693},[679,156407,156408],{"class":681,"line":790},[679,156409,889],{"emptyLinePlaceholder":797},[679,156411,156412],{"class":681,"line":892},[679,156413,130887],{"class":1400},[679,156415,156416],{"class":681,"line":901},[679,156417,156418],{"class":1400}," * This class demonstrates the use of synchronized blocks in Java.\n",[679,156420,156421],{"class":681,"line":909},[679,156422,156423],{"class":1400}," * The synchronized block ensures that only one thread can execute\n",[679,156425,156426],{"class":681,"line":918},[679,156427,156428],{"class":1400}," * the block at a time, providing thread safety.\n",[679,156430,156431],{"class":681,"line":935},[679,156432,77179],{"class":1400},[679,156434,156435,156437,156439,156442],{"class":681,"line":944},[679,156436,6073],{"class":685},[679,156438,4512],{"class":685},[679,156440,156441],{"class":880}," BlockSynchronizedCounter",[679,156443,884],{"class":693},[679,156445,156446],{"class":681,"line":959},[679,156447,889],{"emptyLinePlaceholder":797},[679,156449,156450,156452,156454,156456,156458,156460],{"class":681,"line":964},[679,156451,9232],{"class":685},[679,156453,14925],{"class":685},[679,156455,156201],{"class":693},[679,156457,686],{"class":685},[679,156459,14987],{"class":931},[679,156461,1186],{"class":693},[679,156463,156464,156466,156468,156471,156473,156475,156478,156480],{"class":681,"line":977},[679,156465,9232],{"class":685},[679,156467,12768],{"class":685},[679,156469,156470],{"class":693}," Object lock ",[679,156472,686],{"class":685},[679,156474,2054],{"class":685},[679,156476,156477],{"class":880}," Object",[679,156479,96071],{"class":693},[679,156481,156482],{"class":1400},"// Object used for synchronization\n",[679,156484,156485],{"class":681,"line":982},[679,156486,889],{"emptyLinePlaceholder":797},[679,156488,156489],{"class":681,"line":988},[679,156490,16789],{"class":1400},[679,156492,156493],{"class":681,"line":993},[679,156494,156220],{"class":1400},[679,156496,156497],{"class":681,"line":2129},[679,156498,156499],{"class":1400}," * The synchronized block ensures that only one thread can execute\n",[679,156501,156502],{"class":681,"line":2140},[679,156503,156504],{"class":1400}," * the critical section at a time, preventing race conditions.\n",[679,156506,156507],{"class":681,"line":2145},[679,156508,16826],{"class":1400},[679,156510,156511,156513,156515,156517],{"class":681,"line":2154},[679,156512,6089],{"class":685},[679,156514,6095],{"class":685},[679,156516,70467],{"class":880},[679,156518,2667],{"class":693},[679,156520,156521,156524],{"class":681,"line":2159},[679,156522,156523],{"class":685}," synchronized",[679,156525,156526],{"class":693}," (lock) {\n",[679,156528,156529,156532,156534],{"class":681,"line":2164},[679,156530,156531],{"class":693}," count",[679,156533,1569],{"class":685},[679,156535,1186],{"class":693},[679,156537,156538],{"class":681,"line":3134},[679,156539,1290],{"class":693},[679,156541,156542],{"class":681,"line":3139},[679,156543,985],{"class":693},[679,156545,156546],{"class":681,"line":3144},[679,156547,889],{"emptyLinePlaceholder":797},[679,156549,156550],{"class":681,"line":3149},[679,156551,16789],{"class":1400},[679,156553,156554],{"class":681,"line":3169},[679,156555,156324],{"class":1400},[679,156557,156558],{"class":681,"line":3185},[679,156559,156560],{"class":1400}," * This method also uses a synchronized block to ensure that the most up-to-date\n",[679,156562,156563],{"class":681,"line":3194},[679,156564,156334],{"class":1400},[679,156566,156567],{"class":681,"line":3199},[679,156568,156569],{"class":1400}," * other synchronized blocks.\n",[679,156571,156572],{"class":681,"line":3212},[679,156573,156344],{"class":1400},[679,156575,156576,156578,156580],{"class":681,"line":3217},[679,156577,16799],{"class":1400},[679,156579,85334],{"class":685},[679,156581,156353],{"class":1400},[679,156583,156584],{"class":681,"line":3222},[679,156585,16826],{"class":1400},[679,156587,156588,156590,156592,156594],{"class":681,"line":3227},[679,156589,6089],{"class":685},[679,156591,14925],{"class":685},[679,156593,156368],{"class":880},[679,156595,2667],{"class":693},[679,156597,156598,156600],{"class":681,"line":3232},[679,156599,156523],{"class":685},[679,156601,156526],{"class":693},[679,156603,156604,156606],{"class":681,"line":3499},[679,156605,20443],{"class":685},[679,156607,156377],{"class":693},[679,156609,156610],{"class":681,"line":3509},[679,156611,1290],{"class":693},[679,156613,156614],{"class":681,"line":3516},[679,156615,985],{"class":693},[679,156617,156618],{"class":681,"line":3531},[679,156619,889],{"emptyLinePlaceholder":797},[679,156621,156622],{"class":681,"line":3536},[679,156623,996],{"class":693},[651,156625,156626],{},"Under the hood, synchronized uses intrinsic locks (also called monitors) associated with Java objects. When a thread enters a synchronized block, it acquires the intrinsic lock for that object and releases it when exiting the block. If another thread attempts to enter a block synchronized on the same object, it must wait until the first thread releases that lock.",[651,156628,156629],{},"This simple mechanism has been sufficient for many concurrent Java applications, but it introduced challenges when virtual threads entered the picture.",[4542,156631,156633],{"id":156632},"the-virtual-thread-pinning-problem","The Virtual Thread Pinning Problem",[651,156635,156636],{},"Virtual threads were introduced in JDK 21 as a lightweight alternative to platform threads for building highly concurrent applications. Unlike platform threads, which are backed by operating system threads, virtual threads are managed by the JDK and designed to be lightweight and abundant.",[651,156638,156639],{},"However, virtual threads had a significant limitation in JDK 21: when a virtual thread entered a synchronized block or method and then performed a blocking operation (like I/O or waiting for a database query), it would get \"pinned\" to its carrier thread. This pinning prevented the platform thread from being released back to the thread pool, effectively negating one of the main benefits of virtual threads.",[5909,156641,156643],{"id":156642},"the-restaurant-analogy","The Restaurant Analogy",[651,156645,156646],{},"Think of it like a busy restaurant with a limited number of waiters (platform threads) serving many customers (tasks):",[651,156648,156649,156652],{},[2939,156650,156651],{},"In JDK 21:","\nWhen a customer (virtual thread) enters a private dining room (synchronized block) and needs to wait for their food to cook (blocking I/O), the waiter (platform thread) must stay with that customer the entire time. The waiter can't serve other customers while waiting, even though they're just standing there doing nothing. If too many customers are in private dining rooms waiting for food, all waiters become occupied and no new customers can be served.",[651,156654,156655],{},[660,156656],{"alt":156657,"src":156658},"JDK 21 Behavior","/images/blog/2025/04/09/waiter_pre_jdk24.png",[651,156660,156661,156664],{},[2939,156662,156663],{},"In JDK 24:","\nNow, when a customer enters the private dining room and needs to wait for food, the waiter can leave a pager with the customer and attend to other customers. When the food is ready, the pager buzzes, and any available waiter (potentially a different one) can bring the food to the customer. This dramatically improves the efficiency of the restaurant.",[651,156666,156667],{},[660,156668],{"alt":156669,"src":156670},"JDK 24 Behavior","/images/blog/2025/04/09/waiter_post_jdk24.png",[4542,156672,156674],{"id":156673},"how-jdk-24-solves-the-pinning-problem","How JDK 24 Solves the Pinning Problem",[651,156676,156677,156678,156683],{},"JDK 24 introduces ",[812,156679,156682],{"href":156680,"rel":156681},"https://openjdk.org/jeps/491",[816],"JEP 491: Synchronize Virtual Threads without Pinning",", which addresses this limitation. The enhancement allows virtual threads to be unmounted from their carrier thread when they block inside a synchronized section, making the carrier thread available to run other virtual threads.",[651,156685,156686],{},"This is a significant improvement because:",[27665,156688,156689,156692,156695],{},[5332,156690,156691],{},"It provides better throughput for applications with many virtual threads",[5332,156693,156694],{},"It allows legacy code using synchronized to benefit from virtual threads",[5332,156696,156697],{},"It removes a major barrier to adoption for organizations considering virtual threads`",[651,156699,156700],{},"The key insight is that this optimization works when different virtual threads use different lock objects. If multiple virtual threads contend for the same lock object, they will still block each other (as expected from synchronized semantics).",[4542,156702,156704],{"id":156703},"performance-benchmarks","Performance Benchmarks",[651,156706,156707],{},[660,156708],{"alt":156709,"src":156710},"Benchmarks","/images/blog/2025/04/09/AdobeStock_1228917302.jpeg",[651,156712,156713],{},"To demonstrate the impact of this improvement, let's look at some benchmarks comparing JDK 21 and JDK 24.",[5909,156715,156717],{"id":156716},"simple-java-example","Simple Java Example",[651,156719,156720],{},"The first benchmark creates 5,000 virtual threads, where each thread:",[27665,156722,156723,156726,156729],{},[5332,156724,156725],{},"Performs CPU-intensive work (10,000 math iterations)",[5332,156727,156728],{},"Acquires a unique lock",[5332,156730,156731],{},"Sleeps for 5ms inside the synchronized block",[669,156733,156735],{"className":4107,"code":156734,"language":4109,"meta":674,"style":674},"for (int i = 0; i \u003C NUM_THREADS; i++) {\n final Object lock = new Object(); // Each thread gets its own lock\n executor.submit(() -> {\n try {\n // CPU-bound work happens here\n doCpuWork();\n \n synchronized (lock) {\n // Blocking operation inside synchronized block\n Thread.sleep(Duration.ofMillis(BLOCKING_TIME_MS));\n }\n \n completedTasks.incrementAndGet();\n } catch (Exception e) {\n // Error handling\n }\n });\n}\n",[676,156736,156737,156762,156779,156793,156799,156804,156811,156815,156822,156827,156843,156847,156851,156860,156873,156878,156882,156886],{"__ignoreMap":674},[679,156738,156739,156741,156743,156745,156747,156749,156751,156753,156755,156758,156760],{"class":681,"line":682},[679,156740,1466],{"class":685},[679,156742,4193],{"class":693},[679,156744,1078],{"class":685},[679,156746,36971],{"class":693},[679,156748,686],{"class":685},[679,156750,14987],{"class":931},[679,156752,59465],{"class":693},[679,156754,4505],{"class":685},[679,156756,156757],{"class":693}," NUM_THREADS; i",[679,156759,1569],{"class":685},[679,156761,4390],{"class":693},[679,156763,156764,156766,156768,156770,156772,156774,156776],{"class":681,"line":790},[679,156765,153152],{"class":685},[679,156767,156470],{"class":693},[679,156769,686],{"class":685},[679,156771,2054],{"class":685},[679,156773,156477],{"class":880},[679,156775,96071],{"class":693},[679,156777,156778],{"class":1400},"// Each thread gets its own lock\n",[679,156780,156781,156784,156787,156789,156791],{"class":681,"line":892},[679,156782,156783],{"class":693}," executor.",[679,156785,156786],{"class":880},"submit",[679,156788,55186],{"class":693},[679,156790,16955],{"class":685},[679,156792,884],{"class":693},[679,156794,156795,156797],{"class":681,"line":901},[679,156796,9373],{"class":685},[679,156798,884],{"class":693},[679,156800,156801],{"class":681,"line":909},[679,156802,156803],{"class":1400}," // CPU-bound work happens here\n",[679,156805,156806,156809],{"class":681,"line":918},[679,156807,156808],{"class":880}," doCpuWork",[679,156810,9317],{"class":693},[679,156812,156813],{"class":681,"line":935},[679,156814,145630],{"class":693},[679,156816,156817,156820],{"class":681,"line":944},[679,156818,156819],{"class":685}," synchronized",[679,156821,156526],{"class":693},[679,156823,156824],{"class":681,"line":959},[679,156825,156826],{"class":1400}," // Blocking operation inside synchronized block\n",[679,156828,156829,156832,156834,156837,156840],{"class":681,"line":964},[679,156830,156831],{"class":693}," Thread.",[679,156833,9609],{"class":880},[679,156835,156836],{"class":693},"(Duration.",[679,156838,156839],{"class":880},"ofMillis",[679,156841,156842],{"class":693},"(BLOCKING_TIME_MS));\n",[679,156844,156845],{"class":681,"line":977},[679,156846,25517],{"class":693},[679,156848,156849],{"class":681,"line":982},[679,156850,145630],{"class":693},[679,156852,156853,156856,156858],{"class":681,"line":988},[679,156854,156855],{"class":693}," completedTasks.",[679,156857,77906],{"class":880},[679,156859,9317],{"class":693},[679,156861,156862,156864,156866,156869,156871],{"class":681,"line":993},[679,156863,15675],{"class":693},[679,156865,9394],{"class":685},[679,156867,156868],{"class":693}," (Exception ",[679,156870,9400],{"class":2099},[679,156872,4390],{"class":693},[679,156874,156875],{"class":681,"line":2129},[679,156876,156877],{"class":1400}," // Error handling\n",[679,156879,156880],{"class":681,"line":2140},[679,156881,1290],{"class":693},[679,156883,156884],{"class":681,"line":2145},[679,156885,59286],{"class":693},[679,156887,156888],{"class":681,"line":2154},[679,156889,996],{"class":693},[651,156891,156892],{},"Results:",[5316,156894,156895,156898],{},[5332,156896,156897],{},"JDK 21: 31.791 seconds",[5332,156899,156900],{},"JDK 24: 0.454 seconds",[651,156902,156903],{},"That's a 70x improvement!",[5909,156905,156907],{"id":156906},"spring-boot-application-example","Spring Boot Application Example",[651,156909,156910],{},"For a more realistic scenario, let's look at a Spring Boot application that simulates an inventory management system where each update is synchronized to prevent race conditions:",[669,156912,156914],{"className":4107,"code":156913,"language":4109,"meta":674,"style":674},"@Service\npublic class InventoryService {\n \n private final Map\u003CString, Integer> inventory = new ConcurrentHashMap\u003C>();\n private final ConcurrentHashMap\u003CString, Object> productLocks = new ConcurrentHashMap\u003C>();\n private final DatabaseService dbService;\n \n // Constructor omitted for brevity\n \n public boolean updateInventory(String productId, int quantity) {\n // Get or create a lock specific to this productId\n Object productLock = productLocks.computeIfAbsent(productId, k -> new Object());\n \n // Synchronize on the product-specific lock\n synchronized (productLock) {\n int currentStock = inventory.getOrDefault(productId, 0);\n \n if (currentStock + quantity \u003C 0) {\n return false; // Can't have negative inventory\n }\n \n // Simulate database write (blocking operation)\n dbService.persistInventoryChange(productId, quantity);\n \n // Update in-memory inventory\n inventory.put(productId, currentStock + quantity);\n return true;\n }\n }\n}\n",[676,156915,156916,156922,156933,156937,156962,156987,156996,157000,157005,157009,157032,157037,157060,157064,157069,157076,157098,157102,157120,157131,157135,157139,157144,157155,157159,157164,157179,157187,157191,157195],{"__ignoreMap":674},[679,156917,156918,156920],{"class":681,"line":682},[679,156919,4116],{"class":693},[679,156921,9486],{"class":685},[679,156923,156924,156926,156928,156931],{"class":681,"line":790},[679,156925,6073],{"class":685},[679,156927,4512],{"class":685},[679,156929,156930],{"class":880}," InventoryService",[679,156932,884],{"class":693},[679,156934,156935],{"class":681,"line":892},[679,156936,119642],{"class":693},[679,156938,156939,156941,156943,156946,156948,156950,156952,156955,156957,156959],{"class":681,"line":901},[679,156940,9232],{"class":685},[679,156942,12768],{"class":685},[679,156944,156945],{"class":693}," Map\u003C",[679,156947,4758],{"class":685},[679,156949,2797],{"class":693},[679,156951,1083],{"class":685},[679,156953,156954],{"class":693},"> inventory ",[679,156956,686],{"class":685},[679,156958,2054],{"class":685},[679,156960,156961],{"class":693}," ConcurrentHashMap\u003C>();\n",[679,156963,156964,156966,156968,156971,156973,156975,156978,156981,156983,156985],{"class":681,"line":909},[679,156965,9232],{"class":685},[679,156967,12768],{"class":685},[679,156969,156970],{"class":693}," ConcurrentHashMap\u003C",[679,156972,4758],{"class":685},[679,156974,2797],{"class":693},[679,156976,156977],{"class":685},"Object",[679,156979,156980],{"class":693},"> productLocks ",[679,156982,686],{"class":685},[679,156984,2054],{"class":685},[679,156986,156961],{"class":693},[679,156988,156989,156991,156993],{"class":681,"line":918},[679,156990,9232],{"class":685},[679,156992,12768],{"class":685},[679,156994,156995],{"class":693}," DatabaseService dbService;\n",[679,156997,156998],{"class":681,"line":935},[679,156999,119642],{"class":693},[679,157001,157002],{"class":681,"line":944},[679,157003,157004],{"class":1400}," // Constructor omitted for brevity\n",[679,157006,157007],{"class":681,"line":959},[679,157008,119642],{"class":693},[679,157010,157011,157013,157015,157018,157020,157023,157025,157027,157030],{"class":681,"line":964},[679,157012,6089],{"class":685},[679,157014,14493],{"class":685},[679,157016,157017],{"class":880}," updateInventory",[679,157019,11400],{"class":693},[679,157021,157022],{"class":2099},"productId",[679,157024,2797],{"class":693},[679,157026,1078],{"class":685},[679,157028,157029],{"class":2099}," quantity",[679,157031,4390],{"class":693},[679,157033,157034],{"class":681,"line":977},[679,157035,157036],{"class":1400}," // Get or create a lock specific to this productId\n",[679,157038,157039,157042,157044,157047,157049,157052,157054,157056,157058],{"class":681,"line":982},[679,157040,157041],{"class":693}," Object productLock ",[679,157043,686],{"class":685},[679,157045,157046],{"class":693}," productLocks.",[679,157048,152156],{"class":880},[679,157050,157051],{"class":693},"(productId, k ",[679,157053,16955],{"class":685},[679,157055,2054],{"class":685},[679,157057,156477],{"class":880},[679,157059,9431],{"class":693},[679,157061,157062],{"class":681,"line":988},[679,157063,142274],{"class":693},[679,157065,157066],{"class":681,"line":993},[679,157067,157068],{"class":1400}," // Synchronize on the product-specific lock\n",[679,157070,157071,157073],{"class":681,"line":2129},[679,157072,156523],{"class":685},[679,157074,157075],{"class":693}," (productLock) {\n",[679,157077,157078,157081,157084,157086,157089,157091,157094,157096],{"class":681,"line":2140},[679,157079,157080],{"class":685}," int",[679,157082,157083],{"class":693}," currentStock ",[679,157085,686],{"class":685},[679,157087,157088],{"class":693}," inventory.",[679,157090,152720],{"class":880},[679,157092,157093],{"class":693},"(productId, ",[679,157095,1060],{"class":931},[679,157097,1208],{"class":693},[679,157099,157100],{"class":681,"line":2145},[679,157101,145630],{"class":693},[679,157103,157104,157106,157109,157111,157114,157116,157118],{"class":681,"line":2154},[679,157105,112604],{"class":685},[679,157107,157108],{"class":693}," (currentStock ",[679,157110,3065],{"class":685},[679,157112,157113],{"class":693}," quantity ",[679,157115,4505],{"class":685},[679,157117,14987],{"class":931},[679,157119,4390],{"class":693},[679,157121,157122,157124,157126,157128],{"class":681,"line":2159},[679,157123,143712],{"class":685},[679,157125,14559],{"class":931},[679,157127,75640],{"class":693},[679,157129,157130],{"class":1400},"// Can't have negative inventory\n",[679,157132,157133],{"class":681,"line":2164},[679,157134,25517],{"class":693},[679,157136,157137],{"class":681,"line":3134},[679,157138,145630],{"class":693},[679,157140,157141],{"class":681,"line":3139},[679,157142,157143],{"class":1400}," // Simulate database write (blocking operation)\n",[679,157145,157146,157149,157152],{"class":681,"line":3144},[679,157147,157148],{"class":693}," dbService.",[679,157150,157151],{"class":880},"persistInventoryChange",[679,157153,157154],{"class":693},"(productId, quantity);\n",[679,157156,157157],{"class":681,"line":3149},[679,157158,145630],{"class":693},[679,157160,157161],{"class":681,"line":3169},[679,157162,157163],{"class":1400}," // Update in-memory inventory\n",[679,157165,157166,157169,157171,157174,157176],{"class":681,"line":3185},[679,157167,157168],{"class":693}," inventory.",[679,157170,114147],{"class":880},[679,157172,157173],{"class":693},"(productId, currentStock ",[679,157175,3065],{"class":685},[679,157177,157178],{"class":693}," quantity);\n",[679,157180,157181,157183,157185],{"class":681,"line":3194},[679,157182,20443],{"class":685},[679,157184,14523],{"class":931},[679,157186,1186],{"class":693},[679,157188,157189],{"class":681,"line":3199},[679,157190,1290],{"class":693},[679,157192,157193],{"class":681,"line":3212},[679,157194,985],{"class":693},[679,157196,157197],{"class":681,"line":3217},[679,157198,996],{"class":693},[651,157200,157201],{},"When benchmarking 10,000 concurrent requests across 1,000 different product IDs:",[5316,157203,157204,157207],{},[5332,157205,157206],{},"JDK 21: 12.486 seconds (800.9 requests/second)",[5332,157208,157209],{},"JDK 24: 2.345 seconds (4,264.4 requests/second)",[651,157211,157212],{},"That's a 5.3x improvement in throughput!",[4542,157214,157216],{"id":157215},"implementing-in-your-spring-boot-application","Implementing in Your Spring Boot Application",[651,157218,157219,157220,2391],{},"Enabling virtual threads in Spring Boot is straightforward. For Spring Boot 3.2 and later, add this property to your ",[676,157221,16242],{},[669,157223,157224],{"className":76589,"code":118078,"language":35538,"meta":674,"style":674},[676,157225,157226],{"__ignoreMap":674},[679,157227,157228,157230],{"class":681,"line":682},[679,157229,118085],{"class":685},[679,157231,93485],{"class":693},[651,157233,157234],{},"For best results with JDK 24's synchronized improvements:",[27665,157236,157237],{},[5332,157238,157239,157242,157243,157245],{},[2939,157240,157241],{},"Use per-object synchronization",": Instead of synchronizing on shared objects (like ",[676,157244,4732],{}," or a class instance), use dedicated lock objects for different resources.",[669,157247,157249],{"className":4107,"code":157248,"language":4109,"meta":674,"style":674},"// AVOID this pattern with virtual threads\npublic synchronized void updateResource(String resourceId, int value) {\n // Blocking operation\n databaseService.update(resourceId, value);\n}\n\n// PREFER this pattern with virtual threads\nprivate final ConcurrentHashMap\u003CString, Object> resourceLocks = new ConcurrentHashMap\u003C>();\n\npublic void updateResource(String resourceId, int value) {\n Object lock = resourceLocks.computeIfAbsent(resourceId, k -> new Object());\n synchronized (lock) {\n // Blocking operation\n databaseService.update(resourceId, value);\n }\n}\n",[676,157250,157251,157256,157275,157280,157290,157294,157298,157303,157326,157330,157344,157367,157374,157379,157388,157392],{"__ignoreMap":674},[679,157252,157253],{"class":681,"line":682},[679,157254,157255],{"class":1400},"// AVOID this pattern with virtual threads\n",[679,157257,157258,157260,157262,157264,157267,157270,157272],{"class":681,"line":790},[679,157259,6073],{"class":685},[679,157261,156241],{"class":685},[679,157263,6095],{"class":685},[679,157265,157266],{"class":880}," updateResource",[679,157268,157269],{"class":693},"(String resourceId, ",[679,157271,1078],{"class":685},[679,157273,157274],{"class":693}," value) {\n",[679,157276,157277],{"class":681,"line":892},[679,157278,157279],{"class":1400}," // Blocking operation\n",[679,157281,157282,157285,157287],{"class":681,"line":901},[679,157283,157284],{"class":693}," databaseService.",[679,157286,82237],{"class":880},[679,157288,157289],{"class":693},"(resourceId, value);\n",[679,157291,157292],{"class":681,"line":909},[679,157293,996],{"class":693},[679,157295,157296],{"class":681,"line":918},[679,157297,889],{"emptyLinePlaceholder":797},[679,157299,157300],{"class":681,"line":935},[679,157301,157302],{"class":1400},"// PREFER this pattern with virtual threads\n",[679,157304,157305,157307,157309,157311,157313,157315,157317,157320,157322,157324],{"class":681,"line":944},[679,157306,21301],{"class":685},[679,157308,12768],{"class":685},[679,157310,156970],{"class":693},[679,157312,4758],{"class":685},[679,157314,2797],{"class":693},[679,157316,156977],{"class":685},[679,157318,157319],{"class":693},"> resourceLocks ",[679,157321,686],{"class":685},[679,157323,2054],{"class":685},[679,157325,156961],{"class":693},[679,157327,157328],{"class":681,"line":959},[679,157329,889],{"emptyLinePlaceholder":797},[679,157331,157332,157334,157336,157338,157340,157342],{"class":681,"line":964},[679,157333,6073],{"class":685},[679,157335,6095],{"class":685},[679,157337,157266],{"class":880},[679,157339,157269],{"class":693},[679,157341,1078],{"class":685},[679,157343,157274],{"class":693},[679,157345,157346,157349,157351,157354,157356,157359,157361,157363,157365],{"class":681,"line":977},[679,157347,157348],{"class":693}," Object lock ",[679,157350,686],{"class":685},[679,157352,157353],{"class":693}," resourceLocks.",[679,157355,152156],{"class":880},[679,157357,157358],{"class":693},"(resourceId, k ",[679,157360,16955],{"class":685},[679,157362,2054],{"class":685},[679,157364,156477],{"class":880},[679,157366,9431],{"class":693},[679,157368,157369,157372],{"class":681,"line":982},[679,157370,157371],{"class":685}," synchronized",[679,157373,156526],{"class":693},[679,157375,157376],{"class":681,"line":988},[679,157377,157378],{"class":1400}," // Blocking operation\n",[679,157380,157381,157384,157386],{"class":681,"line":993},[679,157382,157383],{"class":693}," databaseService.",[679,157385,82237],{"class":880},[679,157387,157289],{"class":693},[679,157389,157390],{"class":681,"line":2129},[679,157391,985],{"class":693},[679,157393,157394],{"class":681,"line":2140},[679,157395,996],{"class":693},[27665,157397,157398,157404],{"start":790},[5332,157399,157400,157403],{},[2939,157401,157402],{},"Consider thread-safe alternatives",": For simple use cases, consider using concurrent collections or atomic operations instead of synchronized blocks.",[5332,157405,157406,157409],{},[2939,157407,157408],{},"Don't mix implementation approaches",": Be consistent in how you handle synchronization throughout your codebase.",[4542,157411,157413],{"id":157412},"migration-considerations","Migration Considerations",[651,157415,157416],{},"If you're migrating existing applications to leverage virtual threads:",[27665,157418,157419,157425,157431,157436],{},[5332,157420,157421,157424],{},[2939,157422,157423],{},"Identify synchronization hotspots",": Profile your application to identify methods that use synchronization and perform blocking operations.",[5332,157426,157427,157430],{},[2939,157428,157429],{},"Refactor shared locks",": Where possible, refactor code to use dedicated locks for different resources.",[5332,157432,157433,157435],{},[2939,157434,147767],{},": The behavior changes between JDK 21 and JDK 24 are subtle but significant. Comprehensive testing is essential.",[5332,157437,157438,157441],{},[2939,157439,157440],{},"Verify compatibility",": Ensure all your libraries and dependencies work correctly with JDK 24.",[4542,157443,9042],{"id":9041},[651,157445,157446],{},"The improvements to virtual threads in JDK 24 represent a significant advancement for Java's concurrency model. By allowing virtual threads to use synchronized blocks without pinning, JDK 24 removes a major barrier to adopting virtual threads in production applications.",[651,157448,157449],{},"For Spring Boot developers, this means:",[27665,157451,157452,157455,157458],{},[5332,157453,157454],{},"Legacy codebases with synchronized blocks can now benefit from virtual threads",[5332,157456,157457],{},"Significantly improved throughput for high-concurrency applications",[5332,157459,157460],{},"Simplified migration path from traditional threading models",[651,157462,157463],{},"If you're building applications that handle many concurrent operations, especially with blocking I/O, upgrading to JDK 24 should be a priority. The performance improvements are substantial, and the implementation changes required are minimal.",[651,157465,157466],{},"Have you tested your Spring applications with JDK 24's improved virtual threads? Share your experiences in the comments below!",[651,157468,78024],{},[786,157470,157471],{},"html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .s4-Ip, html code.shiki .s4-Ip{--shiki-default:#6A737D;--shiki-dark:#6A737D;--shiki-sepia:#6A737D}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .spoOn, html code.shiki .spoOn{--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#E36209}",{"title":674,"searchDepth":790,"depth":790,"links":157473},[157474,157475,157478,157479,157483,157484,157485],{"id":156118,"depth":790,"text":156119},{"id":156632,"depth":790,"text":156633,"children":157476},[157477],{"id":156642,"depth":892,"text":156643},{"id":156673,"depth":790,"text":156674},{"id":156703,"depth":790,"text":156704,"children":157480},[157481,157482],{"id":156716,"depth":892,"text":156717},{"id":156906,"depth":892,"text":156907},{"id":157215,"depth":790,"text":157216},{"id":157412,"depth":790,"text":157413},{"id":9041,"depth":790,"text":9042},"Learn how JDK 24's enhancement to virtual threads that eliminates pinning with synchronized blocks can dramatically improve performance in your Spring Boot applications.",{"slug":157488,"date":157489,"published":797,"author":798,"tags":157490,"video":157491,"keywords":157492},"jdk-24-virtual-threads-without-pinning","2025-04-09T17:00:00.000Z",[36422,23816],"https://www.youtube.com/embed/V4gsffMge7E","virtual threads, JDK 24, Java 24, synchronized blocks, Spring Boot, performance optimization, thread pinning, concurrency",{"title":6,"description":157486},"blog/2025/09/jdk-24-virtual-threads-without-pinning","356RZLeWpQIeceDJzbTqcOZhkG5HvOmH2ri5N8rN66k",{"id":157497,"title":157498,"body":157499,"description":157729,"extension":793,"meta":157730,"navigation":797,"path":157733,"seo":157734,"stem":157735,"__hash__":157736},"content/newsletter/2019/07/28/coffee-and-code-01.md","Coffee & Code Newsletter: #1",{"type":648,"value":157500,"toc":157712},[157501,157508,157512,157518,157521,157524,157527,157535,157539,157547,157551,157554,157568,157571,157575,157578,157580,157589,157592,157608,157610,157633,157636,157659,157661,157677,157679,157689,157693,157696,157701,157705,157708],[651,157502,157503,157504,157507],{},"Welcome to the first installment of Coffee and Code with me, ",[812,157505,798],{"href":82688,"rel":157506},[816],". It's a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. I will try and keep these pretty short and digestible. So if you don't have a cup of coffee go and grab one and if you do, let's get to it.",[4542,157509,157511],{"id":157510},"previous-week","Previous Week",[5909,157513,157515],{"id":157514},"live-stream",[2939,157516,157517],{},"Live Stream",[651,157519,157520],{},"This week I ran my very first live stream and while I was a little bit nervous I was really happy with the way everything turned out. I have been saying for a while now that I wanted to start live streaming and I finally stopped coming up with excuses and jumped right in. I want to thank all of you who took time out of your day to join me. It is much easier to do these live sessions with an interactive audience.",[651,157522,157523],{},"In this live stream, I started by giving the current state of web application architecture types. Then we dove into what static site generators are and spent a little bit of time on what the \"JAM Stack\" is.",[651,157525,157526],{},"Finally, we wrapped up by looking at a VueJS static site generator called Gridsome. This is the framework I used to build my new site and I have been impressed with it. I walked you through setting up your first Gridsome site and I showed you the start of how you could build your own blog.",[651,157528,157529,157530,664],{},"If you weren't able to make the live stream the ",[812,157531,157534],{"href":157532,"rel":157533},"https://www.youtube.com/watch?v=UEBTiMpvgas",[816],"recording is posted on YouTube",[5909,157536,157537],{"id":43724},[2939,157538,37521],{},[651,157540,157541,157542,157546],{},"This week I also ",[812,157543,36328],{"href":157544,"rel":157545},"https://www.danvega.dev/blog/2019/07/23/website-new-features-improvements",[816]," on new features and improvements to my website. These were a series of new features that I have been working on and I was excited to get these out there.",[4542,157548,157550],{"id":157549},"upcoming-week","Upcoming Week",[651,157552,157553],{},"I am still working on a schedule for live streaming but here are a few topics I have in mind.",[5316,157555,157556,157559,157562,157565],{},[5332,157557,157558],{},"Adding a Newsletter archive to my website",[5332,157560,157561],{},"Building a Gridsome Starter with my blog theme",[5332,157563,157564],{},"Vuex Intro",[5332,157566,157567],{},"Vue Function Based Component API",[651,157569,157570],{},"If you have an idea for something you would like to see me cover in a live stream please feel free to reach out to me.",[4542,157572,157574],{"id":157573},"around-the-web","Around the Web",[651,157576,157577],{},"These are things I found cool around the web this week.",[5909,157579,69849],{"id":69848},[5316,157581,157582],{},[5332,157583,157584],{},[812,157585,157588],{"href":157586,"rel":157587},"https://scotch.io/tutorials/building-an-animal-adoption-site-with-node-and-vue-part-1",[816],"Building an Animal Adoption Site with Node and Vue - Part 1",[5909,157590,120990],{"id":157591},"videos",[5316,157593,157594,157601],{},[5332,157595,157596],{},[812,157597,157600],{"href":157598,"rel":157599},"https://vimeo.com/348717993",[816],"Sarah Drasner on Let's Write a Vue App From Scratch",[5332,157602,157603],{},[812,157604,157607],{"href":157605,"rel":157606},"https://www.youtube.com/watch?v=g9bSmxnx-O0",[816],"CJ on Build a Hacker New Client with the Vue 3 Function Based Component API",[5909,157609,26378],{"id":39439},[5316,157611,157612,157619,157626],{},[5332,157613,157614],{},[812,157615,157618],{"href":157616,"rel":157617},"https://devmode.fm/episodes/new-awesomeness-coming-in-vuejs-3-0",[816],"New awesomeness coming in Vue.js 3.0",[5332,157620,157621],{},[812,157622,157625],{"href":157623,"rel":157624},"https://devchat.tv/views-on-vue/vov-071-gridsome-with-gift-egwuenu/",[816],"Gridsome with Gift Egwuenu",[5332,157627,157628],{},[812,157629,157632],{"href":157630,"rel":157631},"https://www.redhat.com/en/command-line-heroes/season-3/creating-javascript",[816],"Command Line Heroes: Season 3: Creating JavaScript",[5909,157634,157635],{"id":133422},"Projects",[5316,157637,157638,157645,157652],{},[5332,157639,157640],{},[812,157641,157644],{"href":157642,"rel":157643},"https://github.com/hugomd/parrot.live",[816],"curl parrot.live",[5332,157646,157647],{},[812,157648,157651],{"href":157649,"rel":157650},"https://www.notion.so/danvega/Coffee-Code-1-07-28-2019-cfc553c3953a4e989688ceb9942d511b#9a65ac73276a474cba39d96959c1bd22",[816],"vue-function-api",[5332,157653,157654],{},[812,157655,157658],{"href":157656,"rel":157657},"https://github.com/u3u/vue-hooks",[816],"vue-hooks",[5909,157660,21973],{"id":21972},[5316,157662,157663,157670],{},[5332,157664,157665],{},[812,157666,157669],{"href":157667,"rel":157668},"https://www.leveluptutorials.com/tutorials/dev-tools-and-debugging",[816],"Level Up Tutorials: DevTools & Debugging",[5332,157671,157672],{},[812,157673,157676],{"href":157674,"rel":157675},"https://egghead.io/courses/javascript-es2019-in-practice",[816],"Egghead.io: JavaScript ES2019 in Practice",[5909,157678,79609],{"id":79608},[5316,157680,157681],{},[5332,157682,157683,157688],{},[812,157684,157687],{"href":157685,"rel":157686},"https://www.javascriptandfriends.com/",[816],"JavaScript & Friends"," August 02, 2019",[5909,157690,157692],{"id":157691},"follow-this-person","Follow This Person",[651,157694,157695],{},"This is someone I really enjoy following on Twitter and I think you should check them out.",[651,157697,157698],{},[812,157699,60851],{"href":60851,"rel":157700},[816],[4542,157702,157704],{"id":157703},"until-next-week","Until Next Week",[651,157706,157707],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this first installment of Coffee & Code and I will see you next Sunday morning. If you have any links you would like me to include please contact me and I might add them to a future newsletter. I hope you have a great week and as always friends...",[651,157709,41105,157710,69920],{},[41107,157711],{},{"title":674,"searchDepth":790,"depth":790,"links":157713},[157714,157718,157719,157728],{"id":157510,"depth":790,"text":157511,"children":157715},[157716,157717],{"id":157514,"depth":892,"text":157517},{"id":43724,"depth":892,"text":37521},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":157720},[157721,157722,157723,157724,157725,157726,157727],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":79608,"depth":892,"text":79609},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},"Welcome to the first installment of Coffee and Code with me, Dan Vega. It's a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. I will try and keep these pretty short and digestible. So if you don't have a cup of coffee go and grab one and if you do, let's get to it.",{"slug":157731,"issue":682,"date":157732},"coffee-and-code-newsletter-1","2019-07-28T08:00:00.000Z","/newsletter/2019/07/28/coffee-and-code-01",{"title":157498,"description":157729},"newsletter/2019/07/28/coffee-and-code-01","zc_cnDA1bwZ7o_v64aEb6CQLMtt_Jrjv3xu4H5FQR74",{"id":157738,"title":157739,"body":157740,"description":157930,"extension":793,"meta":157931,"navigation":797,"path":157934,"seo":157935,"stem":157936,"__hash__":157937},"content/newsletter/2019/08/04/coffee-and-code-02.md","Coffee & Code Newsletter: #2",{"type":648,"value":157741,"toc":157915},[157742,157749,157751,157759,157763,157771,157778,157785,157789,157796,157798,157805,157808,157810,157813,157816,157818,157820,157822,157845,157847,157869,157871,157893,157897,157906,157908,157911],[651,157743,157744,157745,157748],{},"Welcome to Coffee and Code with me, ",[812,157746,798],{"href":82688,"rel":157747},[816],". This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you don't already have a cup of coffee grab one now and let's get to it.",[4542,157750,157511],{"id":157510},[651,157752,157753,157754,157758],{},"I was really busy this week but I was able to sneak in a live coding session yesterday so we can talk about that. I also started playing around with ",[812,157755,42865],{"href":157756,"rel":157757},"https://micronaut.io/",[816]," this week. If you're a Java developer have you had a chance to check it out yet? So far I am really enjoying just how fast and easy to use it is. If you have used it before or you're interested in seeing some content from me on it please let me know.",[5909,157760,157761],{"id":157514},[2939,157762,157517],{},[651,157764,157765,157766,157770],{},"I was able to sneak a live stream in yesterday and it was a lot of fun. I was going to try streaming on ",[812,157767,60837],{"href":157768,"rel":157769},"https://www.twitch.tv/danvega",[816]," but I had some problems so I ended up just going live on YouTube. I am still learning the platforms and figuring out how to use them so I apologize for any issues.",[651,157772,157773,157774,157777],{},"In this coding session, I wanted to start building out a newsletter archive. I want you to have the ability to read a newsletter item online and share it with friends. If you are reading this in an email client you can look towards the top where there is a ",[676,157775,157776],{},"Read this on danvega.dev"," link.",[651,157779,157780,157781,664],{},"We spent some time talking about how to add a new file system source and a new content type. If you're interested in watching the replay it has been posted on ",[812,157782,15432],{"href":157783,"rel":157784},"https://youtu.be/5lXmyp4DJxg",[816],[5909,157786,157787],{"id":43724},[2939,157788,37521],{},[651,157790,157791,157792,664],{},"I don't have anything new this week but I will share with you an article that I mentioned in the live stream. Each of my blog posts has a lot of metadata at the top of the markdown file. I don't like having to look these up and write them by hand so I wrote a ",[812,157793,157795],{"href":76548,"rel":157794},[816],"generator for creating blog posts in Gridsome",[5909,157797,15432],{"id":61224},[651,157799,157800],{},[812,157801,157804],{"href":157802,"rel":157803},"https://www.youtube.com/watch?v=K2_3rrcZVgg&feature=youtu.be",[816],"How to Remove Elements from an ArrayList in Java.",[651,157806,157807],{},"In this tutorial, we are going to learn how to safely remove elements from an ArrayList in Java while using a for loop to loop over that same list.",[4542,157809,157550],{"id":157549},[651,157811,157812],{},"I can't believe it but we have another cohort graduating from Tech Elevator in 2 weeks. I am a product manager for a team of students building their final capstone project so between that and my own project work is going to take up most of my time.",[651,157814,157815],{},"I need to find a way to schedule out these live coding sessions. Would it be helpful if they were at the same time every week? Reach out to me and let me know.",[4542,157817,157574],{"id":157573},[651,157819,157577],{},[5909,157821,69849],{"id":69848},[5316,157823,157824,157831,157838],{},[5332,157825,157826],{},[812,157827,157830],{"href":157828,"rel":157829},"https://dev.to/firstclown/understanding-data-flow-in-vuex-b5b",[816],"Understanding data flow in Vuex",[5332,157832,157833],{},[812,157834,157837],{"href":157835,"rel":157836},"https://daverupert.com/2019/07/what-i-like-about-vue/",[816],"What I like about Vue",[5332,157839,157840],{},[812,157841,157844],{"href":157842,"rel":157843},"https://dev.to/raymondcamden/multiple-ways-of-api-integration-in-your-jamstack-4mh6",[816],"Multiple ways of API Integration in your JamStack",[5909,157846,120990],{"id":157591},[5316,157848,157849,157856,157863],{},[5332,157850,157851],{},[812,157852,157855],{"href":157853,"rel":157854},"https://www.youtube.com/watch?v=pws4qzGn5ak",[816],"Wes Bos - Get Better at JavaScript with just JavaScript",[5332,157857,157858],{},[812,157859,157862],{"href":157860,"rel":157861},"https://www.youtube.com/watch?v=t54tuaoHVLo",[816],"Rebuilding Discord w/ Tailwind CSS v1.0",[5332,157864,157865],{},[812,157866,36474],{"href":157867,"rel":157868},"https://www.youtube.com/channel/UCCBVCTuk6uJrN3iFV_3vurg/videos",[816],[5909,157870,26378],{"id":39439},[5316,157872,157873,157879,157886],{},[5332,157874,157875],{},[812,157876,157878],{"href":157616,"rel":157877},[816],"devMode.fm: New awesomeness coming in Vue.JS 3.0",[5332,157880,157881],{},[812,157882,157885],{"href":157883,"rel":157884},"http://differential.libsyn.com/anthony-hughes-company-is-world-class-at-transforming-lives",[816],"Differential: Anthony Hughes company is world class at transforming lives",[5332,157887,157888],{},[812,157889,157892],{"href":157890,"rel":157891},"https://megaphone.link/LMM3473364591",[816],"Money Lab: Small Changes, Big Payoffs",[5909,157894,157896],{"id":157895},"follow-on-twitter","Follow on Twitter",[5316,157898,157899],{},[5332,157900,157901],{},[812,157902,157905],{"href":157903,"rel":157904},"https://twitter.com/firstclown",[816],"Joe Erickson: @firstclown",[4542,157907,157704],{"id":157703},[651,157909,157910],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of Coffee & Code and I will see you next Sunday morning. If you have any links you would like me to include please contact me and I might add them to a future newsletter. I hope you have a great week and as always friends...",[651,157912,41105,157913,69920],{},[41107,157914],{},{"title":674,"searchDepth":790,"depth":790,"links":157916},[157917,157922,157923,157929],{"id":157510,"depth":790,"text":157511,"children":157918},[157919,157920,157921],{"id":157514,"depth":892,"text":157517},{"id":43724,"depth":892,"text":37521},{"id":61224,"depth":892,"text":15432},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":157924},[157925,157926,157927,157928],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":157895,"depth":892,"text":157896},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":157932,"issue":790,"date":157933},"coffee-and-code-newsletter-2","2019-08-04T08:00:00.000Z","/newsletter/2019/08/04/coffee-and-code-02",{"title":157739,"description":157930},"newsletter/2019/08/04/coffee-and-code-02","eId0C3bWorKUaCLvVOL5LydORsl3LLoUI6qMP00gOQc",{"id":157939,"title":157940,"body":157941,"description":157930,"extension":793,"meta":158148,"navigation":797,"path":158151,"seo":158152,"stem":158153,"__hash__":158154},"content/newsletter/2019/08/11/coffee-and-code-03.md","Coffee & Code Newsletter: #3",{"type":648,"value":157942,"toc":158133},[157943,157948,157950,157959,157964,157971,157973,157979,157984,157986,157988,157999,158001,158007,158009,158016,158019,158021,158030,158033,158035,158037,158039,158055,158057,158073,158075,158098,158100,158116,158118,158120,158125,158127,158129],[651,157944,157744,157945,157748],{},[812,157946,798],{"href":82688,"rel":157947},[816],[4542,157949,157511],{"id":157510},[651,157951,157952,157953,157958],{},"A couple of months ago I applied to become an author for ",[812,157954,157957],{"href":157955,"rel":157956},"https://www.freecodecamp.org/news/",[816],"freeCodeCamp"," and this weekend I received the following email that I am so excited to share with you.",[651,157960,157961],{},[660,157962],{"alt":674,"src":157963},"/images/newsletter/2019/08/11/freecodecamp-email.jpeg",[651,157965,157966,157967,664],{},"I have been a huge admirer of what freeCodeCamp does for our community and I can't wait to start contributing. A huge thanks to Quincy Larson for approving my application. Now the real question is what should my first article be about? If you have any thoughts please ",[812,157968,157970],{"href":51472,"rel":157969},[816],"tweet at me",[5909,157972,37521],{"id":43724},[651,157974,157975],{},[812,157976,468],{"href":157977,"rel":157978},"https://www.danvega.dev/blog/2019/08/08/css-grid-generator",[816],[651,157980,157981],{},[660,157982],{"alt":674,"src":157983},"/images/newsletter/2019/08/11/css-grid-generator-cover.png",[651,157985,67777],{},[651,157987,67780],{},[651,157989,157990,157991,157994,157995,157998],{},"This is why when tools like ",[812,157992,67788],{"href":67786,"rel":157993},[816]," & ",[812,157996,67793],{"href":67791,"rel":157997},[816]," came along I was beyond excited about the possibilities of creating clean layouts. I think if you combine these 2 technologies along with just how far JavaScript has come it is what has really got me excited about front end development these days.",[651,158000,67797],{},[651,158002,158003],{},[812,158004,158006],{"href":157977,"rel":158005},[816],"Continue Reading",[5909,158008,15432],{"id":61224},[651,158010,158011],{},[812,158012,158015],{"href":158013,"rel":158014},"https://www.youtube.com/watch?v=3vO8FTNyHww",[816],"Installing Java with SDKMan",[651,158017,158018],{},"Nothing new this week in video format so I am pulling this one from my archives. One of my favorite tools around is called SDKMan. In this tutorial, I introduce you to SDKMan and how to use it.",[5909,158020,79433],{"id":79432},[651,158022,158023,158024,158029],{},"Listen, I realize that I have a problem and I'm ok with it. 🤷♂️ I love learning new things and when I found out about a new(ish) JavaScript framework for the server I just had to check it out. This week I finally had a chance to play around with ",[812,158025,158028],{"href":158026,"rel":158027},"https://nestjs.com/",[816],"NestJS",". While I didn't build anything that exciting I was able to produce a CRUD application that talked to a MongoDB database.",[651,158031,158032],{},"This might sound a little strange given the fact that they are not the same tech stacks but It felt a lot like using Spring Boot. I was able to get something done quickly and I feel like NestJS enables me to be a more productive developer. If you're interested in learning more about NestJS or have your opinions about it please reach out to me.",[4542,158034,157574],{"id":157573},[651,158036,157577],{},[5909,158038,69849],{"id":69848},[5316,158040,158041,158048],{},[5332,158042,158043],{},[812,158044,158047],{"href":158045,"rel":158046},"https://dev.to/firstclown/should-you-always-use-getters-in-vuex-4p0c",[816],"Should you always use getters in Vuex?",[5332,158049,158050],{},[812,158051,158054],{"href":158052,"rel":158053},"https://dev.to/raymondcamden/drag-and-drop-file-upload-in-vue-js-11kd",[816],"Drag & Drop File Upload in VueJS",[5909,158056,120990],{"id":157591},[5316,158058,158059,158066],{},[5332,158060,158061],{},[812,158062,158065],{"href":158063,"rel":158064},"https://www.youtube.com/watch?v=TFmYL75RleM&list=WL&index=17&t=0s",[816],"Getting Started with Vuetify 2.0",[5332,158067,158068],{},[812,158069,158072],{"href":158070,"rel":158071},"https://www.youtube.com/watch?v=sqkwHUyV-YY",[816],"Behind the scenes with Coding Train",[5909,158074,26378],{"id":39439},[5316,158076,158077,158084,158091],{},[5332,158078,158079],{},[812,158080,158083],{"href":158081,"rel":158082},"https://devmode.fm/episodes/webpack-inside-out-with-sean-larkin",[816],"devmode.fm: Webpack inside and out with Sean Larkin",[5332,158085,158086],{},[812,158087,158090],{"href":158088,"rel":158089},"https://devchat.tv/views-on-vue/vov-073-contributing-to-open-source-with-debbie-obrien/",[816],"Contributing to open source with Debbie O'Brien",[5332,158092,158093],{},[812,158094,158097],{"href":158095,"rel":158096},"https://realtalkjavascript.simplecast.fm/71af117a",[816],"Real Talk JavaScript: Long time Java & GO to First time with JavaScript",[5909,158099,157635],{"id":133422},[5316,158101,158102,158109],{},[5332,158103,158104],{},[812,158105,158108],{"href":158106,"rel":158107},"https://tailwind.run/",[816],"Tailwind.run()",[5332,158110,158111],{},[812,158112,158115],{"href":158113,"rel":158114},"https://tornis.robbowen.digital/",[816],"Tornis",[5909,158117,157692],{"id":157691},[651,158119,157695],{},[651,158121,158122],{},[812,158123,57131],{"href":57129,"rel":158124},[816],[4542,158126,157704],{"id":157703},[651,158128,157707],{},[651,158130,41105,158131,69920],{},[41107,158132],{},{"title":674,"searchDepth":790,"depth":790,"links":158134},[158135,158140,158147],{"id":157510,"depth":790,"text":157511,"children":158136},[158137,158138,158139],{"id":43724,"depth":892,"text":37521},{"id":61224,"depth":892,"text":15432},{"id":79432,"depth":892,"text":79433},{"id":157573,"depth":790,"text":157574,"children":158141},[158142,158143,158144,158145,158146],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":158149,"issue":892,"date":158150},"coffee-and-code-newsletter-3","2019-08-11T08:00:00.000Z","/newsletter/2019/08/11/coffee-and-code-03",{"title":157940,"description":157930},"newsletter/2019/08/11/coffee-and-code-03","nZLXXEF6helKTZ5jChCNGwtmE2vmYFhCeEssZs3SMz4",{"id":158156,"title":158157,"body":158158,"description":157776,"extension":793,"meta":158314,"navigation":797,"path":158317,"seo":158318,"stem":158319,"__hash__":158320},"content/newsletter/2019/08/18/coffee-and-code-04.md","Coffee & Code Newsletter: #4",{"type":648,"value":158159,"toc":158300},[158160,158166,158168,158170,158179,158182,158188,158192,158195,158201,158203,158210,158213,158215,158217,158219,158228,158230,158239,158241,158257,158259,158282,158284,158286,158292,158294,158296],[651,158161,158162],{},[812,158163,157776],{"href":158164,"rel":158165},"https://www.danvega.dev/newsletter/2019/08/18/coffee-and-code-04",[816],[651,158167,157930],{},[4542,158169,157511],{"id":157510},[651,158171,158172,158173,158178],{},"This week I started a discussion on dev.to called \"",[812,158174,158177],{"href":158175,"rel":158176},"https://dev.to/therealdanvega/what-is-your-conference-talk-walkup-song-36kb",[816],"What is your conference walk up song","\". I thought it was an interesting idea and if you have a chance I would love to hear what yours would be. Other than that I don't have any new articles to share with you but I do have some pretty exciting news. I have some very cool projects in the works that should help get my writing in front of more people which should lead to helping out a lot more people.",[651,158180,158181],{},"I told you that I got accepted to freeCodeCamp last week. This week I also had a really good meeting with someone who I instantly clicked with in the Vue community. I am going to be working on some content with him and I sent out this cryptic tweet to let everyone know what I was working on. 😉",[651,158183,158184],{},[812,158185,158186],{"href":158186,"rel":158187},"https://twitter.com/therealdanvega/status/1161620722445410304",[816],[5909,158189,158190],{"id":157514},[2939,158191,157517],{},[651,158193,158194],{},"No live stream this week but I did send this tweet out that I would love your thoughts on.",[651,158196,158197],{},[812,158198,158199],{"href":158199,"rel":158200},"https://twitter.com/therealdanvega/status/1162351000440725506",[816],[4542,158202,157550],{"id":157549},[651,158204,158205,158206,158209],{},"Now that our summer cohort students have graduated at ",[812,158207,41137],{"href":41135,"rel":158208},[816]," I think things should quiet down over the next month. I will be working on the 2 big articles I told you about earlier and trying to lock down a live streaming schedule.",[651,158211,158212],{},"PS - It's my birthday Wednesday so if you want to say hi on Twitter that would make an old man feel loved.",[4542,158214,157574],{"id":157573},[651,158216,157577],{},[5909,158218,69849],{"id":69848},[5316,158220,158221],{},[5332,158222,158223],{},[812,158224,158227],{"href":158225,"rel":158226},"https://blog.bitsrc.io/vue-js-3-future-oriented-programming-54dee797988b",[816],"VueJS 3.0 - Future-Oriented Programming",[5909,158229,120990],{"id":157591},[5316,158231,158232],{},[5332,158233,158234],{},[812,158235,158238],{"href":158236,"rel":158237},"https://www.youtube.com/watch?v=Y9uDghYuld4",[816],"Raymond Camden: Vue JS Introduction and tutorial",[5909,158240,26378],{"id":39439},[5316,158242,158243,158250],{},[5332,158244,158245],{},[812,158246,158249],{"href":158247,"rel":158248},"https://kentcdodds.com/chats-with-kent-podcast/seasons/01/episodes/growing-your-skills-and-career-through-teaching-with-ali-spittel",[816],"Chats with Kent Season 1 (14 Episodes)",[5332,158251,158252],{},[812,158253,158256],{"href":158254,"rel":158255},"https://www.hanselminutes.com/696/the-pragmatic-programmer-celebrates-20-years-with-dave-thomas-and-andy-hunt",[816],"Hanselminutes: The Pragmatic Programmer celebrates 20 years.",[5909,158258,21973],{"id":21972},[5316,158260,158261,158268,158275],{},[5332,158262,158263],{},[812,158264,158267],{"href":158265,"rel":158266},"https://www.youtube.com/playlist?list=PL7CcGwsqRpSM3w9BT_21tUU8JN2SnyckR",[816],"Adam Wathan: Designing with Tailwind CSS Playlist (15 videos)",[5332,158269,158270],{},[812,158271,158274],{"href":158272,"rel":158273},"http://flexboxfroggy.com/",[816],"Flexbox Froggy",[5332,158276,158277],{},[812,158278,158281],{"href":158279,"rel":158280},"https://learn.hasura.io/",[816],"GraphQL Tutorials from Hasura",[5909,158283,157692],{"id":157691},[651,158285,157695],{},[651,158287,158288],{},[812,158289,43752],{"href":158290,"rel":158291},"https://twitter.com/raymondcamden",[816],[4542,158293,157704],{"id":157703},[651,158295,157707],{},[651,158297,41105,158298,69920],{},[41107,158299],{},{"title":674,"searchDepth":790,"depth":790,"links":158301},[158302,158305,158306,158313],{"id":157510,"depth":790,"text":157511,"children":158303},[158304],{"id":157514,"depth":892,"text":157517},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":158307},[158308,158309,158310,158311,158312],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":21972,"depth":892,"text":21973},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":158315,"issue":901,"date":158316},"coffee-and-code-newsletter-4","2019-08-18T08:00:00.000Z","/newsletter/2019/08/18/coffee-and-code-04",{"title":158157,"description":157776},"newsletter/2019/08/18/coffee-and-code-04","jUOitwUIQa52T_D4JCWWfplzDoCSj35Mn8cFgW1CUg4",{"id":158322,"title":158323,"body":158324,"description":157930,"extension":793,"meta":158572,"navigation":797,"path":158575,"seo":158576,"stem":158577,"__hash__":158578},"content/newsletter/2019/08/25/coffee-and-code-05.md","Coffee & Code Newsletter: #5",{"type":648,"value":158325,"toc":158556},[158326,158328,158330,158333,158337,158354,158357,158363,158367,158370,158377,158383,158385,158387,158389,158418,158420,158436,158438,158454,158456,158479,158481,158504,158506,158529,158531,158539,158542,158548,158550,158552],[651,158327,157930],{},[4542,158329,157511],{"id":157510},[651,158331,158332],{},"It was my birthday this week (I lost count at this point) and I want to thank everyone who reached out to me to wish me a Happy Birthday.",[5909,158334,158336],{"id":158335},"vue-composition-api","Vue Composition API",[651,158338,158339,158340,158345,158346,664],{},"Back in June VueJS released an RFC (request for comments) called ",[2939,158341,158342],{},[7300,158343,158344],{},"Function-based Component API",". There were some that got really upset with this proposal and it caused a lot of conversation in the community. This week the Vue Core Team released an updated version of the RFC titled ",[812,158347,158350],{"href":158348,"rel":158349},"https://github.com/vuejs/rfcs/pull/78",[816],[2939,158351,158352],{},[7300,158353,69821],{},[651,158355,158356],{},"I think a lot of the complaints came from everyone thinking that this is how they would have to write their Single File Components (SFC) and that just wasn't the case. There are some important changes to the RFC and if you want to learn more about the Composition API there is an entire documentation site dedicated to it.",[651,158358,158359],{},[812,158360,158361],{"href":158361,"rel":158362},"https://vue-composition-api-rfc.netlify.com/#summary",[816],[5909,158364,158365],{"id":43724},[2939,158366,37521],{},[651,158368,158369],{},"I don't have any new articles this week but I do have one that a bunch of folks have been sharing on Twitter lately. If you are in the JavaScript ecosystem you know there is this package manager called NPM.",[651,158371,158372,158373,664],{},"Most of us just use it as away to pull dependencies into our projects but have you ever thought about creating your NPM package? if so I have this really great article that walks you through creating and publishing your ",[812,158374,158376],{"href":60200,"rel":158375},[816],"first NPM package",[651,158378,158379],{},[660,158380],{"alt":158381,"src":158382},"Create Your First NPM Package Blog Post","/images/newsletter/2019/08/25/first-npm-package.png",[4542,158384,157574],{"id":157573},[651,158386,157577],{},[5909,158388,69849],{"id":69848},[5316,158390,158391,158397,158404,158411],{},[5332,158392,158393],{},[812,158394,158396],{"href":157835,"rel":158395},[816],"What I like About Vue - Dave Rupert",[5332,158398,158399],{},[812,158400,158403],{"href":158401,"rel":158402},"https://scotch.io/tutorials/build-an-infinite-scroll-image-gallery-with-gatsby-and-netlify-functions",[816],"Build an infinite scroll image gallery with Gatsby and Netlify functions",[5332,158405,158406],{},[812,158407,158410],{"href":158408,"rel":158409},"https://css-tricks.com/lets-build-a-jamstack-e-commerce-store-with-netlify-functions/",[816],"Let’s Build a JAMstack E-Commerce Store with Netlify Functions",[5332,158412,158413],{},[812,158414,158417],{"href":158415,"rel":158416},"https://www.smashingmagazine.com/2019/07/using-slots-vue-js/",[816],"Using Slots in VueJS",[5909,158419,6016],{"id":39340},[5316,158421,158422,158429],{},[5332,158423,158424],{},[812,158425,158428],{"href":158426,"rel":158427},"https://leanpub.com/firstyearincode",[816],"You first year in code",[5332,158430,158431],{},[812,158432,158435],{"href":158433,"rel":158434},"https://gettogetherbook.com",[816],"Get Together Book",[5909,158437,120990],{"id":157591},[5316,158439,158440,158447],{},[5332,158441,158442],{},[812,158443,158446],{"href":158444,"rel":158445},"https://www.smashingmagazine.com/2019/08/smashingconf-toronto-2019-video/",[816],"SmashingConf Toronto Conference Playlist",[5332,158448,158449],{},[812,158450,158453],{"href":158451,"rel":158452},"https://www.youtube.com/watch?v=l4P53_nGnmg&t=799s",[816],"From Swift Developer to YouTuber",[5909,158455,26378],{"id":39439},[5316,158457,158458,158465,158472],{},[5332,158459,158460],{},[812,158461,158464],{"href":158462,"rel":158463},"https://kentcdodds.com/chats-with-kent-podcast/seasons/01/episodes/getting-started-with-code-live-streaming-with-suz-hinton",[816],"Chats with Kent: Getting Started with Code Live Streaming - Suz Hinton",[5332,158466,158467],{},[812,158468,158471],{"href":158469,"rel":158470},"https://nofluffjuststuff.com/podcast/1/raju_gandhi_on_vuejs_and_evaluating_web_frameworks",[816],"Raju Gandhi on Vuejs and Evaluating web frameworks",[5332,158473,158474],{},[812,158475,158478],{"href":158476,"rel":158477},"https://podcasts.apple.com/us/podcast/youtube-sues-elon-tweets-and-apple-has-battery-problems/id1474429475?i=1000447602345",[816],"MKBHD: YouTube Sues, Elon Tweets, and Apple Has Battery Problems",[5909,158480,157635],{"id":133422},[5316,158482,158483,158490,158497],{},[5332,158484,158485],{},[812,158486,158489],{"href":158487,"rel":158488},"https://help.github.com/en/articles/about-github-package-registry",[816],"The Github Package Registry is now in beta",[5332,158491,158492],{},[812,158493,158496],{"href":158494,"rel":158495},"https://github.com/testing-library/vue-testing-library",[816],"Vue Testing Library",[5332,158498,158499],{},[812,158500,158503],{"href":158501,"rel":158502},"https://twitter.com/this_vid",[816],"Download This Video",[5909,158505,79609],{"id":79608},[5316,158507,158508,158515,158522],{},[5332,158509,158510],{},[812,158511,158514],{"href":158512,"rel":158513},"https://cfe.dev/events/flashback-conference-2020/",[816],"Flashback Conference",[5332,158516,158517],{},[812,158518,158521],{"href":158519,"rel":158520},"https://vuejs.london/",[816],"Vue.js London 2019",[5332,158523,158524],{},[812,158525,158528],{"href":158526,"rel":158527},"http://www.codemash.org/call-speakers/",[816],"Code Mash CFP",[5909,158530,157692],{"id":157691},[651,158532,158533,158534,158538],{},"I came across a presentation by Raju Gandhi on testing for some research I am doing on the topic and I was just blown away. When I come across people like this I try to find as much content from them as possible. I also listened to a ",[812,158535,158537],{"href":158469,"rel":158536},[816],"podcast"," that he was on talking about how much he enjoys working with VueJS.",[651,158540,158541],{},"Raju has a Java background, loves Vue and dabbles in all kinds of tech. Did I mention he also lives in Ohio? I need to tak some time, head down to Columbus and hang out with Raju soon! Please give him a follow on Twitter using the link below.",[651,158543,158544],{},[812,158545,158546],{"href":158546,"rel":158547},"https://twitter.com/looselytyped",[816],[4542,158549,157704],{"id":157703},[651,158551,157707],{},[651,158553,41105,158554,69920],{},[41107,158555],{},{"title":674,"searchDepth":790,"depth":790,"links":158557},[158558,158562,158571],{"id":157510,"depth":790,"text":157511,"children":158559},[158560,158561],{"id":158335,"depth":892,"text":158336},{"id":43724,"depth":892,"text":37521},{"id":157573,"depth":790,"text":157574,"children":158563},[158564,158565,158566,158567,158568,158569,158570],{"id":69848,"depth":892,"text":69849},{"id":39340,"depth":892,"text":6016},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":79608,"depth":892,"text":79609},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":158573,"issue":909,"date":158574},"coffee-and-code-newsletter-5","2019-08-25T08:00:00.000Z","/newsletter/2019/08/25/coffee-and-code-05",{"title":158323,"description":157930},"newsletter/2019/08/25/coffee-and-code-05","XRM32DZajwuznKVfCjnqeDgG2pVP3fUQ7QyuO34vOv0",{"id":158580,"title":158581,"body":158582,"description":157930,"extension":793,"meta":158775,"navigation":797,"path":158778,"seo":158779,"stem":158780,"__hash__":158781},"content/newsletter/2019/09/01/coffee-and-code-06.md","Coffee & Code Newsletter: #6",{"type":648,"value":158583,"toc":158759},[158584,158586,158588,158591,158593,158601,158604,158606,158609,158615,158618,158626,158632,158638,158644,158650,158652,158654,158656,158658,158674,158676,158692,158694,158710,158712,158721,158725,158734,158736,158751,158753,158755],[651,158585,157930],{},[4542,158587,157511],{"id":157510},[651,158589,158590],{},"This week was a busy week for me. I published a new blog article, a tutorial on YouTube, and launched a back to school promotion for all of my courses on Udemy.",[5909,158592,15432],{"id":61224},[651,158594,43185,158595,158600],{},[812,158596,158599],{"href":158597,"rel":158598},"https://www.youtube.com/watch?v=fi4ZjqNcSQc",[816],"published a new video on YouTube"," about Web Template Studio, which is a Visual Studio Code Extension. Web Template Studio (WebTS) is a user-friendly wizard to quickly bootstrap a web application. Best of all, Web Template Studio is open-source on GitHub.",[651,158602,158603],{},"I installed the extension for the first time and walked through bootstrapping a new web application using Vue + Express.",[5909,158605,21973],{"id":21972},[651,158607,158608],{},"I'm a life-long learner and it is something I really enjoy doing. Since you're a student here on Udemy I am guessing most of you are the same. I mean we pretty much have to be in the field that we are in right?",[651,158610,158611],{},[660,158612],{"alt":158613,"src":158614},"Back to School Sale on my Udemy Courses","/images/newsletter/2019/09/01/back-to-school.jpg",[651,158616,158617],{},"When students of all ages start going back to school it reminds me of getting those fresh school supplies and heading to school eager to learn something new. If you want to learn something new over the holiday weekend I am offering up all of my courses for $9.99. If you're interested you can use the links below but grab these coupons fast, the back to school sale ends September 2nd.",[651,158619,158620,158621],{},"Coupon: ",[2939,158622,158623],{},[7300,158624,158625],{},"BACKTOSCHOOL",[651,158627,158628],{},[812,158629,82980],{"href":158630,"rel":158631},"https://www.udemy.com/spring-boot-intro/?couponCode=BACKTOSCHOOL",[816],[651,158633,158634],{},[812,158635,79479],{"href":158636,"rel":158637},"https://www.udemy.com/spring-boot-2/?couponCode=BACKTOSCHOOL",[816],[651,158639,158640],{},[812,158641,24640],{"href":158642,"rel":158643},"https://www.udemy.com/apache-groovy/?couponCode=BACKTOSCHOOL",[816],[651,158645,158646],{},[812,158647,38461],{"href":158648,"rel":158649},"https://www.udemy.com/course/angular-4-java-developers/?couponCode=BACKTOSCHOOL",[816],[4542,158651,157550],{"id":157549},[4542,158653,157574],{"id":157573},[651,158655,157577],{},[5909,158657,69849],{"id":69848},[5316,158659,158660,158667],{},[5332,158661,158662],{},[812,158663,158666],{"href":158664,"rel":158665},"https://dev.to/dobromirhristov/the-developer-s-guide-to-the-vue-ecosystem-4amb",[816],"The Developers Guide to the Vue Ecosystem",[5332,158668,158669],{},[812,158670,158673],{"href":158671,"rel":158672},"https://www.smashingmagazine.com/2019/08/vuepress-documentation/",[816],"VuePress: Documentation Made Easy",[5909,158675,120990],{"id":157591},[5316,158677,158678,158685],{},[5332,158679,158680],{},[812,158681,158684],{"href":158682,"rel":158683},"https://www.youtube.com/watch?v=FPA_3dXzH7A",[816],"Groovy 3 & Beyond: An Insider's Guide",[5332,158686,158687],{},[812,158688,158691],{"href":158689,"rel":158690},"https://www.youtube.com/watch?v=aCv29JKmHNY",[816],"Inside Bill's Brain: Decoding Bill Gates",[5909,158693,26378],{"id":39439},[5316,158695,158696,158703],{},[5332,158697,158698],{},[812,158699,158702],{"href":158700,"rel":158701},"https://devchat.tv/views-on-vue/vov-076-typescript-tell-all-with-jack-koppa/",[816],"Views on Vue: TypeScript tell all with Jack Koppa",[5332,158704,158705],{},[812,158706,158709],{"href":158707,"rel":158708},"https://www.scottadamssays.com/2019/08/28/episode-644-scott-adams-the-happiness-formula/",[816],"Coffee with Scott Adams: The Happiness Formula",[5909,158711,157635],{"id":133422},[5316,158713,158714],{},[5332,158715,158716],{},[812,158717,158720],{"href":158718,"rel":158719},"https://vue-community.org/",[816],"Vue Community Guide",[5909,158722,158724],{"id":158723},"products","Products",[5316,158726,158727],{},[5332,158728,158729],{},[812,158730,158733],{"href":158731,"rel":158732},"https://www.mozilla.org/en-US/firefox/developer/",[816],"FireFox Quantum: Developer Edition",[5909,158735,157692],{"id":157691},[651,158737,158738,158739,158744,158745,158750],{},"I got a chance to watch ",[812,158740,158743],{"href":158741,"rel":158742},"https://twitter.com/lizcodes",[816],"@lizcodes"," coding on her ",[812,158746,158749],{"href":158747,"rel":158748},"https://www.twitch.tv/illuminatedspace",[816],"live stream"," last week. She in the middle of building a project using Gatsby and React which I don't use at all but I found myself wanting to stick around. She is not only a super talented engineer but she just has a lot of fun writing code and talking to anyone in the room. If you haven't had a chance to check her out, do it now!",[4542,158752,157704],{"id":157703},[651,158754,157707],{},[651,158756,41105,158757,69920],{},[41107,158758],{},{"title":674,"searchDepth":790,"depth":790,"links":158760},[158761,158765,158766,158774],{"id":157510,"depth":790,"text":157511,"children":158762},[158763,158764],{"id":61224,"depth":892,"text":15432},{"id":21972,"depth":892,"text":21973},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":158767},[158768,158769,158770,158771,158772,158773],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":158723,"depth":892,"text":158724},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":158776,"issue":918,"date":158777},"coffee-and-code-newsletter-6","2019-09-01T07:00:00.000Z","/newsletter/2019/09/01/coffee-and-code-06",{"title":158581,"description":157930},"newsletter/2019/09/01/coffee-and-code-06","fKFjxZhM8vwtdSzlbGQpg5gyYvnkpO258lqWfoBUO8U",{"id":158783,"title":158784,"body":158785,"description":157930,"extension":793,"meta":158963,"navigation":797,"path":158966,"seo":158967,"stem":158968,"__hash__":158969},"content/newsletter/2019/09/08/coffee-and-code-07.md","Coffee & Code Newsletter: #7",{"type":648,"value":158786,"toc":158951},[158787,158789,158791,158794,158803,158811,158814,158816,158818,158820,158843,158845,158878,158880,158889,158891,158921,158923,158932,158934,158937,158943,158945,158947],[651,158788,157930],{},[4542,158790,157511],{"id":157510},[651,158792,158793],{},"There isn't a whole lot to talk about as far as new content goes from me. I did want to share a couple of things with you that I hope you find exciting.",[651,158795,158796,158797,158802],{},"First off if you're in the Cleveland area or know anyone who is and you're interested in working for an amazing company, ",[812,158798,158801],{"href":158799,"rel":158800},"https://www.techelevator.com/become-an-instructor",[816],"Tech Elevator is looking for another instructor",". I absolutely love Tech Elevator because every single person in the company cares about the students' outcome. If you have a passion for programming and want to help others this is a perfect opportunity for you.",[651,158804,158805,158810],{},[812,158806,158809],{"href":158807,"rel":158808},"https://hacktoberfest.digitalocean.com/",[816],"Hacktoberfest"," is only a few weeks away and I want to make sure all of you sign up for it. Hacktoberfest is a great program that encourages more participation in the open-source community. If you can complete the 2019 challenge you will earn a limited edition t-shirt. I have to tell you that the shirts they give out every year are really awesome and I am hoping to get my hands on one of them this year. I might even set up some issues that I could use some help on in a few my projects so stay tuned.",[651,158812,158813],{},"Finally, I have been working really hard on an article for the Vue community. This thing is massive and approaching 5,000 words but I imagine it will get cut down some. The only reason I am telling you about it is so that you don't think I am over here slacking on you guys. This is what has been taking up my time and I hope to have something to share with you soon.",[4542,158815,157574],{"id":157573},[651,158817,157577],{},[5909,158819,69849],{"id":69848},[5316,158821,158822,158829,158836],{},[5332,158823,158824],{},[812,158825,158828],{"href":158826,"rel":158827},"https://wattenberger.com/blog/d3",[816],"How to Learn D3.js",[5332,158830,158831],{},[812,158832,158835],{"href":158833,"rel":158834},"https://beta.music.apple.com/us/browse",[816],"Apple Music Launches on the web",[5332,158837,158838],{},[812,158839,158842],{"href":158840,"rel":158841},"https://medium.com/young-coder/how-javascript-grew-up-and-became-a-real-language-17a0b948b77f",[816],"How JavaScript Grew Up and Became a Real Language",[5909,158844,120990],{"id":157591},[5316,158846,158847,158854,158861,158868],{},[5332,158848,158849],{},[812,158850,158853],{"href":158851,"rel":158852},"https://www.youtube.com/watch?v=lHBE0mIDTHk",[816],"New & Experimental CSS Tools in Firefox",[5332,158855,158856],{},[812,158857,158860],{"href":158858,"rel":158859},"https://www.youtube.com/watch?v=6Ievupll1ng",[816],"This Dot State of Frameworks- August 2019",[5332,158862,158863],{},[812,158864,158867],{"href":158865,"rel":158866},"https://www.youtube.com/watch?v=eveoy_So-nA",[816],"Ecamm Live 3.2 Announcement",[5332,158869,158870],{},[812,158871,158874,158875],{"href":158872,"rel":158873},"https://www.youtube.com/watch?v=JMU4eYGpES8",[816],"YouTube Live: Top 5 New Features in Live Control Room ",[679,158876,158877],{},"Sneak Peek",[5909,158879,26378],{"id":39439},[5316,158881,158882],{},[5332,158883,158884],{},[812,158885,158888],{"href":158886,"rel":158887},"https://thatsmyjamstack.com/posts/raymond-camden/",[816],"Raymond Camden on the history of Static, hosting, side projects and more!",[5909,158890,157635],{"id":133422},[5316,158892,158893,158900,158907,158914],{},[5332,158894,158895],{},[812,158896,158899],{"href":158897,"rel":158898},"https://darklang.com/",[816],"Dark Programming Language",[5332,158901,158902],{},[812,158903,158906],{"href":158904,"rel":158905},"https://code.visualstudio.com/updates/v1_38",[816],"Visual Studio Code - August 2019 (version 1.38)",[5332,158908,158909],{},[812,158910,158913],{"href":158911,"rel":158912},"https://devblogs.microsoft.com/typescript/announcing-typescript-3-6/",[816],"Announcing TypeScript 3.6",[5332,158915,158916],{},[812,158917,158920],{"href":158918,"rel":158919},"https://www.wappalyzer.com",[816],"Wappalyzer",[5909,158922,6016],{"id":39340},[5316,158924,158925],{},[5332,158926,158927],{},[812,158928,158931],{"href":158929,"rel":158930},"https://amzn.to/2A0aOqp",[816],"Superfans: The Easy Way to Stand Out, Grow Your Tribe, and Build a Successful Business",[5909,158933,157692],{"id":157691},[651,158935,158936],{},"Anthony Gore is the creator of VueJS Developers which is an online community the teaches Vue through a series of articles, courses, and a weekly newsletter. Anthony is an awesome teacher and I am always learning something from him. Give him a follow on Twitter and let him know you appreciate his work 👋",[651,158938,158939],{},[812,158940,158941],{"href":158941,"rel":158942},"https://twitter.com/anthonygore",[816],[4542,158944,157704],{"id":157703},[651,158946,157707],{},[651,158948,41105,158949,69920],{},[41107,158950],{},{"title":674,"searchDepth":790,"depth":790,"links":158952},[158953,158954,158962],{"id":157510,"depth":790,"text":157511},{"id":157573,"depth":790,"text":157574,"children":158955},[158956,158957,158958,158959,158960,158961],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":39340,"depth":892,"text":6016},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":158964,"issue":935,"date":158965},"coffee-and-code-newsletter-7","2019-09-08T07:00:00.000Z","/newsletter/2019/09/08/coffee-and-code-07",{"title":158784,"description":157930},"newsletter/2019/09/08/coffee-and-code-07","_s8NresiN79Uj-tOkqOKv5F0NHCfq62G1X_eGEcqK-I",{"id":158971,"title":158972,"body":158973,"description":157930,"extension":793,"meta":159209,"navigation":797,"path":159212,"seo":159213,"stem":159214,"__hash__":159215},"content/newsletter/2019/09/15/coffee-and-code-08.md","Coffee & Code Newsletter: #8",{"type":648,"value":158974,"toc":159193},[158975,158977,158979,158982,158985,158996,158998,159001,159006,159019,159028,159031,159034,159043,159046,159056,159058,159060,159062,159064,159094,159096,159112,159114,159130,159132,159141,159143,159152,159154,159170,159172,159180,159185,159187,159189],[651,158976,157930],{},[4542,158978,157511],{"id":157510},[651,158980,158981],{},"I try and keep all things negative off of social media because there is enough of it on there. With that said I have posted some tweets of late that weren't all that positive that had to do with burnout and I just want to thank all of you who reached out. I appreciate all of you so, so much.",[651,158983,158984],{},"I have some exciting news to share with you about Tech Elevator but a little news about what I was up to this week. I was able to finish the rough draft of that huge article I was working on for the Vue community. It has gone through the editing process and now I just need to make some corrections and it should be ready to be published soon so stay on the lookout for that.",[651,158986,158987,158988,158991,158992,664],{},"I did some testing in my office this week with video sets, green screens and I even ran a quick test on ",[812,158989,60837],{"href":157768,"rel":158990},[816],". I think I have a pretty solid setup and I might even try another Twitch stream after this newsletter goes out so if you haven't done so already ",[812,158993,158995],{"href":157768,"rel":158994},[816],"please follow me",[5909,158997,41137],{"id":41664},[651,158999,159000],{},"Next week we welcome over 200 students across 5 locations in 3 states as new cohorts begin. I just want to wish all of our students good luck. It is not easy but if you work hard you will get through it. Now, on to some really exciting news!",[651,159002,159003],{},[660,159004],{"alt":674,"src":159005},"/images/newsletter/2019/09/15/philadelphia.png",[651,159007,159008,159009,159012,159013,159018],{},"This was a huge week at ",[812,159010,41137],{"href":41135,"rel":159011},[816]," where we announced our 6th location would be opening up in Philadelphia. If you want to read more about why we are so excited about this opportunity I would encourage you to ",[812,159014,159017],{"href":159015,"rel":159016},"https://www.linkedin.com/pulse/liberating-potential-birthplace-america-anthony-hughes/",[816],"read this article"," by our CEO, Anthony Hughes. As exciting as expansion is, growth without a mission isn't sustainable.",[651,159020,159021,159022,159027],{},"You will find a lot of coding bootcamps boasting about their numbers without any verified data to back them up. Here at Tech Elevator, we submit all of our results to ",[812,159023,159026],{"href":159024,"rel":159025},"https://cirr.org/",[816],"CIRR",", The Council on Integrity in Results Reporting. CIRR is an independent, non-profit organization whose members believe that prospective students should know a school's outcomes before deciding whether to enroll.",[651,159029,159030],{},"CIRR posted our result this week and Tech Elevator is graduating 95% of students across all our campuses and of that 94% are getting placed within 180 days of graduating. The starting salary of students entering our program is $30,000 and the average starting salary when they leave is $58,000. This means that in just 14 weeks students are leaving with double their salary and a career that they are excited about.",[651,159032,159033],{},"This allows all of you to see what I already know, Tech Elevator is all about changing our student's lives. This makes me so happy because not only are we growing but we are doing it the right way and this is something I am incredibly proud of.",[651,159035,159036,159037,159042],{},"So enough bragging, what does this mean for you? First, we are hiring across many of our ",[812,159038,159041],{"href":159039,"rel":159040},"https://www.techelevator.com/join-our-team",[816],"locations for multiple positions",". If you enjoy mentoring and have a real passion for software development you might be a great fit as an instructor. If you would like to hear more about becoming an instructor or have any questions please feel free to contact me.",[651,159044,159045],{},"This also means that if you have any friends or family in the Philadelphia area that are interested in a career in software development you need to tell them about Tech Elevator. Our results speak for themselves, we give you everything you need to start a new career in software development. I truly believe in what we do here and again if you have any questions, please feel free to reach out.",[651,159047,159048],{},[7300,159049,159050,159051,664],{},"I posted this article over on ",[812,159052,159055],{"href":159053,"rel":159054},"https://dev.to/therealdanvega/tech-elevator-launches-in-philidelphia-54eo",[816],"dev.to",[4542,159057,157550],{"id":157549},[4542,159059,157574],{"id":157573},[651,159061,157577],{},[5909,159063,69849],{"id":69848},[5316,159065,159066,159073,159080,159087],{},[5332,159067,159068],{},[812,159069,159072],{"href":159070,"rel":159071},"https://www.freecodecamp.org/news/coding-bootcamp-handbook/",[816],"The Coding Bootcamp Handbook: How Do Bootcamps Work and Are They Right for You?",[5332,159074,159075],{},[812,159076,159079],{"href":159077,"rel":159078},"https://dev.to/zooly/fast-static-site-with-gridsome-and-bulma-in-5-minutes-4md9",[816],"Fast Static Site with Gridsome and Bulma in 5 minutes",[5332,159081,159082],{},[812,159083,159086],{"href":159084,"rel":159085},"https://dev.to/lampewebdev/css-quickies-css-variables-or-how-you-create-a-white-dark-theme-easily-1i0i",[816],"CSS Quickies: CSS Variables - Or how you create a 🌞white/🌑dark theme easily",[5332,159088,159089],{},[812,159090,159093],{"href":159091,"rel":159092},"https://scotch.io/tutorials/using-parcel-in-a-vuejs-app",[816],"Using Parcel in a Vue.js App",[5909,159095,120990],{"id":157591},[5316,159097,159098,159105],{},[5332,159099,159100],{},[812,159101,159104],{"href":159102,"rel":159103},"https://www.freecodecamp.org/news/spring-boot-tutorial/",[816],"Spring Boot in 2 hours",[5332,159106,159107],{},[812,159108,159111],{"href":159109,"rel":159110},"https://support.ecamm.com/en/articles/3323109-discover-ecamm-live-all-tutorial-videos",[816],"Ecamm Live Tutorials",[5909,159113,26378],{"id":39439},[5316,159115,159116,159123],{},[5332,159117,159118],{},[812,159119,159122],{"href":159120,"rel":159121},"https://syntax.fm/show/178/how-we-record-edit-and-host-our-courses",[816],"Syntax.fm: How We Record, Edit, and Host Our Courses",[5332,159124,159125],{},[812,159126,159129],{"href":159127,"rel":159128},"https://nofluffjuststuff.com/podcast/1/mini_episode_the_state_of_java_in_2019",[816],"Mini-Episode: The state of Java in 2019",[5909,159131,157635],{"id":133422},[5316,159133,159134],{},[5332,159135,159136],{},[812,159137,159140],{"href":159138,"rel":159139},"https://github.com/denysdovhan/wtfjs",[816],"wtfjs",[5909,159142,21973],{"id":21972},[5316,159144,159145],{},[5332,159146,159147],{},[812,159148,159151],{"href":159149,"rel":159150},"https://dev.to/pluralsight/vue-getting-started-3cf0",[816],"Vue: Getting Started by John Papa",[5909,159153,6016],{"id":39340},[5316,159155,159156,159163],{},[5332,159157,159158],{},[812,159159,159162],{"href":159160,"rel":159161},"https://leanpub.com/testingvuejscomponentswithjest",[816],"Testing Vue.js components with Jest",[5332,159164,159165],{},[812,159166,159169],{"href":159167,"rel":159168},"https://github.com/getify/You-Dont-Know-JS",[816],"You Don't Know JS",[5909,159171,157692],{"id":157691},[651,159173,159174,159175,159179],{},"I have known ",[812,159176,159178],{"href":70005,"rel":159177},[816],"Todd"," for a long time and he is just one of those great people you come across in life. In a former life, we were both ColdFusion developers living around Cleveland until he decided to move out to Georgia. Todd is now a developer advocate for Oracle and he builds some pretty cool things. On top of that, he's a great family man and I am lucky to call him a friend.",[651,159181,159182],{},[812,159183,70005],{"href":70005,"rel":159184},[816],[4542,159186,157704],{"id":157703},[651,159188,157910],{},[651,159190,41105,159191,69920],{},[41107,159192],{},{"title":674,"searchDepth":790,"depth":790,"links":159194},[159195,159198,159199,159208],{"id":157510,"depth":790,"text":157511,"children":159196},[159197],{"id":41664,"depth":892,"text":41137},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":159200},[159201,159202,159203,159204,159205,159206,159207],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":39340,"depth":892,"text":6016},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":159210,"issue":944,"date":159211},"coffee-and-code-newsletter-8","2019-09-15T07:00:00.000Z","/newsletter/2019/09/15/coffee-and-code-08",{"title":158972,"description":157930},"newsletter/2019/09/15/coffee-and-code-08","M2S5GtCmOCoigh2nz6GqYI5B3Icj4tFF69EiYT0MJVY",{"id":159217,"title":159218,"body":159219,"description":157930,"extension":793,"meta":159534,"navigation":797,"path":159537,"seo":159538,"stem":159539,"__hash__":159540},"content/newsletter/2019/09/22/coffee-and-code-09.md","Coffee & Code Newsletter: #9",{"type":648,"value":159220,"toc":159519},[159221,159223,159225,159239,159241,159249,159258,159394,159397,159399,159401,159403,159432,159434,159450,159452,159461,159463,159478,159480,159482,159491,159493,159500,159503,159508,159510,159512,159516],[651,159222,157930],{},[4542,159224,157511],{"id":157510},[651,159226,159227,159228,159233,159234,159238],{},"My focus this week was spent learning how to create and produce better videos. This will help me when creating course content but my main focus for the rest of this year and into 2020 is YouTube. I finished reading the book ",[812,159229,159232],{"href":159230,"rel":159231},"https://amzn.to/30cOJnR",[816],"YouTube Secrets"," this week and I really enjoyed it. I appreciate books that give you tangible action items and this book was packed full of them. I have a video coming out this week so make sure you ",[812,159235,159237],{"href":22072,"rel":159236},[816],"give me a follow on YouTube"," and let me know what you think about it.",[5909,159240,43848],{"id":43847},[651,159242,159243,159244,159248],{},"If you aren't aware, this website is built on a framework called ",[812,159245,43848],{"href":159246,"rel":159247},"https://gridsome.org",[816],". This week Gridsome had a couple of really big things happen. First, the framework turned one on September 16 so if you have a chance send them some Birthday wishes.",[651,159250,159251,159252,159257],{},"The other big news of the week was the ",[812,159253,159256],{"href":159254,"rel":159255},"https://gridsome.org/blog/2019/09/17/gridsome-v07/",[816],"release of version 0.7",". There were a lot of really great features in this release but none bigger than the ability to use components in Markdown. This will allow us to create custom components that can be used in your markdown blog posts.",[669,159259,159262],{"className":159260,"code":159261,"language":793,"meta":674,"style":674},"language-md shiki shiki-themes github-light github-dark github-light","---\ntitle: A cool title\nexcerpt: A cool description\n---\n\n// Import any Vue Component. Even other .md files!\nimport YouTube from '~/components/YouTube.vue'\nimport AboutUs from '~/sections/AboutUs.md'\n\n// Import any JSON if you need data.\nimport data from '~/data/youtube.json'\n\n// Use front-matter fields anywhere.\n\n# {{ $frontmatter.title }}\n\n> {{ $frontmatter.excerpt }}\n\n// Use your imported Vue Components.\n\u003CYouTube :id=\"data.id\" />\n\u003CAboutUs />\n\nIsn't it great? 🥳\n",[676,159263,159264,159268,159277,159286,159290,159294,159299,159304,159309,159313,159318,159323,159327,159332,159336,159342,159346,159351,159355,159360,159376,159385,159389],{"__ignoreMap":674},[679,159265,159266],{"class":681,"line":682},[679,159267,31635],{"class":693},[679,159269,159270,159272,159274],{"class":681,"line":790},[679,159271,11750],{"class":4508},[679,159273,4282],{"class":693},[679,159275,159276],{"class":689},"A cool title\n",[679,159278,159279,159281,159283],{"class":681,"line":892},[679,159280,55475],{"class":4508},[679,159282,4282],{"class":693},[679,159284,159285],{"class":689},"A cool description\n",[679,159287,159288],{"class":681,"line":901},[679,159289,31635],{"class":693},[679,159291,159292],{"class":681,"line":909},[679,159293,889],{"emptyLinePlaceholder":797},[679,159295,159296],{"class":681,"line":918},[679,159297,159298],{"class":693},"// Import any Vue Component. Even other .md files!\n",[679,159300,159301],{"class":681,"line":935},[679,159302,159303],{"class":693},"import YouTube from '~/components/YouTube.vue'\n",[679,159305,159306],{"class":681,"line":944},[679,159307,159308],{"class":693},"import AboutUs from '~/sections/AboutUs.md'\n",[679,159310,159311],{"class":681,"line":959},[679,159312,889],{"emptyLinePlaceholder":797},[679,159314,159315],{"class":681,"line":964},[679,159316,159317],{"class":693},"// Import any JSON if you need data.\n",[679,159319,159320],{"class":681,"line":977},[679,159321,159322],{"class":693},"import data from '~/data/youtube.json'\n",[679,159324,159325],{"class":681,"line":982},[679,159326,889],{"emptyLinePlaceholder":797},[679,159328,159329],{"class":681,"line":988},[679,159330,159331],{"class":693},"// Use front-matter fields anywhere.\n",[679,159333,159334],{"class":681,"line":993},[679,159335,889],{"emptyLinePlaceholder":797},[679,159337,159338],{"class":681,"line":2129},[679,159339,159341],{"class":159340},"sfWZ8","# {{ $frontmatter.title }}\n",[679,159343,159344],{"class":681,"line":2140},[679,159345,889],{"emptyLinePlaceholder":797},[679,159347,159348],{"class":681,"line":2145},[679,159349,159350],{"class":4508},"> {{ $frontmatter.excerpt }}\n",[679,159352,159353],{"class":681,"line":2154},[679,159354,889],{"emptyLinePlaceholder":797},[679,159356,159357],{"class":681,"line":2159},[679,159358,159359],{"class":693},"// Use your imported Vue Components.\n",[679,159361,159362,159364,159366,159369,159371,159374],{"class":681,"line":2164},[679,159363,4505],{"class":693},[679,159365,15432],{"class":4508},[679,159367,159368],{"class":880}," :id",[679,159370,686],{"class":693},[679,159372,159373],{"class":689},"\"data.id\"",[679,159375,5387],{"class":693},[679,159377,159378,159380,159383],{"class":681,"line":3134},[679,159379,4505],{"class":693},[679,159381,159382],{"class":4508},"AboutUs",[679,159384,5387],{"class":693},[679,159386,159387],{"class":681,"line":3139},[679,159388,889],{"emptyLinePlaceholder":797},[679,159390,159391],{"class":681,"line":3144},[679,159392,159393],{"class":693},"Isn't it great? 🥳\n",[651,159395,159396],{},"This is going to open up a whole lot of possibilities and allow me to do a few things on the blog that I have wanted to do since I launched it. If anyone wants to see some blog posts or videos around this let me know.",[4542,159398,157574],{"id":157573},[651,159400,157577],{},[5909,159402,69849],{"id":69848},[5316,159404,159405,159412,159419,159425],{},[5332,159406,159407],{},[812,159408,159411],{"href":159409,"rel":159410},"https://medium.com/darklang/unveiling-dark-e0be6f1e0b06",[816],"Unveiling Dark",[5332,159413,159414],{},[812,159415,159418],{"href":159416,"rel":159417},"https://devblogs.microsoft.com/visualstudio/java-on-visual-studio-code-september-update/",[816],"Java on Visual Studio Code September Update",[5332,159420,159421],{},[812,159422,159423],{"href":159423,"rel":159424},"https://blog.pocketcasts.com/major-new-update/",[816],[5332,159426,159427],{},[812,159428,159431],{"href":159429,"rel":159430},"https://devblogs.microsoft.com/commandline/cascadia-code/",[816],"Cascadia Code",[5909,159433,120990],{"id":157591},[5316,159435,159436,159443],{},[5332,159437,159438],{},[812,159439,159442],{"href":159440,"rel":159441},"https://www.youtube.com/watch?v=HjlqoCNHGqc",[816],"Fighting Diabetes with Technology: Oracle Code One Keynote",[5332,159444,159445],{},[812,159446,159449],{"href":159447,"rel":159448},"https://www.youtube.com/watch?v=xlTBof3P4Xc",[816],"Java Language Futures: 2019 Edition",[5909,159451,26378],{"id":39439},[5316,159453,159454],{},[5332,159455,159456],{},[812,159457,159460],{"href":159458,"rel":159459},"https://megaphone.link/LMM4290950291",[816],"Money Lab: Share The YouTube Experiment Worked And Made $12K+",[5909,159462,157635],{"id":133422},[5316,159464,159465,159472],{},[5332,159466,159467],{},[812,159468,159471],{"href":159469,"rel":159470},"https://svgporn.com",[816],"SVG Porn",[5332,159473,159474],{},[812,159475,159477],{"href":158807,"rel":159476},[816],"Hacktoberfest 2019",[5909,159479,21973],{"id":21972},[5909,159481,79609],{"id":79608},[5316,159483,159484],{},[5332,159485,159486],{},[812,159487,159490],{"href":159488,"rel":159489},"https://www.youtube.com/channel/UCdDhYMT2USoLdh4SZIsu_1g/videos",[816],"Oracle Code One (YouTube Channel)",[5909,159492,157692],{"id":157691},[651,159494,159174,159495,159499],{},[812,159496,159498],{"href":19034,"rel":159497},[816],"Ray"," for most of my development career and have always looked up to him as a mentor. He inspired me to start a blog I owe him a lot for his ColdFusion blogging platform. On top of being an awesome developer, he's even a better guy to know and I am lucky to call him a friend.",[651,159501,159502],{},"These days Ray blogs a lot about JavaScript and VueJS. I really enjoy following what Ray is up to and you will to.",[651,159504,159505],{},[812,159506,158290],{"href":158290,"rel":159507},[816],[4542,159509,157704],{"id":157703},[651,159511,157910],{},[651,159513,41105,159514,69920],{},[41107,159515],{},[786,159517,159518],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .sfWZ8, html code.shiki .sfWZ8{--shiki-default:#005CC5;--shiki-default-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold;--shiki-sepia:#005CC5;--shiki-sepia-font-weight:bold}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":159520},[159521,159524,159533],{"id":157510,"depth":790,"text":157511,"children":159522},[159523],{"id":43847,"depth":892,"text":43848},{"id":157573,"depth":790,"text":157574,"children":159525},[159526,159527,159528,159529,159530,159531,159532],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":79608,"depth":892,"text":79609},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":159535,"issue":959,"date":159536},"coffee-and-code-newsletter-9","2019-09-22T07:00:00.000Z","/newsletter/2019/09/22/coffee-and-code-09",{"title":159218,"description":157930},"newsletter/2019/09/22/coffee-and-code-09","l7w0I64wUAiixoRhS6-sWa4FgSr8jMv_LdLULxh42xI",{"id":159542,"title":159543,"body":159544,"description":157930,"extension":793,"meta":159853,"navigation":797,"path":159856,"seo":159857,"stem":159858,"__hash__":159859},"content/newsletter/2019/09/29/coffee-and-code-10.md","Coffee & Code Newsletter: #10",{"type":648,"value":159545,"toc":159835},[159546,159548,159550,159553,159557,159565,159571,159574,159579,159584,159588,159597,159603,159607,159610,159614,159623,159628,159633,159636,159642,159645,159648,159655,159657,159664,159672,159674,159676,159678,159715,159717,159747,159749,159765,159767,159776,159778,159787,159789,159798,159800,159816,159818,159821,159827,159829,159831],[651,159547,157930],{},[4542,159549,157511],{"id":157510},[651,159551,159552],{},"This has been an amazing week for me and I have a lot of great news to share with you so let's get straight to it.",[5909,159554,159556],{"id":159555},"vue-mastery","Vue Mastery",[651,159558,159559,159560,159564],{},"I have been dropping some hints to you guys that I was working on a big article and I am happy to announce that it has been released. The first article in a 3 part series has been released on Vue Mastery titled ",[812,159561,159563],{"href":69321,"rel":159562},[816],"\"A beginner's guide: Unit Testing in Vue\"",". I really want to thank Adam Jhar and the team over at Vue Mastery for this opportunity to get my writing in front of such a great audience.",[651,159566,159567],{},[660,159568],{"alt":159569,"src":159570},"Vue Mastery Unit Testing for beginners","/images/newsletter/2019/09/29/vue-mastery-cover.jpeg",[651,159572,159573],{},"If this wasn't exciting enough I got mentioned in a tweet by Vue Mastery and VueJS!",[651,159575,159576],{},[812,159577,69330],{"href":69330,"rel":159578},[816],[651,159580,159581],{},[812,159582,69336],{"href":69336,"rel":159583},[816],[5909,159585,159587],{"id":159586},"code-mash-2020","Code Mash 2020",[651,159589,159590,159591,159596],{},"I am excited to announce that I have been accepted to speak at ",[812,159592,159595],{"href":159593,"rel":159594},"http://www.codemash.org/",[816],"CodeMash 2020",". This conference takes place on January 7-10 2020 about an hour away from me in Sandusky OH. I will be talking about all of the awesome new features in Vue 3. If you're going to be there please let me know and let's hang out!",[651,159598,159599],{},[660,159600],{"alt":159601,"src":159602},"Code Mash","/images/newsletter/2019/09/29/codemash.png",[5909,159604,159606],{"id":159605},"youtube-videos","YouTube Videos",[651,159608,159609],{},"In the last newsletter, I told you I was working hard on producing higher quality and more consistent videos for YouTube. This week was the first step in that process as I released 2 new videos. I know that If I am going to meet my goals on YouTube the first step in that process is to be consistent. I don't have an idea of what my bandwidth is going to be but I think for now my goal is to upload at least 1 video per week. I like the idea of a tutorial and some type of discussion or product review.",[651,159611,159612],{},[2939,159613,67819],{},[651,159615,159616,159617,159622],{},"This week I ",[812,159618,159621],{"href":159619,"rel":159620},"https://www.youtube.com/watch?v=ZopBBEs9TPg",[816],"released a tutorial"," on how to create CSS Grid Layouts using an awesome tool called the CSS Grid Generator.",[651,159624,159625],{},[660,159626],{"alt":67819,"src":159627},"/images/newsletter/2019/09/29/css-grid-generator.png",[651,159629,159630],{},[2939,159631,159632],{},"Learn to code: One language at a time",[651,159634,159635],{},"As I was working on few different projects I put out the following tweet earlier this week.",[651,159637,159638],{},[812,159639,159640],{"href":159640,"rel":159641},"https://twitter.com/therealdanvega/status/1176882121060880384",[816],[651,159643,159644],{},"This tweet seems to really hit home for a bunch of people so I decided to create a video on the same topic. What I hoped to convey in this message is that it is overwhelming to learn a single programming language. It's even harder when you start thinking to yourself how am I ever going to learn all of these languages and frameworks that people are telling me I need to know.",[651,159646,159647],{},"The simple truth is you're never going to learn everything and the sooner you can accept that the better. You can though, over time understand that the core concepts you learn of one programming language can carry over to the next. I have been doing this for 20 years now so my advice is to stay patient, keep learning and the rest will come.",[651,159649,159650],{},[812,159651,159654],{"href":159652,"rel":159653},"https://www.youtube.com/watch?v=AkXVA6ukdcc",[816],"Learn to code one language at a time",[4542,159656,157550],{"id":157549},[651,159658,159659,159660,159663],{},"I have a new tutorial coming this week on YouTube that covers the Vue CLI 3. In this tutorial I walk you through how to install the Vue CLI and how to use a few of the commands. One command I don't see being mentioned often is ",[676,159661,159662],{},"vue serve"," which allows you to serve a single file component in development mode.",[651,159665,159666,159667,159671],{},"If you aren't already ",[812,159668,159670],{"href":22072,"rel":159669},[816],"subscribed"," do so now and be on the lookout for that one this week!",[4542,159673,157574],{"id":157573},[651,159675,157577],{},[5909,159677,69849],{"id":69848},[5316,159679,159680,159687,159694,159701,159708],{},[5332,159681,159682],{},[812,159683,159686],{"href":159684,"rel":159685},"https://dev.to/remotesynth/what-s-wrong-with-the-tech-interview-process-3b3m",[816],"What's Wrong with the Tech Interview Process?",[5332,159688,159689],{},[812,159690,159693],{"href":159691,"rel":159692},"https://devblogs.microsoft.com/dotnet/announcing-net-core-3-0/",[816],"Announcing .NET Core 3.0",[5332,159695,159696],{},[812,159697,159700],{"href":159698,"rel":159699},"https://dev.to/voluntadpear/comparing-react-hooks-with-vue-composition-api-4b32",[816],"Comparing React Hooks with Vue Composition API",[5332,159702,159703],{},[812,159704,159707],{"href":159705,"rel":159706},"https://dev.to/devteam/you-can-now-generate-self-hostable-static-blogs-right-from-your-dev-content-via-stackbit-7a5",[816],"You can now generate self-hostable static blogs right from your DEV content via Stackbit",[5332,159709,159710],{},[812,159711,159714],{"href":159712,"rel":159713},"https://www.wired.com/story/want-free-coding-lessons-twitch-real-time/",[816],"Want Free Coding Lessons? Twitch Makes It Happen in Real Time",[5909,159716,120990],{"id":157591},[5316,159718,159719,159726,159733,159740],{},[5332,159720,159721],{},[812,159722,159725],{"href":159723,"rel":159724},"https://dotnet.microsoft.com/learn/videos",[816],".NET Videos",[5332,159727,159728],{},[812,159729,159732],{"href":159730,"rel":159731},"https://www.youtube.com/watch?v=yefmcX57Eyg",[816],"IntelliJ IDEA for beginners",[5332,159734,159735],{},[812,159736,159739],{"href":159737,"rel":159738},"https://www.youtube.com/watch?v=YS4e4q9oBaU",[816],"freeCodeCamp: Learn Go Programming - Golang Tutorial for Beginners",[5332,159741,159742],{},[812,159743,159746],{"href":159744,"rel":159745},"https://www.youtube.com/watch?v=e-ThzHtBnys&list=PLFZAa7EupbB61QMSYFB8YX9qQUKnWxu_M",[816],"Gridsome Remark",[5909,159748,26378],{"id":39439},[5316,159750,159751,159758],{},[5332,159752,159753],{},[812,159754,159757],{"href":159755,"rel":159756},"https://ladybug.dev/episode/teaching-code/",[816],"Ladybug Podcast: How To Teach Code",[5332,159759,159760],{},[812,159761,159764],{"href":159762,"rel":159763},"https://shoptalkshow.com/episodes/379/",[816],"Shop Talk Show: Making money on the web",[5909,159766,157635],{"id":133422},[5316,159768,159769],{},[5332,159770,159771],{},[812,159772,159775],{"href":159773,"rel":159774},"https://github.com/microsoft/cascadia-code",[816],"Cascadia Code Open Source Font by Microsoft",[651,159777,6016],{},[5316,159779,159780],{},[5332,159781,159782],{},[812,159783,159786],{"href":159784,"rel":159785},"https://leanpub.com/GoNotebook",[816],"A Go Developer's Notebook",[5909,159788,21973],{"id":21972},[5316,159790,159791],{},[5332,159792,159793],{},[812,159794,159797],{"href":159795,"rel":159796},"https://www.freecodecamp.org/news/learn-data-structures-from-a-google-engineer/",[816],"Learn Data Structures from a Google Engineer",[5909,159799,79609],{"id":79608},[5316,159801,159802,159809],{},[5332,159803,159804],{},[812,159805,159808],{"href":159806,"rel":159807},"https://youtu.be/W8yL8vRnUnA",[816],".NET Conf 2019 (Day 1)",[5332,159810,159811],{},[812,159812,159815],{"href":159813,"rel":159814},"https://www.youtube.com/watch?v=Dd37HBvfnRk",[816],".NET Conf 2019 (Day 2)",[5909,159817,157692],{"id":157691},[651,159819,159820],{},"My person to follow this week is my friend Mark Shust. Mark lives in Cleveland near me and is an online instructor focusing on PHP & Magento. If you are into either of those technologies Mark is a great instructor and a must-follow!",[651,159822,159823],{},[812,159824,159825],{"href":159825,"rel":159826},"https://twitter.com/markshust",[816],[4542,159828,157704],{"id":157703},[651,159830,157910],{},[651,159832,41105,159833,69920],{},[41107,159834],{},{"title":674,"searchDepth":790,"depth":790,"links":159836},[159837,159842,159843,159852],{"id":157510,"depth":790,"text":157511,"children":159838},[159839,159840,159841],{"id":159555,"depth":892,"text":159556},{"id":159586,"depth":892,"text":159587},{"id":159605,"depth":892,"text":159606},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":159844},[159845,159846,159847,159848,159849,159850,159851],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":79608,"depth":892,"text":79609},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":159854,"issue":964,"date":159855},"coffee-and-code-newsletter-10","2019-09-29T07:00:00.000Z","/newsletter/2019/09/29/coffee-and-code-10",{"title":159543,"description":157930},"newsletter/2019/09/29/coffee-and-code-10","vB8SRanjZSBm9FPs_Kdtiqeo0APahNIked0JCaNmbrE",{"id":159861,"title":159862,"body":159863,"description":157930,"extension":793,"meta":160062,"navigation":797,"path":160065,"seo":160066,"stem":160067,"__hash__":160068},"content/newsletter/2019/10/06/cofee-and-code-11.md","Coffee & Code Newsletter: #11",{"type":648,"value":159864,"toc":160046},[159865,159867,159869,159872,159878,159884,159888,159891,159896,159898,159900,159902,159904,159941,159943,159966,159968,159977,159979,160002,160004,160017,160021,160030,160032,160038,160040,160042],[651,159866,157930],{},[4542,159868,157511],{"id":157510},[651,159870,159871],{},"I created two new videos this week for YouTube and I had a lot of fun putting them together. I still don't know what I am doing on YouTube but what I realized is I am not going to figure it out by sitting around and doing nothing. This is why I have been mixing up with tutorials and discussions and I would love to hear your feedback.",[651,159873,159874],{},[812,159875,159876],{"href":159876,"rel":159877},"https://www.youtube.com/watch?v=a3TISMIWzP8",[816],[651,159879,159880],{},[812,159881,159882],{"href":159882,"rel":159883},"https://www.youtube.com/watch?v=eXJ9yKa-aJ0",[816],[5909,159885,159887],{"id":159886},"vue-3","Vue 3",[651,159889,159890],{},"Probably the best news of the week was when Even You dropped the bomb at Vue London dropped Vue 3 alpha. If you haven't had a chance you can download Vue 3 and start hacking away right now!",[651,159892,159893],{},[660,159894],{"alt":674,"src":159895},"/images/newsletter/2019/10/06/evan-you-tweet.png",[4542,159897,157550],{"id":157549},[4542,159899,157574],{"id":157573},[651,159901,157577],{},[5909,159903,69849],{"id":69848},[5316,159905,159906,159913,159920,159927,159934],{},[5332,159907,159908],{},[812,159909,159912],{"href":159910,"rel":159911},"https://dev.to/rubiin/vim-quickies-cheatsheet-30gm",[816],"Vim quickies / cheatsheet",[5332,159914,159915],{},[812,159916,159919],{"href":159917,"rel":159918},"https://dev.to/simonholdorf/10-tips-tricks-to-make-you-a-better-vuejs-developer-4n4d",[816],"10 Tips & Tricks to make you a better VueJS Developer",[5332,159921,159922],{},[812,159923,159926],{"href":159924,"rel":159925},"https://dev.to/bnb/an-unintentionally-comprehensive-introduction-to-github-actions-ci-blm",[816],"An Unintentionally Comprehensive Introduction to GitHub Actions CI",[5332,159928,159929],{},[812,159930,159933],{"href":159931,"rel":159932},"https://dev.to/raymondcamden/using-oauth-and-vue-js-to-build-an-untappd-stats-page-ok6",[816],"Using OAuth and Vue.js to Build an Untappd Stats Page",[5332,159935,159936],{},[812,159937,159940],{"href":159938,"rel":159939},"https://michaelnthiessen.com/levels-of-vue-scope",[816],"4 Levels of Vue Scope",[5909,159942,120990],{"id":157591},[5316,159944,159945,159952,159959],{},[5332,159946,159947],{},[812,159948,159951],{"href":159949,"rel":159950},"https://www.youtube.com/watch?v=PE1rGl1HKr4",[816],"Create a Personal Blog with Nuxt.js & Headless CMS (Ghost) - 1 of 2",[5332,159953,159954],{},[812,159955,159958],{"href":159956,"rel":159957},"https://www.youtube.com/watch?v=T4qLTXGvJ7k",[816],"Create a Personal Blog with Nuxt.js & Headless CMS (Ghost) - 2 of 2",[5332,159960,159961],{},[812,159962,159965],{"href":159963,"rel":159964},"https://www.freecodecamp.org/news/how-to-build-and-deploy-websites-using-netlify-a-comprehensive-tutorial/",[816],"How to build and deploy websites using Netlify - a comprehensive tutorial",[5909,159967,26378],{"id":39439},[5316,159969,159970],{},[5332,159971,159972],{},[812,159973,159976],{"href":159974,"rel":159975},"https://news.vuejs.org/issues/159",[816],"Vue News Podcast",[5909,159978,157635],{"id":133422},[5316,159980,159981,159988,159995],{},[5332,159982,159983],{},[812,159984,159987],{"href":159985,"rel":159986},"https://micronaut-projects.github.io/micronaut-data/latest/guide/",[816],"Micronaut Data 1.0.0.M2 Release",[5332,159989,159990],{},[812,159991,159994],{"href":159992,"rel":159993},"https://twitter.com/nuxt_js/status/1178598763331493888",[816],"Nuxt.js Website Updates",[5332,159996,159997],{},[812,159998,160001],{"href":159999,"rel":160000},"https://www.tailwindui.com/",[816],"Tailwind UI Landing Page (Coming Soon!)",[5909,160003,79609],{"id":79608},[5316,160005,160006,160011],{},[5332,160007,160008],{},[812,160009,159595],{"href":159593,"rel":160010},[816],[5332,160012,160013],{},[812,160014,160015],{"href":160015,"rel":160016},"https://us.vuejs.org/call-for-papers",[816],[5909,160018,160020],{"id":160019},"newsletters","Newsletters",[5316,160022,160023],{},[5332,160024,160025],{},[812,160026,160029],{"href":160027,"rel":160028},"https://blog.jetbrains.com/idea/2019/10/java-annotated-monthly-october-2019",[816],"Java Annotated Monthly",[5909,160031,157692],{"id":157691},[651,160033,160034],{},[812,160035,160036],{"href":160036,"rel":160037},"https://twitter.com/MichaelThiessen",[816],[4542,160039,157704],{"id":157703},[651,160041,157910],{},[651,160043,41105,160044,69920],{},[41107,160045],{},{"title":674,"searchDepth":790,"depth":790,"links":160047},[160048,160051,160052,160061],{"id":157510,"depth":790,"text":157511,"children":160049},[160050],{"id":159886,"depth":892,"text":159887},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":160053},[160054,160055,160056,160057,160058,160059,160060],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":79608,"depth":892,"text":79609},{"id":160019,"depth":892,"text":160020},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},{"slug":160063,"issue":977,"date":160064},"coffee-and-code-newsletter-11","2019-10-06T07:00:00.000Z","/newsletter/2019/10/06/cofee-and-code-11",{"title":159862,"description":157930},"newsletter/2019/10/06/cofee-and-code-11","gvMVD46CCKd524FHZ5-txYBxrj0sfXxyGiiEoT5RTBs",{"id":160070,"title":160071,"body":160072,"description":160322,"extension":793,"meta":160323,"navigation":797,"path":160326,"seo":160327,"stem":160328,"__hash__":160329},"content/newsletter/2019/10/13/12.md","Coffee & Code Newsletter: #12",{"type":648,"value":160073,"toc":160309},[160074,160082,160084,160087,160092,160107,160190,160192,160199,160201,160203,160205,160228,160230,160253,160255,160271,160273,160281,160283,160292,160294,160301,160303,160305],[651,160075,160076,160077,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #11 ",[812,160078,18263],{"href":160079,"rel":160080},"https://www.danvega.dev/newsletter/2019/10/06/coffee-and-code-11/",[816],". If you don't already have a cup of coffee grab one now and let's get to it.",[4542,160083,157511],{"id":157510},[651,160085,160086],{},"I don't have anything in the form of new content but I did spend some time cleaning up my office this weekend. I had a lot of people asking me about my gear so I thought I would share it with you. This is an image of my office and I will walk through some of my gear below.",[651,160088,160089],{},[660,160090],{"alt":674,"src":160091},"/images/newsletter/2019/10/13/office.jpeg",[651,160093,160094,160095,160100,160101,160106],{},"I have been searching for a good mount to go behind my monitor that I could put my DSLR on and I finally found one. ",[812,160096,160099],{"href":160097,"rel":160098},"https://amzn.to/2M8CfVU",[816],"Elgato came out with a Multi-Mount"," which clamps right on my desk and at the minimum height still fits over my widescreen monitor. It also extends higher so If I want to stand up and do some talking head videos I can. I also have the ",[812,160102,160105],{"href":160103,"rel":160104},"https://amzn.to/35tGVx0",[816],"green screen from Elgato"," and I just love everything they release!",[5316,160108,160109,160120,160126,160160,160166,160172,160178,160184],{},[5332,160110,160111,160112],{},"MacBook Pro (Retina, 15-inch, Mid 2015)\n",[5316,160113,160114,160117],{},[5332,160115,160116],{},"2.2 GHz Quad-Core Intel Core i7",[5332,160118,160119],{},"16 GB DDR3",[5332,160121,160122],{},[812,160123,160125],{"href":160103,"rel":160124},[816],"Bestand Laptop Stand",[5332,160127,160128,160132],{},[812,160129,160131],{"href":160103,"rel":160130},[816],"Canon 70D DSLR Camera",[5316,160133,160134,160141,160147,160154],{},[5332,160135,160136],{},[812,160137,160140],{"href":160138,"rel":160139},"https://amzn.to/2ozWliE",[816],"Canon EF-s 24mm f/2.8 STM Lens",[5332,160142,160143],{},[812,160144,160146],{"href":160138,"rel":160145},[816],"ACK-E6 Camera AC Power Adapter Charging Kit",[5332,160148,160149],{},[812,160150,160153],{"href":160151,"rel":160152},"https://amzn.to/33rhLO0",[816],"Wireless Remote Control",[5332,160155,160156],{},[812,160157,160159],{"href":160151,"rel":160158},[816],"Lexar 128GB SD Card",[5332,160161,160162],{},[812,160163,160165],{"href":160097,"rel":160164},[816],"Audio-Technica AT2020USB+ Microphone",[5332,160167,160168],{},[812,160169,160171],{"href":160097,"rel":160170},[816],"Dell UltraSharp U3415W Curved Monitor",[5332,160173,160174],{},[812,160175,160177],{"href":160097,"rel":160176},[816],"Amazon Echo - Black (1st Generator)",[5332,160179,160180],{},[812,160181,160183],{"href":160097,"rel":160182},[816],"Logitech K750 Wireless Keyboard",[5332,160185,160186],{},[812,160187,160189],{"href":160151,"rel":160188},[816],"2\"x12\"x12\" Soundproof Foam Wedge Tiles",[4542,160191,157550],{"id":157549},[651,160193,160194,160195,160198],{},"Please update your address books. I am moving this newsletter's from address to ",[676,160196,160197],{},"newsletter@danvega.dev"," next week.",[4542,160200,157574],{"id":157573},[651,160202,157577],{},[5909,160204,69849],{"id":69848},[5316,160206,160207,160214,160221],{},[5332,160208,160209],{},[812,160210,160213],{"href":160211,"rel":160212},"https://dev.to/jennapederson/building-a-playground-with-nuxt-and-markdown-4c5e",[816],"Building A Playground with Nuxt and Markdown",[5332,160215,160216],{},[812,160217,160220],{"href":160218,"rel":160219},"https://dev.to/simonholdorf/9-projects-you-can-do-to-become-a-frontend-master-in-2020-n2h",[816],"9 Projects you can do to become a Frontend Master in 2020",[5332,160222,160223],{},[812,160224,160227],{"href":160225,"rel":160226},"https://dev.to/simonholdorf/10-vue-directives-that-make-your-dev-lives-easier-5dm7",[816],"10 Vue Directives that make your dev lives easier!",[5909,160229,120990],{"id":157591},[5316,160231,160232,160239,160246],{},[5332,160233,160234],{},[812,160235,160238],{"href":160236,"rel":160237},"https://www.youtube.com/watch?v=6HUjDKVn0e0&list=WL&index=2&t=2s",[816],"Why the Vue 3 Composition API?",[5332,160240,160241],{},[812,160242,160245],{"href":160243,"rel":160244},"https://www.youtube.com/watch?v=ty49_v1tV44",[816],"Go Tutorial Basic | Golang",[5332,160247,160248],{},[812,160249,160252],{"href":160250,"rel":160251},"https://www.youtube.com/watch?v=4deVCNJq3qc",[816],"Learn Vue.js - Full Course for Beginners - 2019",[5909,160254,26378],{"id":39439},[5316,160256,160257,160264],{},[5332,160258,160259],{},[812,160260,160263],{"href":160261,"rel":160262},"https://devchat.tv/views-on-vue/vov-082-developer-tooling-and-dev-setup-for-working-with-vue/",[816],"Views on Vue 082: Developer Tooling and Dev Setup for Working With Vue",[5332,160265,160266],{},[812,160267,160270],{"href":160268,"rel":160269},"http://www.fullstackradio.com/125",[816],"Full Stack Radio: 125: Rob Walling - Choosing the Right Product Idea",[5909,160272,157635],{"id":133422},[5316,160274,160275],{},[5332,160276,160277],{},[812,160278,160280],{"href":69867,"rel":160279},[816],"Vue 3 (Pre-Alpha Release)",[5909,160282,21973],{"id":21972},[5316,160284,160285],{},[5332,160286,160287],{},[812,160288,160291],{"href":160289,"rel":160290},"https://www.vuemastery.com/courses/vue-3-essentials/why-the-composition-api",[816],"Vue 3 Essentials",[5909,160293,157692],{"id":157691},[651,160295,160296,160297,160300],{},"My must follow of the week is ",[812,160298,57159],{"href":57157,"rel":160299},[816],". Ben is all over the place these days and is a huge contributor to the Vue community. Ben is a core team member of VuePress and you will hear him on a couple of podcasts that deal with Vue.",[4542,160302,157704],{"id":157703},[651,160304,157910],{},[651,160306,41105,160307,69920],{},[41107,160308],{},{"title":674,"searchDepth":790,"depth":790,"links":160310},[160311,160312,160313,160321],{"id":157510,"depth":790,"text":157511},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":160314},[160315,160316,160317,160318,160319,160320],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #11 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":160324,"issue":982,"date":160325},"coffee-and-code-newsletter-12","2019-10-13T07:00:00.000Z","/newsletter/2019/10/13/12",{"title":160071,"description":160322},"newsletter/2019/10/13/12","tu8wqmkiUpRGfD3k6nfqX7e8tCaQjK09P8rG0iAv2t0",{"id":160331,"title":160332,"body":160333,"description":160592,"extension":793,"meta":160593,"navigation":797,"path":160596,"seo":160597,"stem":160598,"__hash__":160599},"content/newsletter/2019/10/20/13.md","Coffee & Code Newsletter: #13",{"type":648,"value":160334,"toc":160576},[160335,160342,160344,160347,160356,160362,160365,160376,160390,160394,160403,160417,160419,160421,160423,160453,160455,160485,160487,160503,160505,160514,160516,160546,160548,160557,160559,160562,160568,160570,160572],[651,160336,160337,160338,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #12 ",[812,160339,18263],{"href":160340,"rel":160341},"https://www.danvega.dev/newsletter/coffee-and-code/12",[816],[4542,160343,157511],{"id":157510},[651,160345,160346],{},"If you didn't already know I am the President of a non-profit in Cleveland OH called CLE Cares. Every December we partner with the Marines Toys for Tots Foundation to raise toys for those less fortunate right here in Northeast Ohio. We throw a big event called Toys for Shots and the idea is simple, you bring us a toy and we will give you some drinks.",[651,160348,160349,160350,160355],{},"I was looking at the current website this year and it was getting kind of stale. With not a lot of time on my hands, I decided to purchase a template and use that to update our website. The template was great but it was for a single page site and I really wanted to have multiple pages so even using a template took me some time to strip it down to just want I wanted which was essentially the home page graphics. I have this ",[812,160351,160354],{"href":160352,"rel":160353},"https://toysforshots.netlify.com/",[816],"hosted on Netlify"," and I am still tweaking some things but I would love your feedback.",[651,160357,160358],{},[660,160359],{"alt":160360,"src":160361},"Toys for Shots Redesign","/images/newsletter/2019/10/20/toysforshots.png",[5909,160363,97022],{"id":160364},"vueconf-us",[651,160366,40060,160367,160370,160371,160375],{},[812,160368,97022],{"href":97020,"rel":160369},[816]," call for papers deadline was this week and as always I was a master at procrastination waiting until the very last minute to submit a few talks. If you want to read the full descriptions of my submissions they are available for this conference and every other conference I submit to ",[812,160372,160374],{"href":82767,"rel":160373},[816],"over on my Github",". These are the talks I ended up submitting:",[5316,160377,160378,160381,160384,160387],{},[5332,160379,160380],{},"Vue 3.0 Better, Faster & Stronger",[5332,160382,160383],{},"A beginners guide to testing in Vue.js",[5332,160385,160386],{},"Moving my blog from WordPress to Gridsome",[5332,160388,160389],{},"Serverless and Vue",[5909,160391,160393],{"id":160392},"meetup","Meetup",[651,160395,160396,160397,160402],{},"The other big news of the week was Meetups' ",[812,160398,160401],{"href":160399,"rel":160400},"https://www.meetup.com/lp/paymentchanges?mpId=9038",[816],"announcement of pricing changes",". The proposed change would charge each attendee $2 to RSVP for a meetup or the organizers could pick up that bill. As someone who runs a meetup I had a lot of problems with this and apparently so did everyone else in the community.",[651,160404,160405,160406,160410,160411,160416],{},"It was really great to see freeCodeCamp announce that they were going to build an alternative to Meetup called Chapter. If you want to learn more about this project you can check out their ",[812,160407,82769],{"href":160408,"rel":160409},"https://github.com/freeCodeCamp/chapter",[816],". Finally, if you're looking for alternatives to Meetup I found this ",[812,160412,160415],{"href":160413,"rel":160414},"https://phacks.dev/meetup-com-alternatives",[816],"really great article by Nicolas Goutay"," where he discusses some options that he found.",[4542,160418,157574],{"id":157573},[651,160420,157577],{},[5909,160422,69849],{"id":69848},[5316,160424,160425,160432,160439,160446],{},[5332,160426,160427],{},[812,160428,160431],{"href":160429,"rel":160430},"https://dev.to/azure/too-hard-too-soft-just-right-rendering-html-with-lit-html-1km8",[816],"Too Hard, Too Soft, Just Right - Rendering HTML with lit-html",[5332,160433,160434],{},[812,160435,160438],{"href":160436,"rel":160437},"https://gridsome.org/blog/2019/10/08/infinite-loading-with-gridsome/",[816],"How to integrate Infinite Loading with Gridsome",[5332,160440,160441],{},[812,160442,160445],{"href":160443,"rel":160444},"https://nesslabs.com/focus-on-the-process",[816],"Focus on the process: stop thinking and start doing",[5332,160447,160448],{},[812,160449,160452],{"href":160450,"rel":160451},"https://www.netlify.com/blog/2019/10/16/creating-and-using-your-first-netlify-build-plugin/",[816],"Creating and using your first Netlify Build Plugin",[5909,160454,120990],{"id":157591},[5316,160456,160457,160464,160471,160478],{},[5332,160458,160459],{},[812,160460,160463],{"href":160461,"rel":160462},"https://www.youtube.com/watch?v=wO70Pnv0yeo&",[816],"Opening Keynote (Firebase Summit 2019)",[5332,160465,160466],{},[812,160467,160470],{"href":160468,"rel":160469},"https://www.youtube.com/channel/UCh5UlGiu9d6LegIeUCW4N1w/videos",[816],"Mozilla Developer",[5332,160472,160473],{},[812,160474,160477],{"href":160475,"rel":160476},"https://www.youtube.com/user/goPivotal/videos",[816],"Spring One Conference Recordings",[5332,160479,160480],{},[812,160481,160484],{"href":160482,"rel":160483},"https://www.youtube.com/watch?v=mT5siI19gtc",[816],"Netlify Tutorial - How to build and deploy websites using Netlify",[5909,160486,26378],{"id":39439},[5316,160488,160489,160496],{},[5332,160490,160491],{},[812,160492,160495],{"href":160493,"rel":160494},"https://changelog.com/news/bWR9/visit",[816],"Go Time: Creating the Go programming language",[5332,160497,160498],{},[812,160499,160502],{"href":160500,"rel":160501},"https://changelog.com/news/V2A1/visit",[816],"Go Time: Serverless and Go",[5909,160504,6016],{"id":39340},[5316,160506,160507],{},[5332,160508,160509],{},[812,160510,160513],{"href":160511,"rel":160512},"https://learning.oreilly.com/library/view/the-go-programming/9780134190570/",[816],"The Go Programming Language",[5909,160515,157635],{"id":133422},[5316,160517,160518,160525,160532,160539],{},[5332,160519,160520],{},[812,160521,160524],{"href":160522,"rel":160523},"https://gitsheet.wtf/",[816],"GitSheet",[5332,160526,160527],{},[812,160528,160531],{"href":160529,"rel":160530},"http://mystery.knightlab.com/",[816],"SQL Murder Mystery",[5332,160533,160534],{},[812,160535,160538],{"href":160536,"rel":160537},"https://flickity.metafizzy.co/",[816],"Flickity - Touch, responsive, flickable carousels",[5332,160540,160541],{},[812,160542,160545],{"href":160543,"rel":160544},"https://cloudblogs.microsoft.com/sqlserver/2019/10/17/mssql-extension-for-visual-studio-code-now-has-object-explorer-and-intellicode/",[816],"Mssql extension for Visual Studio Code",[5909,160547,160020],{"id":160019},[5316,160549,160550],{},[5332,160551,160552],{},[812,160553,160556],{"href":160554,"rel":160555},"http://groovycalamari.com/issues/167?#start",[816],"Groovy Calamari",[5909,160558,157692],{"id":157691},[651,160560,160561],{},"I have a been a huge fan of everything Adam Wathan does from his course on Advanced Vue Component Design to his podcast and now his work on Tailwind CSS. Adam is a software developer, entrepreneur, podcaster, educator and so much more. If you're not following Adam over on Twitter he is my must follow of the week.",[651,160563,160564],{},[812,160565,160566],{"href":160566,"rel":160567},"https://twitter.com/adamwathan",[816],[4542,160569,157704],{"id":157703},[651,160571,157910],{},[651,160573,41105,160574,69920],{},[41107,160575],{},{"title":674,"searchDepth":790,"depth":790,"links":160577},[160578,160582,160591],{"id":157510,"depth":790,"text":157511,"children":160579},[160580,160581],{"id":160364,"depth":892,"text":97022},{"id":160392,"depth":892,"text":160393},{"id":157573,"depth":790,"text":157574,"children":160583},[160584,160585,160586,160587,160588,160589,160590],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":39340,"depth":892,"text":6016},{"id":133422,"depth":892,"text":157635},{"id":160019,"depth":892,"text":160020},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #12 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":160594,"issue":988,"date":160595},"coffee-and-code-newsletter-13","2019-10-20T07:00:00.000Z","/newsletter/2019/10/20/13",{"title":160332,"description":160592},"newsletter/2019/10/20/13","t-e98_qxzMXRrN5Oz4Fw6mooFN210svABLWNP8OJLL4",{"id":160601,"title":160602,"body":160603,"description":160798,"extension":793,"meta":160799,"navigation":797,"path":160802,"seo":160803,"stem":160804,"__hash__":160805},"content/newsletter/2019/10/27/14.md","Coffee & Code Newsletter: #14",{"type":648,"value":160604,"toc":160785},[160605,160612,160614,160627,160633,160653,160659,160667,160673,160680,160684,160692,160694,160696,160698,160714,160716,160725,160727,160743,160745,160766,160768,160771,160777,160779,160781],[651,160606,160607,160608,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #13 ",[812,160609,18263],{"href":160610,"rel":160611},"https://www.danvega.dev/newsletter/coffee-and-code/13",[816],[4542,160613,157511],{"id":157510},[651,160615,160616,160617,160621,160622,664],{},"I spent a lot of my free time this week working on my website. One of the first things I did was upgrade ",[812,160618,160620],{"href":159254,"rel":160619},[816],"Gridsome to 0.7.x"," and it's something I have been meaning to get around to for a while. With this upgrade, there were a few things I had to do. The big one was moving any route definitions (which is being deprecated) into a new feature called ",[812,160623,160626],{"href":160624,"rel":160625},"https://gridsome.org/blog/2019/09/17/gridsome-v07/#new-template-configuration",[816],"template configurations",[669,160628,160631],{"className":160629,"code":160630,"language":11464},[16247],"templates: {\n Post: \"/blog/:year/:month/:day/:slug\",\n Tag: \"/tag/:title\",\n Newsletter: \"/newsletter/coffee-and-code/:issue\"\n},\n",[676,160632,160630],{"__ignoreMap":674},[651,160634,160635,160636,160640,160641,160646,160647,160652],{},"With that in place, I wanted to add a ",[812,160637,160639],{"href":125754,"rel":160638},[816],"newsletter landing page"," where I could send people to sign up for the newsletter. I also created a nice ",[812,160642,160645],{"href":160643,"rel":160644},"https://www.danvega.dev/newsletter/thank-you/",[816],"thank you page"," for subscribers after they signed up letting them know that the next issue of the newsletter will come out this Sunday along with some links to the last 3 issues. Finally, I created ",[812,160648,160651],{"href":160649,"rel":160650},"https://www.danvega.dev/newsletter/archives/",[816],"an archives page"," that I don't really link to anywhere but I thought I would give that to all of you in case you wanted it.",[651,160654,160655],{},[660,160656],{"alt":160657,"src":160658},"Newsletter Screenshot","/images/newsletter/2019/10/27/newsletter.png",[651,160660,160661,160662,160666],{},"I also put together a quick landing page that shows off all of my ",[812,160663,160665],{"href":100862,"rel":160664},[816],"current and upcoming courses",". I would like to have individual landing pages for each of my courses but that is going to come with time. I am not the greatest designer but I am happy with it knowing that these are projects I hope to have money to pay a professional to do later down the road.",[651,160668,160669],{},[660,160670],{"alt":160671,"src":160672},"Courses Screenshot","/images/newsletter/2019/10/27/courses.png",[651,160674,160675,160676,160679],{},"If you weren't aware all of the source code for my personal website is up on ",[812,160677,17458],{"href":53665,"rel":160678},[816]," if you're interested in checking it out.",[5909,160681,160683],{"id":160682},"vuetify","Vuetify",[651,160685,160686,160687,160691],{},"I was able to use ",[812,160688,160683],{"href":160689,"rel":160690},"https://vuetifyjs.com/en/",[816]," in a project at work this week and I have to say I was impressed. I am someone who cares about how an application looks but often doesn't have the time or skill to create them. I really appreciate just how easy it is to install, use and customize. The number of components that come out of the box is very impressive and they have pretty much everything you need to build beautiful and functional applications. This is not a paid sponsorship, I am just truly impressed with the project so if you have a chance I would check them out.",[4542,160693,157574],{"id":157573},[651,160695,157577],{},[5909,160697,69849],{"id":69848},[5316,160699,160700,160707],{},[5332,160701,160702],{},[812,160703,160706],{"href":160704,"rel":160705},"https://www.divio.com/blog/documentation/",[816],"What nobody tells you about documentation",[5332,160708,160709],{},[812,160710,160713],{"href":160711,"rel":160712},"https://dev.to/therajsaxena/java-9-to-java-13-top-features-362l",[816],"Java 9 to Java 13 - Top features",[5909,160715,120990],{"id":157591},[5316,160717,160718],{},[5332,160719,160720],{},[812,160721,160724],{"href":160722,"rel":160723},"https://www.youtube.com/watch?v=3hLmDS179YE",[816],"AWS Certified Cloud Practitioner Training 2019 - Full Course",[5909,160726,26378],{"id":39439},[5316,160728,160729,160736],{},[5332,160730,160731],{},[812,160732,160735],{"href":160733,"rel":160734},"https://devchat.tv/views-on-vue/vov-084-nuxt-js-with-sebastien-chopin/",[816],"VoV 084: Nuxt.JS With Sebastien Chopin",[5332,160737,160738],{},[812,160739,160742],{"href":160740,"rel":160741},"https://changelog.com/gotime/104",[816],"Go Time: Building search tools in Go",[5909,160744,157635],{"id":133422},[5316,160746,160747,160752,160759],{},[5332,160748,160749],{},[812,160750,160683],{"href":160689,"rel":160751},[816],[5332,160753,160754],{},[812,160755,160758],{"href":160756,"rel":160757},"https://hacks.mozilla.org/2019/10/firefox-70-a-bountiful-release-for-all/",[816],"Firefox 70",[5332,160760,160761],{},[812,160762,160765],{"href":160763,"rel":160764},"https://www.ecpodcast.io/episodes/16-daniel-vassallo-why-i-quit-a-500k-job-at-amazon-to-work-for-myself",[816],"#16 - Daniel Vassallo - Why I Left a $500k/year Job at AWS to Work For Myself",[5909,160767,157692],{"id":157691},[651,160769,160770],{},"I have been working a lot lately with Adam Jahr of Vue Mastery and it's been an absolute pleasure. When you have a chance to work with people who are passionate about what they do it makes work not feel like work. Adam is a great teacher and what he and his team over at Vue Mastery is doing is pretty darn impressive. Adam is my must follow of the week!",[651,160772,160773],{},[812,160774,160775],{"href":160775,"rel":160776},"https://twitter.com/AdamJahr",[816],[4542,160778,157704],{"id":157703},[651,160780,157910],{},[651,160782,41105,160783,69920],{},[41107,160784],{},{"title":674,"searchDepth":790,"depth":790,"links":160786},[160787,160790,160797],{"id":157510,"depth":790,"text":157511,"children":160788},[160789],{"id":160682,"depth":892,"text":160683},{"id":157573,"depth":790,"text":157574,"children":160791},[160792,160793,160794,160795,160796],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #13 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":160800,"issue":993,"date":160801},"coffee-and-code-newsletter-14","2019-10-27T07:00:00.000Z","/newsletter/2019/10/27/14",{"title":160602,"description":160798},"newsletter/2019/10/27/14","C9opcwfQTf9omOz1i2HEsF9KWDKFIMwfV76Tlw4avLw",{"id":160807,"title":160808,"body":160809,"description":161024,"extension":793,"meta":161025,"navigation":797,"path":161028,"seo":161029,"stem":161030,"__hash__":161031},"content/newsletter/2019/11/03/15.md","Coffee & Code Newsletter: #15",{"type":648,"value":160810,"toc":161010},[160811,160818,160820,160823,160827,160836,160841,160854,160857,160866,160870,160878,160884,160886,160888,160890,160906,160908,160945,160947,160963,160965,160981,160983,160992,160994,161002,161004,161006],[651,160812,160813,160814,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #14 ",[812,160815,18263],{"href":160816,"rel":160817},"https://www.danvega.dev/newsletter/coffee-and-code/14",[816],[4542,160819,157511],{"id":157510},[651,160821,160822],{},"This week I posted a new video on YouTube and the third and final part of my series on Unit testing in Vue was released.",[4542,160824,160826],{"id":160825},"vue-for-beginners-tutorial","Vue for beginners Tutorial",[651,160828,160829,160830,160835],{},"This week I posted a new video on YouTube ",[812,160831,160834],{"href":160832,"rel":160833},"https://www.youtube.com/watch?v=YGgXAHbQHeI",[816],"Vue for beginners",". I know that getting started with a new framework can be pretty intimidating and even more so in the JavaScript ecosystem. The point of this video was to demonstrate just how versatile Vue is and how I think it's perfect for beginners.",[651,160837,160838],{},[660,160839],{"alt":160834,"src":160840},"/images/newsletter/2019/11/03/vue-for-beginners-cover.jpg",[651,160842,31200,160843,160847,160848,160853],{},[812,160844,160846],{"href":43587,"rel":160845},[816],"https://vuejs.org"," the first thing you will see is that Vue is known as ",[2939,160849,160850],{},[7300,160851,160852],{},"The Progressive JavaScript Framework",". At first, this might seem like some marketing jargon but it's not and for me, it's part of the story that makes Vue so welcoming.",[651,160855,160856],{},"This just means that you can incrementally adopt Vue as your needs start to grow. You can start using Vue right now by dropping a script tag on a page and you are ready to go. No need to buy into build processes, code splitting or module bundlers.",[651,160858,160859,160860,160865],{},"This video walks you through how to use Vue on a new page or in an existing application by just including a script tag. This type of content is what I have been working on for my new course \"Up and Running with VueJS\". ",[812,160861,160864],{"href":160862,"rel":160863},"https://danvega.ck.page/823d5f3bee",[816],"Sign up the course landing page"," and you will get the first 5 lessons of this course for free! More information on this new course coming soon.",[4542,160867,160869],{"id":160868},"unit-testing-in-vue","Unit Testing in Vue",[651,160871,160872,160873,160877],{},"The third and final article in the the ",[812,160874,160876],{"href":69449,"rel":160875},[816],"series I did for Vue Mastery"," has been released. In part three we learn more about writing Unit tests in Vue by looking at a few more complex examples. I really enjoyed putting this series together and I hope you learned something from it.",[651,160879,160880],{},[660,160881],{"alt":160882,"src":160883},"Unit Testing for Vue Mastery","/images/newsletter/2019/11/03/unit-testing-vue-mastery.jpg",[4542,160885,157574],{"id":157573},[651,160887,157577],{},[5909,160889,69849],{"id":69848},[5316,160891,160892,160899],{},[5332,160893,160894],{},[812,160895,160898],{"href":160896,"rel":160897},"https://solomon.io/better-markdown-visual-studio-code/",[816],"Better Markdown in Visual Studio Code",[5332,160900,160901],{},[812,160902,160905],{"href":160903,"rel":160904},"https://blogs.oracle.com/javamagazine/october-2019-v2",[816],"Java Magazine: October 2019 Issue",[5909,160907,120990],{"id":157591},[5316,160909,160910,160917,160924,160931,160938],{},[5332,160911,160912],{},[812,160913,160916],{"href":160914,"rel":160915},"https://www.youtube.com/watch?v=0Qm5nS2PMBs",[816],"Photoshop Sneak Peek: New Object Selection Tool",[5332,160918,160919],{},[812,160920,160923],{"href":160921,"rel":160922},"https://www.youtube.com/watch?v=009e_fTfUI4",[816],"S06E12 Modern Web Podcast - Vue 3, Code Education, & the Vue Community",[5332,160925,160926],{},[812,160927,160930],{"href":160928,"rel":160929},"https://www.twitch.tv/videos/500884074",[816],"Getting Started with VueJS as a newbie",[5332,160932,160933],{},[812,160934,160937],{"href":160935,"rel":160936},"https://www.youtube.com/watch?v=Pj_MqNx22Bo",[816],"Hot Tips Scripting on the Twitter Web UI",[5332,160939,160940],{},[812,160941,160944],{"href":160942,"rel":160943},"https://www.youtube.com/watch?v=Q9eh2iJsjxE",[816],"Scott Tolinski & The Origins of LevelUpTuts | mini-doc",[5909,160946,26378],{"id":39439},[5316,160948,160949,160956],{},[5332,160950,160951],{},[812,160952,160955],{"href":160953,"rel":160954},"http://stories.wosu.org/rivet/a-bootcamp/",[816],"WOSU Public Media: A Bootcamp",[5332,160957,160958],{},[812,160959,160962],{"href":160960,"rel":160961},"https://www.vuemastery.com/blog/This-Month-in-Vue-Oct-2019",[816],"VueMastery: This Month in Vue - October 2019",[5909,160964,157635],{"id":133422},[5316,160966,160967,160974],{},[5332,160968,160969],{},[812,160970,160973],{"href":160971,"rel":160972},"https://raindrop.io/",[816],"Raindrop bookmark manager",[5332,160975,160976],{},[812,160977,160980],{"href":160978,"rel":160979},"https://github.com/vuejs/vue-devtools/releases/tag/v5.3.1",[816],"Vue DevTools v5.3.1",[5909,160982,21973],{"id":21972},[5316,160984,160985],{},[5332,160986,160987],{},[812,160988,160991],{"href":160989,"rel":160990},"https://discover.teachable.com/",[816],"Teachable launches a Marketplace",[5909,160993,157692],{"id":157691},[651,160995,160296,160996,161001],{},[812,160997,161000],{"href":160998,"rel":160999},"https://twitter.com/VueScreencasts",[816],"Vue Screencasts",". Jeffrey Biles does a great job with his tutorials on YouTube and is pretty active on Twitter. If you're in the Vue community give him a follow and check out his YouTube channel.",[4542,161003,157704],{"id":157703},[651,161005,157910],{},[651,161007,41105,161008,69920],{},[41107,161009],{},{"title":674,"searchDepth":790,"depth":790,"links":161011},[161012,161013,161014,161015,161023],{"id":157510,"depth":790,"text":157511},{"id":160825,"depth":790,"text":160826},{"id":160868,"depth":790,"text":160869},{"id":157573,"depth":790,"text":157574,"children":161016},[161017,161018,161019,161020,161021,161022],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #14 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":161026,"issue":2129,"date":161027},"coffee-and-code-newsletter-15","2019-11-03T07:00:00.000Z","/newsletter/2019/11/03/15",{"title":160808,"description":161024},"newsletter/2019/11/03/15","uwIsnTonj981sKfAugtfpNnODih3NUPTBwpPo1BSMmY",{"id":161033,"title":161034,"body":161035,"description":161308,"extension":793,"meta":161309,"navigation":797,"path":161312,"seo":161313,"stem":161314,"__hash__":161315},"content/newsletter/2019/11/10/16.md","Coffee & Code Newsletter: #16",{"type":648,"value":161036,"toc":161291},[161037,161044,161046,161049,161052,161055,161061,161066,161070,161073,161079,161084,161089,161092,161098,161102,161112,161119,161123,161132,161135,161140,161142,161144,161146,161176,161178,161208,161210,161219,161221,161244,161246,161255,161257,161260,161266,161268,161283,161285,161287],[651,161038,161039,161040,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #15 ",[812,161041,18263],{"href":161042,"rel":161043},"https://www.danvega.dev/newsletter/coffee-and-code/15",[816],[4542,161045,157511],{"id":157510},[651,161047,161048],{},"It was a busy week for me (when is it not these days) as I was able to release a new video on YouTube and wrap up step one of my new course.",[4542,161050,161051],{"id":69738},"Up and Running with VueJS",[651,161053,161054],{},"This week I completed the free lessons for my next course \"Up and Running with VueJS\". My original plan was to release these as a teaser to the full paid course but I have been going back and forth with a couple of different ideas. This is what the first six lessons in the course look like.",[651,161056,161057],{},[660,161058],{"alt":161059,"src":161060},"Up and Running with Vue","/images/newsletter/2019/11/10/up-and-running-vue-cover.png",[651,161062,161063,161065],{},[2939,161064,69648],{},"\nIn this lesson, I will give you a quick introduction to the course. We will discuss what you can expect to learn in the course along with what the prerequisites for taking this course are and what hardware and software are required.",[651,161067,161068],{},[2939,161069,69651],{},[651,161071,161072],{},"In this lesson, I will give you a quick introduction to what Vue is. We will also go into a little bit of a discussion on selecting the right JavaScript framework for you.",[651,161074,161075,161078],{},[2939,161076,161077],{},"Why Vue","\nNow that you know what Vue is it would be helpful to understand why you should use Vue. There are a lot of really great libraries and frameworks out there, so why choose Vue? If you haven't already guessed Vue is my favorite framework and I have some reasons that I think it's really great for everyone of all skill levels but especially beginners.",[651,161080,161081,161083],{},[2939,161082,69657],{},"\nIt was jQuery that really got me interested in JavaScript (again). I think it was just how easy jQuery made everything. You could just drop in a script tag on a page and start adding some pretty cool features without knowing much. I look at Vue the same way and in this lesson, I will discuss how you can move from jQuery to Vue.",[651,161085,161086],{},[2939,161087,161088],{},"Hello, Vue",[651,161090,161091],{},"No introduction to a language or framework would be complete without the quintessential Hello, World! In this lesson, we will drop a script tag on to a blank page and walk through every line of code that it takes to create a Hello, World. Spoiler alert: It isn't much!",[651,161093,161094,161097],{},[2939,161095,161096],{},"Resources & Next Steps","\nHave I scratched an itch? Do you want to learn more? I am going to give you a going-away present with a list of resources that I find are very helpful when trying to learn Vue. I will, of course, point you to the continuation of this course which will be coming out soon!",[5909,161099,161101],{"id":161100},"up-running-with-vue-free-lessons","Up & Running with Vue Free Lessons",[651,161103,161104,161105,161108,161109,664],{},"If you're interested in watching these lessons you can check them out using the link below. Please understand this is a really rough draft of the course and has no pretty landing pages associated with it. Also, if you have a minute I would really appreciate some feedback on the course. Feel free to reach out to me on ",[812,161106,51474],{"href":51472,"rel":161107},[816]," or send me an ",[812,161110,13988],{"href":161111},"mailto:danvega@gmail.com",[651,161113,161114],{},[812,161115,161118],{"href":161116,"rel":161117},"https://therealdanvega.teachable.com/purchase?product_id=1436764",[816],"Get the FREE Course Here",[4542,161120,161122],{"id":161121},"create-spring-boot-applications-in-visual-studio-code","Create Spring Boot Applications in Visual Studio Code",[651,161124,161125,161126,161131],{},"I was able to ",[812,161127,161130],{"href":161128,"rel":161129},"https://www.youtube.com/watch?v=5mpHejytgFE%5D(https://www.youtube.com/watch?v=5mpHejytgFE)",[816],"post a new video on YouTube"," this week. I thought it would be cool to show how you could create a new Spring Boot Project (using the Spring Initializr) right in Visual Studio Code. I'm a huge fan of IntelliJ and still purchase a personal license for myself and I won't be giving that up anytime soon.",[651,161133,161134],{},"That being said I do find myself working on projects that are both frontend (Vue) and backend (Spring Boot) and in those situations I am just more comfortable in Visual Studio Code. In this tutorial I create a new Spring Boot Project and Vue Project in VS Code and have the two talk to each other. It's a pretty trivial example but I think it's a great introduction.",[651,161136,161137],{},[660,161138],{"alt":456,"src":161139},"/images/newsletter/2019/11/10/spring-boot-in-vscode.png",[4542,161141,157574],{"id":157573},[651,161143,157577],{},[5909,161145,69849],{"id":69848},[5316,161147,161148,161155,161162,161169],{},[5332,161149,161150],{},[812,161151,161154],{"href":161152,"rel":161153},"https://dev.to/firstclown/loading-dynamic-images-in-a-vue-component-50k7",[816],"Loading dynamic images in a Vue Component",[5332,161156,161157],{},[812,161158,161161],{"href":161159,"rel":161160},"https://www.stackbit.com/blog/devto-stackbit/",[816],"Own Your Content, Broaden Your Audience",[5332,161163,161164],{},[812,161165,161168],{"href":161166,"rel":161167},"https://dev.to/devteam/special-announcement-from-the-dev-founders-hel",[816],"Special Announcement From the DEV Founders",[5332,161170,161171],{},[812,161172,161175],{"href":161173,"rel":161174},"https://azure.microsoft.com/en-us/updates/azure-spring-cloud-service-is-now-in-private-preview/",[816],"Azure Spring Cloud service is now in private preview",[5909,161177,120990],{"id":157591},[5316,161179,161180,161187,161194,161201],{},[5332,161181,161182],{},[812,161183,161186],{"href":161184,"rel":161185},"https://www.youtube.com/watch?v=JG1xINOkBoY",[816],"Quill is the company about to take on Slack",[5332,161188,161189],{},[812,161190,161193],{"href":161191,"rel":161192},"https://youtu.be/kwcillcWOg0",[816],"Teachable Machine 1: Image Classification",[5332,161195,161196],{},[812,161197,161200],{"href":161198,"rel":161199},"https://youtu.be/J5rruXYkFuo",[816],"What's New in Premiere Pro 2020 v14.0",[5332,161202,161203],{},[812,161204,161207],{"href":161205,"rel":161206},"https://www.youtube.com/watch?v=6xgMkGMIudE",[816],"Building an Inbox UI with Tailwind CSS",[5909,161209,26378],{"id":39439},[5316,161211,161212],{},[5332,161213,161214],{},[812,161215,161218],{"href":161216,"rel":161217},"https://devmode.fm/episodes/how-when-and-why-to-use-the-jamstack",[816],"defMode.fm: How, when, and why to use the JAMstack",[5909,161220,157635],{"id":133422},[5316,161222,161223,161230,161237],{},[5332,161224,161225],{},[812,161226,161229],{"href":161227,"rel":161228},"https://visualstudio.microsoft.com/services/visual-studio-online/",[816],"Visual Studio Online",[5332,161231,161232],{},[812,161233,161236],{"href":161234,"rel":161235},"https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/",[816],"Announcing TypeScript 3.7",[5332,161238,161239],{},[812,161240,161243],{"href":161241,"rel":161242},"https://dev.to/liyasthomas/i-created-postwoman-an-online-open-source-api-request-builder-41md",[816],"Postwoman API request builder",[5909,161245,21973],{"id":21972},[5316,161247,161248],{},[5332,161249,161250],{},[812,161251,161254],{"href":161252,"rel":161253},"https://doubleyourfreelancing.com/mastering-convertkit",[816],"Mastering ConvertKit",[5909,161256,79609],{"id":79608},[651,161258,161259],{},"I am very jealous of anyone attending VueConf Toronto this week and I can't wait to see some pictures and recordings from the conference.",[651,161261,161262],{},[812,161263,161264],{"href":161264,"rel":161265},"https://vuetoronto.com/",[816],[5909,161267,157692],{"id":157691},[651,161269,161270,161271,161276,161277,161282],{},"This week must follow is ",[812,161272,161275],{"href":161273,"rel":161274},"https://twitter.com/gustojs",[816],"Gusto"," who is a VueJS core team member and the administrator of the ",[812,161278,161281],{"href":161279,"rel":161280},"https://t.co/Nfb5gWGlfU?amp=1",[816],"VueLand Discord",". Gusto is very involved in the Vue community and I always enjoy the content that he continues to put out.",[4542,161284,157704],{"id":157703},[651,161286,157910],{},[651,161288,41105,161289,69920],{},[41107,161290],{},{"title":674,"searchDepth":790,"depth":790,"links":161292},[161293,161294,161297,161298,161307],{"id":157510,"depth":790,"text":157511},{"id":69738,"depth":790,"text":161051,"children":161295},[161296],{"id":161100,"depth":892,"text":161101},{"id":161121,"depth":790,"text":161122},{"id":157573,"depth":790,"text":157574,"children":161299},[161300,161301,161302,161303,161304,161305,161306],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":79608,"depth":892,"text":79609},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #15 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":161310,"issue":2140,"date":161311},"coffee-and-code-newsletter-16","2019-11-10T07:00:00.000Z","/newsletter/2019/11/10/16",{"title":161034,"description":161308},"newsletter/2019/11/10/16","-OvLBcIMXRHnedJEatRlv6QIMJ-BMsAqn_hgHv90plQ",{"id":161317,"title":161318,"body":161319,"description":161580,"extension":793,"meta":161581,"navigation":797,"path":161584,"seo":161585,"stem":161586,"__hash__":161587},"content/newsletter/2019/11/17/17.md","Coffee & Code Newsletter: #17",{"type":648,"value":161320,"toc":161566},[161321,161328,161330,161333,161337,161350,161357,161363,161367,161370,161379,161382,161387,161391,161394,161401,161408,161410,161412,161414,161444,161446,161469,161471,161494,161496,161526,161528,161548,161550,161552],[651,161322,161323,161324,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #16 ",[812,161325,18263],{"href":161326,"rel":161327},"https://www.danvega.dev/newsletter/coffee-and-code/16",[816],[4542,161329,157511],{"id":157510},[651,161331,161332],{},"I had a pretty busy week that includes some website updates, a new video on YouTube",[4542,161334,161336],{"id":161335},"website-updates","Website Updates",[651,161338,161339,161340,161345,161346,161349],{},"I pushed a few updates to my website this week and I think they are for the better. The first thing you might ",[812,161341,161344],{"href":161342,"rel":161343},"https://www.danvega.dev/blog/2019/04/30/up-and-running-with-vue/",[816],"notice"," is the font family has changed to ",[676,161347,161348],{},"Roboto Slab"," and it is much larger, 22px to be specific.",[651,161351,161352,161353,161356],{},"I also updated the footer for each blog post. I had this large obnoxious newsletter signup form and I just wasn't happy with it. I still have the newsletter signup but its not as obtrusive now and I have added comments to blog posts. I have been holding off on adding comments but after giving it some thought I really wanted to add them in. I decided to go with ",[812,161354,44121],{"href":44119,"rel":161355},[816]," which I was using for my old blog but this time I decided to pay for and remove those annoying ads.",[651,161358,161359],{},[660,161360],{"alt":161361,"src":161362},"Blog Post Footer","/images/newsletter/2019/11/17/blog-post-footer.png",[4542,161364,161366],{"id":161365},"hello-vue-3","Hello, Vue 3",[651,161368,161369],{},"I have begun preparing for a talk I am giving at CodeMash 2020 \"Vue 3: Smaller, Faster, Stronger\". If you're going to be at CodeMash please make sure you say hi 👋🏻. I also may or may not be turning this into a course in the new year so If that is of any interest to you let me know.",[651,161371,161372,161373,161378],{},"Vue 3.0 won't be out until the end of Q1 2020 but that doesn't mean you can't start playing with it right now. I created a new ",[812,161374,161377],{"href":161375,"rel":161376},"https://www.youtube.com/watch?v=UAgO2JanN9Y",[816],"tutorial for my YouTube channel"," that shows you how to get started with Vue 3 today.",[651,161380,161381],{},"In this tutorial, you will create a simple example in Vue 2 by just dropping a script tag on a page. Next, we will download the Vue 3 source code (currently pre-alpha) from Github, build it and drop it on a page and recreate that example using the composition API.",[651,161383,161384],{},[660,161385],{"alt":161366,"src":161386},"/images/newsletter/2019/11/17/hello-vue-3.png",[4542,161388,161390],{"id":161389},"my-courses-on-packt-oreilly","My Courses on Packt & O'Reilly",[651,161392,161393],{},"You can now find some of my current courses on Packt & O'Reilly Learning. This is really cool to see and I can't wait to publish some more content on these platforms soon!",[651,161395,161396],{},[812,161397,161400],{"href":161398,"rel":161399},"https://www.packtpub.com/catalogsearch/result/?q=Dan%20Vega",[816],"https://www.packtpub.com/catalogsearch/result/?q=Dan Vega",[651,161402,161403],{},[812,161404,161407],{"href":161405,"rel":161406},"https://learning.oreilly.com/search/?query=%22Dan%20Vega%22",[816],"https://learning.oreilly.com/search/?query=\"Dan Vega\"",[4542,161409,157574],{"id":157573},[651,161411,157577],{},[5909,161413,69849],{"id":69848},[5316,161415,161416,161423,161430,161437],{},[5332,161417,161418],{},[812,161419,161422],{"href":161420,"rel":161421},"https://jerickson.net/how-to-query-your-api-using-vuex-in-your-vue-application/",[816],"How to query your API using Vuex in your Vue application",[5332,161424,161425],{},[812,161426,161429],{"href":161427,"rel":161428},"http://www.defgroovy.com/2019/11/groovy-in-enterprise-in-2019.html",[816],"Groovy in the Enterprise in 2019",[5332,161431,161432],{},[812,161433,161436],{"href":161434,"rel":161435},"https://dev.to/stackbit/gating-content-in-jamstack-sites-40o0",[816],"Gating Content in JAMstack Sites",[5332,161438,161439],{},[812,161440,161443],{"href":161441,"rel":161442},"https://octoverse.github.com/#home",[816],"The State of the Octoverse",[5909,161445,120990],{"id":157591},[5316,161447,161448,161455,161462],{},[5332,161449,161450],{},[812,161451,161454],{"href":161452,"rel":161453},"https://www.youtube.com/watch?v=_H38A6E8Nsg",[816],"Building a Kanban Board with Tailwind CSS",[5332,161456,161457],{},[812,161458,161461],{"href":161459,"rel":161460},"https://www.youtube.com/watch?v=uDAv4qOU_04",[816],"MKBHD: 16\" Macbook Pro First Impressions!",[5332,161463,161464],{},[812,161465,161468],{"href":161466,"rel":161467},"https://www.youtube.com/watch?v=vbsPXHCu8Xg&t=2s",[816],"ReactiveConf 2019 - Sebastien Chopin: Be Lazy, Be Smart, Be Nuxt.",[5909,161470,26378],{"id":39439},[5316,161472,161473,161480,161487],{},[5332,161474,161475],{},[812,161476,161479],{"href":161477,"rel":161478},"https://anchor.fm/ducktapes/episodes/SVGs-with-Chris-Coyier-e8tdq2",[816],"SVGs with Chris Coyier",[5332,161481,161482],{},[812,161483,161486],{"href":161484,"rel":161485},"https://changelog.com/gotime/106",[816],"Code editors and language servers",[5332,161488,161489],{},[812,161490,161493],{"href":161491,"rel":161492},"https://realtalkjavascript.simplecast.com/episodes/eb67f4f6",[816],"RealTalk JavaScript: You Better Shop Around - with Kelly Vaughn",[5909,161495,157635],{"id":133422},[5316,161497,161498,161505,161512,161519],{},[5332,161499,161500],{},[812,161501,161504],{"href":161502,"rel":161503},"https://greensock.com/3/",[816],"GSAP 3 Is Available Now!",[5332,161506,161507],{},[812,161508,161511],{"href":161509,"rel":161510},"https://github.com/mobile",[816],"Github for mobile",[5332,161513,161514],{},[812,161515,161518],{"href":161516,"rel":161517},"https://www.gatsbyjs.com/cloud/",[816],"Gatsby Cloud",[5332,161520,161521],{},[812,161522,161525],{"href":161523,"rel":161524},"https://github.com/getnacelle",[816],"Nacelle",[5909,161527,157692],{"id":157691},[651,161529,161270,161530,161535,161536,161541,161542,161547],{},[812,161531,161534],{"href":161532,"rel":161533},"https://twitter.com/chriscoyier",[816],"Chris Coyier",". Chris is the Co-Founder of CodePen, creator of ",[812,161537,161540],{"href":161538,"rel":161539},"https://css-tricks.com/",[816],"https://css-tricks.com"," and you can hear him on his podcast, ",[812,161543,161546],{"href":161544,"rel":161545},"https://twitter.com/ShopTalkShow",[816],"Shop Talk Show",". Chris has probably forgotten more about that web then most of us know.",[4542,161549,157704],{"id":157703},[651,161551,157910],{},[651,161553,41105,161554,69920,161556,161558,161561,161563],{},[41107,161555],{},[41107,161557],{},[812,161559,161560],{"href":161111},"danvega@gmail.com",[41107,161562],{},[812,161564,53869],{"href":82688,"rel":161565},[816],{"title":674,"searchDepth":790,"depth":790,"links":161567},[161568,161569,161570,161571,161572,161579],{"id":157510,"depth":790,"text":157511},{"id":161335,"depth":790,"text":161336},{"id":161365,"depth":790,"text":161366},{"id":161389,"depth":790,"text":161390},{"id":157573,"depth":790,"text":157574,"children":161573},[161574,161575,161576,161577,161578],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":157691,"depth":892,"text":157692},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #16 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":161582,"issue":2145,"date":161583},"coffee-and-code-newsletter-17","2019-11-17T07:00:00.000Z","/newsletter/2019/11/17/17",{"title":161318,"description":161580},"newsletter/2019/11/17/17","GLJ0uLLxf-u_u1oZxWKTIG0vTXAQ4ojsCC5HANM7dOE",{"id":161589,"title":161590,"body":161591,"description":161817,"extension":793,"meta":161818,"navigation":797,"path":161821,"seo":161822,"stem":161823,"__hash__":161824},"content/newsletter/2019/11/24/18.md","Coffee & Code Newsletter: #18",{"type":648,"value":161592,"toc":161800},[161593,161600,161602,161605,161609,161618,161621,161624,161628,161637,161640,161644,161653,161659,161664,161668,161671,161673,161675,161677,161700,161702,161718,161720,161743,161745,161754,161756,161772,161774,161783,161785,161787],[651,161594,161595,161596,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #17 ",[812,161597,18263],{"href":161598,"rel":161599},"https://www.danvega.dev/newsletter/coffee-and-code/17",[816],[4542,161601,157511],{"id":157510},[651,161603,161604],{},"It has been a busy week for me wrapping up some work projects as I get a nice little vacation next week. With that let's get right into it.",[4542,161606,161608],{"id":161607},"spring-boot-in-visual-studio-code-blog-post","Spring Boot in Visual Studio Code Blog Post",[651,161610,161611,161612,161617],{},"A few weeks ago I ",[812,161613,161616],{"href":161614,"rel":161615},"https://www.youtube.com/watch?v=5mpHejytgFE&feature=youtu.be",[816],"created a new tutorial"," for YouTube that showed you how to create a Spring Boot application in Visual Studio Code. I created this is a video tutorial for YouTube but what I have been struggling to figure out is how can I reuse this content on my blog.",[651,161619,161620],{},"If users are reading my blog posts and come across this post with the video my hope is that If they find value in it I can gain them as a subscriber over on YouTube. My first thought was I can just create a new post, embed the video and be done with it.",[651,161622,161623],{},"That is the path of least resistance for me but I have some problems with it. This doesn't add any value to the video or even give the reader a reason to watch it. It also doesn't have any text which means it won't be picked it up by the search engines which begs the point why have a blog post at all?",[5909,161625,161627],{"id":161626},"failing-fast","Failing Fast",[651,161629,161630,161631,161636],{},"My first attempt this week was to order a transcription of the entire video using ",[812,161632,161635],{"href":161633,"rel":161634},"http://rev.com",[816],"rev.com",". I was going to add the video to the blog post and then a transcription of the video below it. My initial thoughts were that this wasn't going to be very helpful and I was right. After reading through it I realized that nobody was going to read this and it wasn't very search engine friendly.",[651,161638,161639],{},"Another attempt was to create an actual blog post like I normally would if I wanted to teach someone how to create a Spring Boot project in Visual Studio Code. I got about half way through this post and I was not having fun at all. I think it was because I already created this tutorial but I was having a hard time getting through it so I scrapped it.",[5909,161641,161643],{"id":161642},"a-solution","A Solution",[651,161645,161646,161647,161652],{},"I finally found a result that I was happy with and you can ",[812,161648,161651],{"href":161649,"rel":161650},"https://www.danvega.dev/blog/2019/11/21/spring-boot-visual-studio-code/",[816],"read it here",". Yes I embedded the YouTube video in the blog post but I also gave some context as to what this video was about without giving you the whole tutorial in text. I also got some good feedback on Twitter from a few folks. I can add things like code snippets if its code that someone might want to copy or even timestamps to point out where things in the video take place. I really like that and I think it adds value.",[651,161654,161655],{},[660,161656],{"alt":161657,"src":161658},"Spring Boot in VS Code","/images/newsletter/2019/11/18/spring-boot-in-vscode.png",[651,161660,161661],{},[812,161662,161649],{"href":161649,"rel":161663},[816],[5909,161665,161667],{"id":161666},"content-syndication-what-are-you-doing","Content Syndication: What are you doing?",[651,161669,161670],{},"If you're still reading this (give yourself a high-five) I would love to know what you think of the short form post and the video embed. I am also curious to hear what others are doing in this scenario. I have a full-time job, a family and hobbies. I only have so much time each week to produce content and I am constantly striving to be efficient with my time.",[4542,161672,157574],{"id":157573},[651,161674,157577],{},[5909,161676,69849],{"id":69848},[5316,161678,161679,161686,161693],{},[5332,161680,161681],{},[812,161682,161685],{"href":161683,"rel":161684},"https://blog.jetbrains.com/idea/2019/11/meet-grazie-the-ultimate-spelling-grammar-and-style-checker-for-intellij-idea/",[816],"Meet Grazie: the ultimate spelling, grammar, and style checker for IntelliJ IDEA",[5332,161687,161688],{},[812,161689,161692],{"href":161690,"rel":161691},"https://dev.to/stackbit/15-jamstack-resources-you-need-as-a-web-developer-2j7n",[816],"15 JAMstack Resources You Need as a Web Developer",[5332,161694,161695],{},[812,161696,161699],{"href":161697,"rel":161698},"https://dev.to/firstclown/the-basics-of-mapstate-it-s-not-as-hard-as-it-looks-18pn",[816],"The basics of mapState (it's not as hard as it looks)",[5909,161701,120990],{"id":157591},[5316,161703,161704,161711],{},[5332,161705,161706],{},[812,161707,161710],{"href":161708,"rel":161709},"https://www.youtube.com/watch?v=NByw6KZY1Vk",[816],"VSCode Vim Mode: Vim for Non Vim Users",[5332,161712,161713],{},[812,161714,161717],{"href":161715,"rel":161716},"https://www.youtube.com/watch?v=Fbo4pttBZ9k&t=556s",[816],"An Introduction To Vue Testing With Jest - (Realworld App)",[5909,161719,26378],{"id":39439},[5316,161721,161722,161729,161736],{},[5332,161723,161724],{},[812,161725,161728],{"href":161726,"rel":161727},"https://podcasts.apple.com/us/podcast/our-favorite-underrated-youtube-channels-mechanical/id1474429475?i=1000456880083",[816],"Waveform: Our Favorite YouTube Channels, Mechanical Keyboards, & Tech Embargoes Explained",[5332,161730,161731],{},[812,161732,161735],{"href":161733,"rel":161734},"https://syntax.fm/show/198/how-to-get-better-at-problem-solving",[816],"Syntax.fm: How To Get Better At Problem Solving",[5332,161737,161738],{},[812,161739,161742],{"href":161740,"rel":161741},"https://megaphone.link/LMM6357957666",[816],"MoneyLab: Frictionless Course Creation",[5909,161744,157635],{"id":133422},[5316,161746,161747],{},[5332,161748,161749],{},[812,161750,161753],{"href":161751,"rel":161752},"https://dev.to/banthagroup/introducing-new-vue-js-lightbox-4609",[816],"VueJS Lightbox",[5909,161755,160020],{"id":160019},[5316,161757,161758,161765],{},[5332,161759,161760],{},[812,161761,161764],{"href":161762,"rel":161763},"https://news.vuejs.org/issues/166",[816],"Official Vue News: Thursday, November 21, 2019",[5332,161766,161767],{},[812,161768,161771],{"href":161769,"rel":161770},"https://www.baeldung.com/java-weekly-308?utm_source=email-newsletter",[816],"Baeldung: Java Weekly, Issue 308",[5909,161773,21973],{"id":21972},[5316,161775,161776],{},[5332,161777,161778],{},[812,161779,161782],{"href":161780,"rel":161781},"https://thoughtbot.com/upcase/onramp-to-vim",[816],"Onramp to Vim",[4542,161784,157704],{"id":157703},[651,161786,157910],{},[651,161788,41105,161789,69920,161791,161793,161795,161797],{},[41107,161790],{},[41107,161792],{},[812,161794,161560],{"href":161111},[41107,161796],{},[812,161798,53869],{"href":82688,"rel":161799},[816],{"title":674,"searchDepth":790,"depth":790,"links":161801},[161802,161803,161808,161816],{"id":157510,"depth":790,"text":157511},{"id":161607,"depth":790,"text":161608,"children":161804},[161805,161806,161807],{"id":161626,"depth":892,"text":161627},{"id":161642,"depth":892,"text":161643},{"id":161666,"depth":892,"text":161667},{"id":157573,"depth":790,"text":157574,"children":161809},[161810,161811,161812,161813,161814,161815],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":160019,"depth":892,"text":160020},{"id":21972,"depth":892,"text":21973},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #17 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":161819,"issue":2154,"date":161820},"coffee-and-code-newsletter-18","2019-11-18T07:00:00.000Z","/newsletter/2019/11/24/18",{"title":161590,"description":161817},"newsletter/2019/11/24/18","7pae6izq7OuDdPljXLb26CO7LNJA8x6bVlt0XquN4fw",{"id":161826,"title":161827,"body":161828,"description":161995,"extension":793,"meta":161996,"navigation":797,"path":161999,"seo":162000,"stem":162001,"__hash__":162002},"content/newsletter/2019/12/01/19.md","Coffee & Code Newsletter: #19",{"type":648,"value":161829,"toc":161982},[161830,161837,161839,161842,161846,161849,161852,161855,161858,161865,161871,161873,161875,161877,161900,161902,161918,161920,161936,161938,161947,161949,161965,161967,161969],[651,161831,161832,161833,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #18 ",[812,161834,18263],{"href":161835,"rel":161836},"https://www.danvega.dev/newsletter/coffee-and-code/18",[816],[4542,161838,157511],{"id":157510},[651,161840,161841],{},"It was Thanksgiving this week here in the US and I just want to start this issue off by saying thank you to all of you. I appreciate that you are all here on my mailing list and that you give me a chance to ramble once a week. With that said, let's get into this week (shorter than normal) issue.",[5909,161843,161845],{"id":161844},"new-course-up-and-running-with-vuejs","New Course: Up and Running with Vue.js",[651,161847,161848],{},"If you have been paying attention to this blog at all you know that I am a huge fan of Vue. One of my favorite features of Vue is just how approachable it is for beginners. I was looking around at the landscape of Vue courses in the marketplace and I had a few thoughts.",[651,161850,161851],{},"First, there are some really great courses from some awesome authors out there. Second, a lot of the introductions to Vue come with hours of additional content which can seem overwhelming to beginners.",[651,161853,161854],{},"With that, I set out to build a course for absolute beginners. I had a couple of goals that I wanted to stick to when I began building out this course. The first was I wanted to keep this course short and the finished version comes in under 2 hours. Next, I wanted to make sure that reinforce concepts that you were learning along the way with quizzes, exercises, and coding challenges. Finally, I wanted to target developers who just wanted to drop a script tag on a page and go. This means that that you don't need to understand Node & NPM and there are no command-line interfaces (CLI) to install.",[651,161856,161857],{},"I am really happy with the result and I will have more to share with you in the near future but I wanted to share it with you. If you use the link below you can grab the course for $9.99. If you know someone who might be interested in the course please pass the link along and I appreciate you supporting me friends.",[651,161859,161860],{},[812,161861,161864],{"href":161862,"rel":161863},"https://www.udemy.com/course/vue-intro/?couponCode=E8832D7EDBEE693C295E",[816],"Up and Running with Vue.js",[651,161866,161867],{},[660,161868],{"alt":161869,"src":161870},"Course Landing Page","/images/newsletter/2019/12/01/course-landing-page.png",[4542,161872,157574],{"id":157573},[651,161874,157577],{},[5909,161876,69849],{"id":69848},[5316,161878,161879,161886,161893],{},[5332,161880,161881],{},[812,161882,161885],{"href":161883,"rel":161884},"https://dev.to/martinheinz/going-serverless-with-openfaas-and-golang-building-optimized-templates-29ic",[816],"Going Serverless with OpenFaaS and Golang - Building Optimized Templates",[5332,161887,161888],{},[812,161889,161892],{"href":161890,"rel":161891},"https://snipcart.com/blog/vue-component-example-tutorial",[816],"How Vue Components Work (& How They Simplify Web Development)",[5332,161894,161895],{},[812,161896,161899],{"href":161897,"rel":161898},"https://blog.logrocket.com/animating-vue-with-greensock/",[816],"Animating Vue with GreenSock",[5909,161901,120990],{"id":157591},[5316,161903,161904,161911],{},[5332,161905,161906],{},[812,161907,161910],{"href":161908,"rel":161909},"https://www.youtube.com/watch?v=nHXy2due_fA",[816],"What's New in Gradle 6.0",[5332,161912,161913],{},[812,161914,161917],{"href":161915,"rel":161916},"https://www.youtube.com/watch?v=apR_jIM0rb4",[816],"Why I Chose Vue over React & Why I'm not going back",[5909,161919,26378],{"id":39439},[5316,161921,161922,161929],{},[5332,161923,161924],{},[812,161925,161928],{"href":161926,"rel":161927},"http://www.fullstackradio.com/129",[816],"Full Stack Radio: Evan You - What's Coming in Vue.js 3.0",[5332,161930,161931],{},[812,161932,161935],{"href":161933,"rel":161934},"https://www.ifelsepodcast.com/episodes/react-vs-vue-with-guests-cassidy-williams-erik-hanchett",[816],"if/else: React vs. Vue: With Guests Cassidy Williams & Erik Hanchett",[5909,161937,6016],{"id":39340},[5316,161939,161940],{},[5332,161941,161942],{},[812,161943,161946],{"href":161944,"rel":161945},"https://www.packtpub.com/programming/testing-vue-js-components-with-jest",[816],"Testing Vue.js Components with Jest",[5909,161948,21973],{"id":21972},[5316,161950,161951,161958],{},[5332,161952,161953],{},[812,161954,161957],{"href":161955,"rel":161956},"https://beginnerjavascript.com/",[816],"Wes Bos: Beginner JavaScript",[5332,161959,161960],{},[812,161961,161964],{"href":161962,"rel":161963},"https://testingjavascript.com/",[816],"Kent C. Dodds: Testing JavaScript",[4542,161966,157704],{"id":157703},[651,161968,157910],{},[651,161970,41105,161971,69920,161973,161975,161977,161979],{},[41107,161972],{},[41107,161974],{},[812,161976,161560],{"href":161111},[41107,161978],{},[812,161980,53869],{"href":82688,"rel":161981},[816],{"title":674,"searchDepth":790,"depth":790,"links":161983},[161984,161987,161994],{"id":157510,"depth":790,"text":157511,"children":161985},[161986],{"id":161844,"depth":892,"text":161845},{"id":157573,"depth":790,"text":157574,"children":161988},[161989,161990,161991,161992,161993],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":39340,"depth":892,"text":6016},{"id":21972,"depth":892,"text":21973},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #18 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":161997,"issue":2159,"date":161998},"coffee-and-code-newsletter-19","2019-12-01T07:00:00.000Z","/newsletter/2019/12/01/19",{"title":161827,"description":161995},"newsletter/2019/12/01/19","-cUDiWne7ddrqCIlFYK0h_yxEp-3yuXmvtZ47and198",{"id":162004,"title":162005,"body":162006,"description":162298,"extension":793,"meta":162299,"navigation":797,"path":162302,"seo":162303,"stem":162304,"__hash__":162305},"content/newsletter/2019/12/08/20.md","Coffee & Code Newsletter: #20",{"type":648,"value":162007,"toc":162282},[162008,162015,162017,162020,162024,162027,162030,162037,162041,162049,162054,162058,162070,162076,162082,162086,162093,162096,162099,162102,162108,162117,162119,162121,162123,162146,162148,162178,162180,162203,162205,162221,162223,162260,162262,162269],[651,162009,162010,162011,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #19 ",[812,162012,18263],{"href":162013,"rel":162014},"https://www.danvega.dev/newsletter/coffee-and-code/19",[816],[4542,162016,157511],{"id":157510},[651,162018,162019],{},"This week I worked on my new course, a blog post and a video for YouTube. If someone would have invited me on a podcast I would have completed the content dream 🤣I have a lot to talk about this week so let's get right into it.",[5909,162021,162023],{"id":162022},"up-running-with-vue-trailer","Up & Running with Vue Trailer",[651,162025,162026],{},"This week I was able to record, edit and publish a trailer for my new course. This course is an introduction to Vue for beginners. Previously I just had the first video as the trailer and that is never a good idea. I wanted a short trailer letting potential students know what they can expect from the course and what my motivation behind creating it was. I haven't really promoted this course much because I wanted to have this in place first.",[651,162028,162029],{},"You can check out the trailer on the course landing page using the link below. For anyone who has already purchased the course or purchases it in the future, I appreciate the support!",[651,162031,162032],{},[812,162033,162036],{"href":162034,"rel":162035},"https://www.udemy.com/course/vue-intro/?referralCode=E9DECFF78CA706D7A68A",[816],"Up & Running with Vue.js on Udemy",[5909,162038,162040],{"id":162039},"top-ways-to-learn-vue-3","Top ways to learn Vue 3",[651,162042,162043,162044,162048],{},"I wrote a blog post for Vue Mastery and it was published on Monday. In this blog post I walk you through some of my favorite resources for learning Vue 3. This was especially helpful for me as I am preparing for a Vue 3 talk at ",[812,162045,97312],{"href":162046,"rel":162047},"https://www.codemash.org/",[816]," in January.",[651,162050,162051],{},[812,162052,69857],{"href":69857,"rel":162053},[816],[5909,162055,162057],{"id":162056},"creating-a-bare-vue-project-using-the-vue-cli","Creating a bare Vue project using the Vue CLI",[651,162059,162060,162061,162063,162064,162066,162067,162069],{},"When I watch others create new Vue projects using the Vue CLI I often see the same steps. They will create the project, and then remove all of the boilerplate code that the CLI gives you. They start with removing the ",[676,162062,81303],{}," component and the code needed to import it and display it in ",[676,162065,49477],{},". Whenever I tell them that there is an option you can pass to the ",[676,162068,57853],{}," command to remove that boilerplate code they are amazed. This video is a short one but definitely helpful, hope you enjoy it.",[651,162071,162072],{},[812,162073,162074],{"href":162074,"rel":162075},"https://www.youtube.com/watch?v=ki4Z3rI-6pg&t=10s",[816],[651,162077,162078],{},[660,162079],{"alt":162080,"src":162081},"Vue CLI YouTube Video Cover","/images/newsletter/2019/12/08/vue-cli-bare-cover.png",[5909,162083,162085],{"id":162084},"closing-in-on-100000-monthly-page-views","Closing in on 100,000 monthly page views!",[651,162087,162088,162089,162092],{},"I started this year off by creating the website ",[812,162090,53869],{"href":53869,"rel":162091},[816],". I wanted to get my blog off of WordPress and on to a static site and Vue + Gridsome seemed to be a great fit for what I wanted to do. I started off with 0 blog posts and just started writing about whatever inspired me that day. I really enjoyed the workflow of writing in markdown, committing to Git and the article being live on my site.",[651,162094,162095],{},"I decided pretty early on that I was going to move my existing posts over but I needed to figure out how to do it. I have been blogging for 15 years and had accumulated nearly 1000 posts. I knew that a lot of them were no longer relevant or didn't contain much value. I want all of the articles on my blog to be helpful and not some review of some movie I watched last weekend. It is the end of the year I am happy to say that all of the posts I wanted to move are now moved over.",[651,162097,162098],{},"About a month ago I got rid of Google Analytics and forked over the $9 a month to Netlify for their analytics and I did this for a couple of reasons. Netlify is just such a fantastic service that I wanted to give them money for something, no I'm not kidding about that. Second, Google Analytics can be blocked and by my tech-savvy audience, it is probably being blocked more times than not. Because of this, I don't think I have ever got a real good gauge on where I was stats wise.",[651,162100,162101],{},"I have been paying attention to my stats lately and they have been on the rise. I logged in this week and to my surprise I had 92,463 page views in the last month. I can't believe that I am closing in on 100,000 and I am going to make the push to get there.",[651,162103,162104],{},[660,162105],{"alt":162106,"src":162107},"danvega.dev Analytics","/images/newsletter/2019/12/08/danvega-dev-analytics.png",[651,162109,162110,162111,162116],{},"I attribute these stats to a couple of things. The first is I cut out a lot of content that wasn't really valuable to anyone. I learned this from an episode of the Smart Passive Income with Pat Flynn titled ",[812,162112,162115],{"href":162113,"rel":162114},"https://www.smartpassiveincome.com/podcasts/how-to-do-a-content-audit-with-todd-tresidder/",[816],"How Deleting a Third of Your Content Can Triple Your Traffic—How to Do a Content Audit with Todd Tresidder",". After that, it was all about providing good content to my readers along with a new super-fast static website. I'm excited about the future of this blog and thank all of you who support it.",[4542,162118,157574],{"id":157573},[651,162120,157577],{},[5909,162122,69849],{"id":69848},[5316,162124,162125,162132,162139],{},[5332,162126,162127],{},[812,162128,162131],{"href":162129,"rel":162130},"https://www.vuemastery.com/blog/why-nuxtjs-is-the-perfect-framework-for-building-static-websites",[816],"Why Nuxt.js is the perfect framework for building static websites",[5332,162133,162134],{},[812,162135,162138],{"href":162136,"rel":162137},"https://codingcoach.io/blog/netlify-serverless-function-instagram-api/",[816],"Instagram Feed API",[5332,162140,162141],{},[812,162142,162145],{"href":162143,"rel":162144},"https://devblogs.microsoft.com/dotnet/introducing-net-5/",[816],"Introducing .NET 5",[5909,162147,120990],{"id":157591},[5316,162149,162150,162157,162164,162171],{},[5332,162151,162152],{},[812,162153,162156],{"href":162154,"rel":162155},"https://www.youtube.com/watch?v=T_JEsOBuI7U",[816],"Reactivity in Vue 3 Compositional functions API - VueJs Columbus Meetup",[5332,162158,162159],{},[812,162160,162163],{"href":162161,"rel":162162},"https://www.youtube.com/watch?v=0pThnRneDjw",[816],"Web Development In 2020 - A Practical Guide",[5332,162165,162166],{},[812,162167,162170],{"href":162168,"rel":162169},"https://www.youtube.com/watch?v=ff4fgQxPaO0",[816],"Faster apps with JSON.parse (Chrome Dev Summit 2019)",[5332,162172,162173],{},[812,162174,162177],{"href":162175,"rel":162176},"https://www.youtube.com/watch?v=WSjj-IhBiSY&feature=youtu.be",[816],"The State of Java Relational Persistence by Maciej Walkowiak @ Spring I/O 2019",[5909,162179,26378],{"id":39439},[5316,162181,162182,162189,162196],{},[5332,162183,162184],{},[812,162185,162188],{"href":162186,"rel":162187},"https://changelog.com/podcast/370",[816],"The Changelog: The making of GitHub Sponsors",[5332,162190,162191],{},[812,162192,162195],{"href":162193,"rel":162194},"https://www.ecpodcast.io/episodes/17-greg-schier-how-i-built-and-sold-insomnia",[816],"The Entrepreneurial Coder: #17 - Greg Schier - How I Built and Sold Insomnia",[5332,162197,162198],{},[812,162199,162202],{"href":162200,"rel":162201},"https://syntax.fm/show/202/potluck-tabs-are-better-coding-music-seo-is-angular-good-biggie-smalls-soy-sauce-more",[816],"Syntax.fm Potluck",[5909,162204,21973],{"id":21972},[5316,162206,162207,162214],{},[5332,162208,162209],{},[812,162210,162213],{"href":162211,"rel":162212},"https://m.academy/p/magento-2-coding-kickstart",[816],"Magento 2 Coding Kickstart",[5332,162215,162216],{},[812,162217,162220],{"href":162218,"rel":162219},"https://www.vuemastery.com/courses/vue-3-essentials/",[816],"Vue Mastery: Vue 3 Essentials",[5909,162222,157635],{"id":133422},[5316,162224,162225,162232,162239,162246,162253],{},[5332,162226,162227],{},[812,162228,162231],{"href":162229,"rel":162230},"https://github.com/vuejs/vue-apollo/releases/tag/v4.0.0-alpha.1",[816],"vue-apollo v4.0.0-alpha.1",[5332,162233,162234],{},[812,162235,162238],{"href":162236,"rel":162237},"https://github.com/joaoeudes7/V-Emoji-Picker",[816],"V-Emoji-Picker",[5332,162240,162241],{},[812,162242,162245],{"href":162243,"rel":162244},"https://www.npmjs.com/package/npkill",[816],"npkill",[5332,162247,162248],{},[812,162249,162252],{"href":162250,"rel":162251},"https://speak.dev/",[816],"Speak.dev",[5332,162254,162255],{},[812,162256,162259],{"href":162257,"rel":162258},"https://www.producthunt.com/posts/craiglist-for-mobile",[816],"Craigslist gets a mobile app 24 years later!",[4542,162261,157704],{"id":157703},[651,162263,162264,162265,162268],{},"Thanks for reading the 20th issue of Coffee and Code. I honestly didn't know if I would keep this up for 5 issues let alone 20 but I am having fun doing so. According to my stats, a few of you are actually opening these emails so I hope you're reading them and finding value. If you have any suggestions you can reply to this email or reach out to me on ",[812,162266,51474],{"href":51472,"rel":162267},[816],". I hope you have a great week and as always friends...",[651,162270,41105,162271,69920,162273,162275,162277,162279],{},[41107,162272],{},[41107,162274],{},[812,162276,161560],{"href":161111},[41107,162278],{},[812,162280,53869],{"href":82688,"rel":162281},[816],{"title":674,"searchDepth":790,"depth":790,"links":162283},[162284,162290,162297],{"id":157510,"depth":790,"text":157511,"children":162285},[162286,162287,162288,162289],{"id":162022,"depth":892,"text":162023},{"id":162039,"depth":892,"text":162040},{"id":162056,"depth":892,"text":162057},{"id":162084,"depth":892,"text":162085},{"id":157573,"depth":790,"text":157574,"children":162291},[162292,162293,162294,162295,162296],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":21972,"depth":892,"text":21973},{"id":133422,"depth":892,"text":157635},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #19 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":162300,"issue":2164,"date":162301},"coffee-and-code-newsletter-20","2019-12-08T07:00:00.000Z","/newsletter/2019/12/08/20",{"title":162005,"description":162298},"newsletter/2019/12/08/20","BxFWRwC2CS8WP01o7HltF_DxI8VbqHFostuVT2fzpXM",{"id":162307,"title":162308,"body":162309,"description":162506,"extension":793,"meta":162507,"navigation":797,"path":162510,"seo":162511,"stem":162512,"__hash__":162513},"content/newsletter/2019/12/22/21.md","Coffee & Code Newsletter: #21",{"type":648,"value":162310,"toc":162492},[162311,162318,162320,162333,162337,162340,162346,162350,162353,162360,162362,162364,162366,162403,162405,162428,162430,162446,162448,162464,162466,162475,162477,162479],[651,162312,162313,162314,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #20 ",[812,162315,18263],{"href":162316,"rel":162317},"https://www.danvega.dev/newsletter/coffee-and-code/20",[816],[4542,162319,157511],{"id":157510},[651,162321,162322,162323,51393,162327,162332],{},"Last week was the first time I missed a newsletter and I would like to apologize for that. This past weekend was the culmination of months of planning for me and my team. I run an event called Toys for Shots here in Cleveland and Saturday night was our 12th time doing so. If you're interested in learning more you can visit ",[812,162324,162326],{"href":39495,"rel":162325},[816],"www.toysforshots.com",[812,162328,162331],{"href":162329,"rel":162330},"https://photos.google.com/share/AF1QipPKY5YQ-Jh8C4XJJZZ8BEnQKkdLryxWhlx98evSPJ3u-TVFFjxoJDkUdJ_-Fdo1Dg?key=RVZWc1BMYjRQcVBwUldrWjh5UWJIcDRQS2lGN3NR",[816],"check out some pictures"," from the event.",[5909,162334,162336],{"id":162335},"new-course-blog-post","New Course: Blog Post",[651,162338,162339],{},"I already sent out an email about this but I wrote up my motivation behind creating my new course. If you end up taking the course I would really love your feedback.",[651,162341,162342],{},[812,162343,162344],{"href":162344,"rel":162345},"https://www.danvega.dev/blog/2019/12/19/up-and-running-with-vuejs/",[816],[5909,162347,162349],{"id":162348},"state-of-javascript-2109","State of JavaScript 2109",[651,162351,162352],{},"The state of JavaScript survey was sent out earlier this year and the results are in. What I really love about this survey is the amazing job they do to present the data. Data on its own isn't all that exciting but the results are both fun to read through and look at. Did any of the results stand out for you?",[651,162354,162355],{},[812,162356,162359],{"href":162357,"rel":162358},"https://2019.stateofjs.com/resources/#blogs_news_magazines_others",[816],"https://2019.stateofjs.com",[4542,162361,157574],{"id":157573},[651,162363,157577],{},[5909,162365,69849],{"id":69848},[5316,162367,162368,162375,162382,162389,162396],{},[5332,162369,162370],{},[812,162371,162374],{"href":162372,"rel":162373},"https://www.raymondcamden.com/2019/11/26/sanitizing-html-in-vuejs",[816],"Sanitizing HTML in Vue.js",[5332,162376,162377],{},[812,162378,162381],{"href":162379,"rel":162380},"https://dev.to/firstclown/what-s-the-purpose-of-vuex-1bag",[816],"What's the purpose of Vuex?",[5332,162383,162384],{},[812,162385,162388],{"href":162386,"rel":162387},"https://www.raymondcamden.com/2019/12/11/using-bearer-for-easier-oauth-and-api-calls",[816],"Using Bearer for Easier OAuth and API Calls",[5332,162390,162391],{},[812,162392,162395],{"href":162393,"rel":162394},"https://blog.digitalocean.com/scotch-io-is-joining-digitalocean/",[816],"Scotch.io is joining DigitalOcean",[5332,162397,162398],{},[812,162399,162402],{"href":162400,"rel":162401},"https://medium.com/flutter/web-support-for-flutter-goes-beta-35b64a1217c0",[816],"Web support for Flutter goes beta",[5909,162404,120990],{"id":157591},[5316,162406,162407,162414,162421],{},[5332,162408,162409],{},[812,162410,162413],{"href":162411,"rel":162412},"https://vuetoronto.com/videos/design-principles-of-vue-3-evan-you/",[816],"Design Principles of Vue 3.0",[5332,162415,162416],{},[812,162417,162420],{"href":162418,"rel":162419},"https://www.youtube.com/watch?v=qQ1oQJJn1nQ",[816],"Why are there Four Firefoxes?",[5332,162422,162423],{},[812,162424,162427],{"href":162425,"rel":162426},"https://www.youtube.com/watch?v=_IDDT1_LfY0",[816],"Evan You: State of UI Components",[5909,162429,26378],{"id":39439},[5316,162431,162432,162439],{},[5332,162433,162434],{},[812,162435,162438],{"href":162436,"rel":162437},"https://syntax.fm/show/204/2019-gift-guide",[816],"Syntax.fm: 2019 Gift Guide",[5332,162440,162441],{},[812,162442,162445],{"href":162443,"rel":162444},"https://changelog.com/jsparty/106",[816],"JS Party #16: Mikeal schools us on ES Modules",[5909,162447,21973],{"id":21972},[5316,162449,162450,162457],{},[5332,162451,162452],{},[812,162453,162456],{"href":162454,"rel":162455},"https://egghead.io/courses/build-a-video-chat-app-with-twilio-and-gatsby",[816],"Build a Video Chat App with Twilio and Gatsby",[5332,162458,162459],{},[812,162460,162463],{"href":162461,"rel":162462},"https://www.youtube.com/watch?v=her_7pa0vrg",[816],"Spring Security Full Course",[5909,162465,157635],{"id":133422},[5316,162467,162468],{},[5332,162469,162470],{},[812,162471,162474],{"href":162472,"rel":162473},"https://github.com/nuxt/nuxt.js/releases/tag/v2.11.0",[816],"Nuxt v2.11.0 Release",[4542,162476,157704],{"id":157703},[651,162478,157910],{},[651,162480,41105,162481,69920,162483,162485,162487,162489],{},[41107,162482],{},[41107,162484],{},[812,162486,161560],{"href":161111},[41107,162488],{},[812,162490,53869],{"href":82688,"rel":162491},[816],{"title":674,"searchDepth":790,"depth":790,"links":162493},[162494,162498,162505],{"id":157510,"depth":790,"text":157511,"children":162495},[162496,162497],{"id":162335,"depth":892,"text":162336},{"id":162348,"depth":892,"text":162349},{"id":157573,"depth":790,"text":157574,"children":162499},[162500,162501,162502,162503,162504],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":21972,"depth":892,"text":21973},{"id":133422,"depth":892,"text":157635},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #20 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":162508,"issue":3134,"date":162509},"coffee-and-code-newsletter-21","2019-12-22T07:00:00.000Z","/newsletter/2019/12/22/21",{"title":162308,"description":162506},"newsletter/2019/12/22/21","xIcBj6uPOLzha_Pi6g_R98fu3rRbxW9Z3bxew-HDxLE",{"id":162515,"title":162516,"body":162517,"description":162746,"extension":793,"meta":162747,"navigation":797,"path":162750,"seo":162751,"stem":162752,"__hash__":162753},"content/newsletter/2020/01/19/22.md","Coffee & Code Newsletter: #22",{"type":648,"value":162518,"toc":162730},[162519,162526,162528,162531,162533,162541,162554,162558,162564,162566,162568,162574,162578,162584,162587,162590,162596,162600,162603,162608,162614,162616,162618,162620,162643,162645,162661,162663,162672,162674,162682,162684,162713,162715,162717],[651,162520,162521,162522,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #21 ",[812,162523,18263],{"href":162524,"rel":162525},"https://www.danvega.dev/newsletter/coffee-and-code/21",[816],[4542,162527,157511],{"id":157510},[651,162529,162530],{},"I'm back! Sorry for taking a few weeks off but I was super busy and I can't wait to tell you all about it. I hope all of your holidays were wonderful and you're all recharged and ready for the new year. I have a bunch to get into so let's do it.",[5909,162532,159595],{"id":69934},[651,162534,162535,162536,162540],{},"I was really busy over the holidays preparing for ",[812,162537,97312],{"href":162538,"rel":162539},"http://codemash.org",[816],". This was my very first time attending and speaking at CodeMash so I had my hands full. This was an amazing conference and I can't recommend it enough. I have two blog posts below that recap both the conference and my talk.",[5316,162542,162543,162548],{},[5332,162544,162545],{},[812,162546,70086],{"href":70106,"rel":162547},[816],[5332,162549,162550],{},[812,162551,447],{"href":162552,"rel":162553},"https://www.danvega.dev/blog/2020/01/13/codemash-2020-recap/",[816],[5909,162555,162557],{"id":162556},"start-using-vue-3-in-a-project-right-now-screencast","Start using Vue 3 in a project right now (Screencast)",[651,162559,162560],{},[660,162561],{"alt":162562,"src":162563},"start-using-vue3-today-cover.png","/images/newsletter/2020/01/19/start-using-vue3-today-cover.png",[651,162565,70143],{},[651,162567,70146],{},[651,162569,162570],{},[812,162571,162572],{"href":162572,"rel":162573},"https://www.danvega.dev/blog/2020/01/17/start-using-vue3-today/",[816],[5909,162575,162577],{"id":162576},"grammarly-visual-studio-code-extension","Grammarly Visual Studio Code Extension",[651,162579,162580],{},[660,162581],{"alt":162582,"src":162583},"Grammarly_Visual_Studio_Code_Extension.png","/images/newsletter/2020/01/19/Grammarly_Visual_Studio_Code_Extension.png",[651,162585,162586],{},"In this tutorial, I am going to show you a new extension that brings Grammarly support to Visual Studio Code. Personally, I have been interested in Grammarly for Code for a long time now so you can imagine how excited I was to find out someone released an extension.",[651,162588,162589],{},"I will walk through how to find it, install it, use it and how you can customize it. I also show another extension that you might need to disable to get this to work properly. I have only been using Grammarly for Visual Studio Code for a few days now but it's already become one of my favorite extensions.",[651,162591,162592],{},[812,162593,162594],{"href":162594,"rel":162595},"https://youtu.be/Pfmrs9F7S-g",[816],[5909,162597,162599],{"id":162598},"_7000-youtube-subscribers","7,000+ YouTube Subscribers",[651,162601,162602],{},"I hit another big milestone this week as I crossed 7,000 subscribers on YouTube. I want to thank all of you for subscribing and if you haven't yet, what are you waiting for 😉I uploaded two more videos this week and it's a constant learning process for me. I don't have a big room with natural light so I am always experimenting with new ways to record tutorials. If you have any feedback for me or tutorials you would like to see please don't hesitate reaching out.",[651,162604,162605],{},[812,162606,22072],{"href":22072,"rel":162607},[816],[651,162609,162610],{},[812,162611,162612],{"href":162612,"rel":162613},"https://youtu.be/o-jiS563yI8",[816],[4542,162615,157574],{"id":157573},[651,162617,157577],{},[5909,162619,69849],{"id":69848},[5316,162621,162622,162629,162636],{},[5332,162623,162624],{},[812,162625,162628],{"href":162626,"rel":162627},"https://illustrated.dev/resources/",[816],"Illustrated.dev resources to improve your drawing and visual thinking skills.",[5332,162630,162631],{},[812,162632,162635],{"href":162633,"rel":162634},"https://znck.dev/blog/2019-grammarly-in-code",[816],"How Rahul Kadyan built a Grammarly Extension for Visual Studio Code",[5332,162637,162638],{},[812,162639,162642],{"href":162640,"rel":162641},"https://blogs.windows.com/msedgedev/2020/01/15/upgrading-new-microsoft-edge-79-chromium/",[816],"The new Microsoft Edge is out of preview and available for download",[5909,162644,120990],{"id":157591},[5316,162646,162647,162654],{},[5332,162648,162649],{},[812,162650,162653],{"href":162651,"rel":162652},"https://www.youtube.com/watch?v=DD1fEhcEzY8",[816],"Test driven development with Vue.js by Sarah Dayan",[5332,162655,162656],{},[812,162657,162660],{"href":162658,"rel":162659},"https://www.youtube.com/watch?v=R50q4NES6Iw",[816],"dotCSS 2019 - Sarah Dayan - In Defense of Utility-First CSS",[5909,162662,26378],{"id":39439},[5316,162664,162665],{},[5332,162666,162667],{},[812,162668,162671],{"href":162669,"rel":162670},"https://syntax.fm/show/213/hasty-treat-a-month-on-firefox",[816],"Syntax FM: Hasty Treat - A Month On Firefox",[5909,162673,21973],{"id":21972},[5316,162675,162676],{},[5332,162677,162678],{},[812,162679,162680],{"href":162680,"rel":162681},"https://egghead.io/playlists/introduction-to-tailwind-and-the-utility-first-workflow-0b697b10",[816],[5909,162683,157635],{"id":133422},[5316,162685,162686,162692,162699,162706],{},[5332,162687,162688],{},[812,162689,162577],{"href":162690,"rel":162691},"https://marketplace.visualstudio.com/items?itemName=znck.grammarly",[816],[5332,162693,162694],{},[812,162695,162698],{"href":162696,"rel":162697},"https://uses.tech/",[816],"A list of /uses pages detailing developer setups, gear, software and configs.",[5332,162700,162701],{},[812,162702,162705],{"href":162703,"rel":162704},"https://github.com/jakubroztocil/httpie/releases/tag/2.0.0",[816],"HTTPie 2.0",[5332,162707,162708],{},[812,162709,162712],{"href":162710,"rel":162711},"https://www.jetbrains.com/lp/mono/",[816],"JetBrains Mono",[4542,162714,157704],{"id":157703},[651,162716,157910],{},[651,162718,41105,162719,69920,162721,162723,162725,162727],{},[41107,162720],{},[41107,162722],{},[812,162724,161560],{"href":161111},[41107,162726],{},[812,162728,53869],{"href":82688,"rel":162729},[816],{"title":674,"searchDepth":790,"depth":790,"links":162731},[162732,162738,162745],{"id":157510,"depth":790,"text":157511,"children":162733},[162734,162735,162736,162737],{"id":69934,"depth":892,"text":159595},{"id":162556,"depth":892,"text":162557},{"id":162576,"depth":892,"text":162577},{"id":162598,"depth":892,"text":162599},{"id":157573,"depth":790,"text":157574,"children":162739},[162740,162741,162742,162743,162744],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":21972,"depth":892,"text":21973},{"id":133422,"depth":892,"text":157635},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #21 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":162748,"issue":3139,"date":162749},"coffee-and-code-newsletter-22","2020-01-19T07:00:00.000Z","/newsletter/2020/01/19/22",{"title":162516,"description":162746},"newsletter/2020/01/19/22","sOYazO4_tae1TlVr0N4kC9pRBKhuLk7_rOrmhKu8t-A",{"id":162755,"title":162756,"body":162757,"description":162980,"extension":793,"meta":162981,"navigation":797,"path":162984,"seo":162985,"stem":162986,"__hash__":162987},"content/newsletter/2020/01/26/23.md","Coffee & Code Newsletter: #23",{"type":648,"value":162758,"toc":162964},[162759,162766,162772,162774,162777,162781,162789,162795,162799,162802,162808,162812,162821,162823,162825,162827,162843,162845,162861,162863,162886,162888,162904,162906,162915,162917,162947,162949,162951],[651,162760,162761,162762,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #22 ",[812,162763,18263],{"href":162764,"rel":162765},"https://www.danvega.dev/newsletter/coffee-and-code/22",[816],[651,162767,162768],{},[660,162769],{"alt":162770,"src":162771},"Coffee and Code #23 Cover","/images/newsletter/2020/01/26/coffee_and_code_23.png",[4542,162773,157511],{"id":157510},[651,162775,162776],{},"It was another busy week for me and I am finally getting back into the swing of things and I finally feel like the new year has finally started. With that let's get into it...",[5909,162778,162780],{"id":162779},"vue-3-fragments-video","Vue 3 Fragments Video",[651,162782,162783,162788],{},[812,162784,162787],{"href":162785,"rel":162786},"https://youtu.be/iC9smVwm7GE",[816],"In this tutorial,"," you are going to learn all about a new feature in Vue 3 called Fragments. You will learn about the problem as it exists in Vue 2 and how Vue 3 is making this issue go away.",[651,162790,162791],{},[660,162792],{"alt":162793,"src":162794},"Vue 3 Fragments","/images/newsletter/2020/01/26/vue3_fragments_thumbnail.png",[5909,162796,162798],{"id":162797},"thumbnail-design","Thumbnail Design",[651,162800,162801],{},"The one thing that always stresses me out when I create a new video is the thumbnail. I know they are really important when it comes to YouTube but it takes me way too long to come up with a good design. I started playing with a new design this week that I could reuse for every tutorial and just switch up the theme based on the content. I would love your thoughts on the image above, below and the overall theme.",[651,162803,162804],{},[660,162805],{"alt":162806,"src":162807},"Grammarly extension for Visual Studio Code","/images/newsletter/2020/01/26/grammarly_visual_studio_code_extension_thumbnail.png",[5909,162809,162811],{"id":162810},"new-domain-name","New domain name",[651,162813,162814,162815,162820],{},"I am the king of buying new domain names and letting them sit. With that said I bought a new domain this week that I think I am actually going to do something with. The domain is ",[812,162816,162819],{"href":162817,"rel":162818},"http://www.intervue.com",[816],"www.intervue.com"," and my thought is to do some kind of application that helps anyone going for a new job where Vue is the primary framework. There will be questions around HTML, CSS, JavaScript and of course Vue. If you have any interest in this please let me know.",[4542,162822,157574],{"id":157573},[651,162824,157577],{},[5909,162826,69849],{"id":69848},[5316,162828,162829,162836],{},[5332,162830,162831],{},[812,162832,162835],{"href":162833,"rel":162834},"https://gist.github.com/chrisvfritz/52490a5d2e42876edcf7d7e5f94dee87",[816],"Chris Fritz is leaving the Vue Core Team",[5332,162837,162838],{},[812,162839,162842],{"href":162840,"rel":162841},"https://blog.jetbrains.com/idea/2020/01/intellij-idea-2020-1-eap/",[816],"IntelliJ IDEA 2020.1 EAP",[5909,162844,120990],{"id":157591},[5316,162846,162847,162854],{},[5332,162848,162849],{},[812,162850,162853],{"href":162851,"rel":162852},"https://www.youtube.com/watch?v=vRUGJZK129Y",[816],"Toddcast - Episode 2: RetroPie, Elementary OS And The M5Stack",[5332,162855,162856],{},[812,162857,162860],{"href":162858,"rel":162859},"https://www.twitch.tv/videos/540247999",[816],"JavaScript & Friends: Gridsome Introduction by Ray Camden",[5909,162862,26378],{"id":39439},[5316,162864,162865,162872,162879],{},[5332,162866,162867],{},[812,162868,162871],{"href":162869,"rel":162870},"https://twitter.com/WVFRM/status/1220669306205364224",[816],"Waveform: How we make our YouTube Videos",[5332,162873,162874],{},[812,162875,162878],{"href":162876,"rel":162877},"https://syntax.fm/show/216/tech-to-watch-in-2020",[816],"Syntax FM: Tech to Watch in 2020",[5332,162880,162881],{},[812,162882,162885],{"href":162883,"rel":162884},"https://www.ladybug.dev/20-tips-for-devs",[816],"Ladybug Podcast: 20 design tips for developers in 2020",[5909,162887,21973],{"id":21972},[5316,162889,162890,162897],{},[5332,162891,162892],{},[812,162893,162896],{"href":162894,"rel":162895},"https://www.udacity.com/course/cloud-developer-nanodegree--nd9990",[816],"Udacity: Become a cloud developer",[5332,162898,162899],{},[812,162900,162903],{"href":162901,"rel":162902},"https://www.vuemastery.com/courses/unit-testing/what-to-test",[816],"Vue Mastery: Unit Testing",[5909,162905,6016],{"id":39340},[5316,162907,162908],{},[5332,162909,162910],{},[812,162911,162914],{"href":162912,"rel":162913},"https://itrevolution.com/the-unicorn-project/",[816],"The Unicorn Project",[5909,162916,157635],{"id":133422},[5316,162918,162919,162926,162933,162940],{},[5332,162920,162921],{},[812,162922,162925],{"href":162923,"rel":162924},"https://www.vuescreencasts.com/",[816],"Vue Screencasts Launches",[5332,162927,162928],{},[812,162929,162932],{"href":162930,"rel":162931},"https://www.channelsstack.com/",[816],"Channels Stack",[5332,162934,162935],{},[812,162936,162939],{"href":162937,"rel":162938},"https://tableplus.com/",[816],"TablePlus",[5332,162941,162942],{},[812,162943,162946],{"href":162944,"rel":162945},"https://miragejs.com/",[816],"Mirage JS: An API Mocking Library for Frontend developers",[4542,162948,157704],{"id":157703},[651,162950,157910],{},[651,162952,41105,162953,69920,162955,162957,162959,162961],{},[41107,162954],{},[41107,162956],{},[812,162958,161560],{"href":161111},[41107,162960],{},[812,162962,53869],{"href":82688,"rel":162963},[816],{"title":674,"searchDepth":790,"depth":790,"links":162965},[162966,162971,162979],{"id":157510,"depth":790,"text":157511,"children":162967},[162968,162969,162970],{"id":162779,"depth":892,"text":162780},{"id":162797,"depth":892,"text":162798},{"id":162810,"depth":892,"text":162811},{"id":157573,"depth":790,"text":157574,"children":162972},[162973,162974,162975,162976,162977,162978],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":21972,"depth":892,"text":21973},{"id":39340,"depth":892,"text":6016},{"id":133422,"depth":892,"text":157635},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #22 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":162982,"issue":3144,"date":162983},"coffee-and-code-newsletter-23","2020-01-26T07:00:00.000Z","/newsletter/2020/01/26/23",{"title":162756,"description":162980},"newsletter/2020/01/26/23","zs4cH_yQi90rJTrEoLViXR8TSzCoWz2eUCFY64zCrD4",{"id":162989,"title":162990,"body":162991,"description":163224,"extension":793,"meta":163225,"navigation":797,"path":163228,"seo":163229,"stem":163230,"__hash__":163231},"content/newsletter/2020/02/02/24.md","Coffee & Code Newsletter: #24",{"type":648,"value":162992,"toc":163206},[162993,163001,163007,163009,163012,163016,163025,163031,163035,163038,163044,163046,163049,163053,163056,163062,163064,163067,163069,163071,163073,163096,163098,163121,163123,163146,163148,163157,163159,163189,163191,163193],[651,162994,162995,162996,160081],{},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout ",[812,162997,163000],{"href":162998,"rel":162999},"https://www.danvega.dev/newsletter/coffee-and-code/23",[816],"Coffee and Code #23 here",[651,163002,163003],{},[660,163004],{"alt":163005,"src":163006},"Coffee & Code: 24","/images/newsletter/2020/02/02/coffee_and_code_24.png",[4542,163008,157511],{"id":157510},[651,163010,163011],{},"Thanks for everyone's feedback on my YouTube thumbnail art. I heard from a couple folks that the font was hard to read so I ended up switch it up to what you see above. With that let's get into this weeks topics.",[4542,163013,163015],{"id":163014},"vuejs-for-beginners","Vue.JS for beginners",[651,163017,163018,163019,163024],{},"If you follow me at all you probably already know this but I recently released a new course called ",[812,163020,163023],{"href":163021,"rel":163022},"https://www.youtube.com/redirect?v=zjYBw7lN8X4&event=video_description&redir_token=yQh8clKfvyxIDONs8rOkFaN8zGx8MTU4MDY3MzY3OUAxNTgwNTg3Mjc5&q=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Fvue-intro%2F%3FreferralCode%3DE9DECFF78CA706D7A68A",[816],"Vue.js for Beginners: Up and Running with Vue",". I haven't really mentioned it to my YouTube audience yet so I thought I would do that this week. I recorded a quick video with my motivation for creating the course and I played the course trailer. If you're interested in watching that video the link is below and If you're not a subscriber on YouTube yet, what are you waiting for 😉",[651,163026,163027],{},[812,163028,163029],{"href":163029,"rel":163030},"https://youtu.be/zjYBw7lN8X4",[816],[5909,163032,163034],{"id":163033},"stir-trek-call-for-papers","Stir Trek Call for papers",[651,163036,163037],{},"By the time you're reading this, the call for papers for Stir Trek 2020 has ended. As usual, I waited until the very last minute to submit a talk but I was able to submit one. The title for the talk is \"Vue 3: Smaller, Faster & Stronger\". This is the talk I gave to CodeMash but I have already made a few updates and if this gets accepted I have some ideas for a few more.",[651,163039,163040],{},[812,163041,163042],{"href":163042,"rel":163043},"https://stirtrek.com/",[816],[4542,163045,157550],{"id":157549},[651,163047,163048],{},"What I have planned for this week...",[5909,163050,163052],{"id":163051},"vue-3-smaller-faster-stronger","Vue 3: Smaller, Faster Stronger",[651,163054,163055],{},"If you're in the Cleveland area I will be giving a presentation on Vue 3. This is the same presentation I gave at CodeMash and I am excited to give it again.",[651,163057,163058],{},[812,163059,163060],{"href":163060,"rel":163061},"https://www.meetup.com/vuecle/events/267933150/",[816],[5909,163063,159606],{"id":159605},[651,163065,163066],{},"I should have another 2 videos out this week. The first is going to be on building a silly app in Vue called Are You Rob Base. I had a lot of fun building this out and I hope you learn something from it. The 2nd I haven't recorded yet so I don't want to announce it just yet but if you have a topic you would like to see me cover please let me know.",[4542,163068,157574],{"id":157573},[651,163070,157577],{},[5909,163072,69849],{"id":69848},[5316,163074,163075,163082,163089],{},[5332,163076,163077],{},[812,163078,163081],{"href":163079,"rel":163080},"https://www.raymondcamden.com/2020/01/27/vue-and-form-fields",[816],"Ray Camden: Vue and Form Fields",[5332,163083,163084],{},[812,163085,163088],{"href":163086,"rel":163087},"https://dev.to/philnash/how-to-find-cfps-for-developer-conferences-307n",[816],"How to find CFPs for developer conferences",[5332,163090,163091],{},[812,163092,163095],{"href":163093,"rel":163094},"https://dev.to/vuevixens/2019-retrospective-and-2020-roadmap-41m2",[816],"Vue Vixens: 2019 Retrospective and 2020 Roadmap",[5909,163097,120990],{"id":157591},[5316,163099,163100,163107,163114],{},[5332,163101,163102],{},[812,163103,163106],{"href":163104,"rel":163105},"https://www.youtube.com/channel/UCmXVXfidLZQkppLPaATcHag",[816],"Chris on Code",[5332,163108,163109],{},[812,163110,163113],{"href":163111,"rel":163112},"https://www.youtube.com/watch?v=qaGjS7-qWzg",[816],"Is reduce() bad? - HTTP 203",[5332,163115,163116],{},[812,163117,163120],{"href":163118,"rel":163119},"https://www.youtube.com/channel/UCoSvlWS5XcwaSzIcbuJ-Ysg",[816],"Notion Office Hours: YouTube",[5909,163122,26378],{"id":39439},[5316,163124,163125,163132,163139],{},[5332,163126,163127],{},[812,163128,163131],{"href":163129,"rel":163130},"http://www.fullstackradio.com/133",[816],"Full Stack Radio: 133: Sam Selikoff - Building Production-Ready SPAs Fast with Mirage.js",[5332,163133,163134],{},[812,163135,163138],{"href":163136,"rel":163137},"https://syntax.fm/show/217/hasty-treat-building-a-community-slack-discord-spectrum-discourse-forums",[816],"Hasty Treat - Building A Community Slack, Discord, Spectrum, Discourse, Forums",[5332,163140,163141],{},[812,163142,163145],{"href":163143,"rel":163144},"https://syntax.fm/show/215/hasty-treat-picking-the-stack-for-uses-tech-gatsby-react-context-styled-components",[816],"Syntax FM: Hasty Treat - Picking the Stack for uses.tech - Gatsby, React, Context, Styled Components",[5909,163147,6016],{"id":39340},[5316,163149,163150],{},[5332,163151,163152],{},[812,163153,163156],{"href":163154,"rel":163155},"https://leanpub.com/ydkjsy-get-started",[816],"You Don't Know JS Yet: Get Started",[5909,163158,157635],{"id":133422},[5316,163160,163161,163168,163175,163182],{},[5332,163162,163163],{},[812,163164,163167],{"href":163165,"rel":163166},"https://roughjs.com/",[816],"Rough.js: Create graphics with a hand-drawn, sketchy, appearance",[5332,163169,163170],{},[812,163171,163174],{"href":163172,"rel":163173},"https://tiny-helpers.dev/",[816],"Tiny Helpers: A collection of free single-purpose online tools for web developers...",[5332,163176,163177],{},[812,163178,163181],{"href":163179,"rel":163180},"https://vscodethemes.com/",[816],"VS Code Themes Showcase: Preview themes from the VSCode marketplace.",[5332,163183,163184],{},[812,163185,163188],{"href":163186,"rel":163187},"https://github.com/vuejs/vuepress/blob/master/CHANGELOG.md",[816],"VuePress 1.3.0: Minimalistic Vue-powered static site generator",[4542,163190,157704],{"id":157703},[651,163192,157910],{},[651,163194,41105,163195,69920,163197,163199,163201,163203],{},[41107,163196],{},[41107,163198],{},[812,163200,161560],{"href":161111},[41107,163202],{},[812,163204,53869],{"href":82688,"rel":163205},[816],{"title":674,"searchDepth":790,"depth":790,"links":163207},[163208,163209,163212,163216,163223],{"id":157510,"depth":790,"text":157511},{"id":163014,"depth":790,"text":163015,"children":163210},[163211],{"id":163033,"depth":892,"text":163034},{"id":157549,"depth":790,"text":157550,"children":163213},[163214,163215],{"id":163051,"depth":892,"text":163052},{"id":159605,"depth":892,"text":159606},{"id":157573,"depth":790,"text":157574,"children":163217},[163218,163219,163220,163221,163222],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":39340,"depth":892,"text":6016},{"id":133422,"depth":892,"text":157635},{"id":157703,"depth":790,"text":157704},"Welcome to Coffee and Code with me, Dan Vega. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you missed last week's newsletter you can checkout Coffee and Code #23 here. If you don't already have a cup of coffee grab one now and let's get to it.",{"slug":163226,"issue":3149,"date":163227},"coffee-and-code-newsletter-24","2020-02-02T07:00:00.000Z","/newsletter/2020/02/02/24",{"title":162990,"description":163224},"newsletter/2020/02/02/24","eRVN4GoNBPpwTG1EpL-kV12iOq0AR1w0FFLXRWxzBEU",{"id":163233,"title":163234,"body":163235,"description":163239,"extension":793,"meta":163486,"navigation":797,"path":163489,"seo":163490,"stem":163491,"__hash__":163492},"content/newsletter/2020/02/09/25.md","Coffee & Code Newsletter: #25",{"type":648,"value":163236,"toc":163468},[163237,163240,163246,163248,163251,163255,163264,163268,163276,163280,163283,163289,163291,163294,163296,163298,163300,163330,163332,163355,163357,163380,163382,163405,163407,163416,163418,163440,163442,163451,163453,163455],[651,163238,163239],{},"Welcome to Coffee and Code with me, Dan Vega for the week of Feb 2 - Feb 9, 2020. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you don't already have one grab a cup and your favorite beverage and let's get to it.",[651,163241,163242],{},[660,163243],{"alt":163244,"src":163245},"Coffee and Code #25 Cover","/images/newsletter/2020/02/09/coffee_and_code_25.png",[4542,163247,157511],{"id":157510},[651,163249,163250],{},"It was a busy week along with some pretty exciting personal news, so let's get into it...",[5909,163252,163254],{"id":163253},"are-you-rob-base","Are you Rob Base?",[651,163256,163257,163258,163263],{},"This week I put out a video where I wrote a silly application in Vue called ",[812,163259,163262],{"href":163260,"rel":163261},"https://www.youtube.com/watch?v=_dPX3tT3EnU&feature=youtu.be",[816],"Are you Rob Base",". I start out the tutorial by talking about how as a developer we need to keep building things, even if they seem silly. You can read all the articles, watch all the videos but until you start writing code you just aren't going to learn. I have started to take my own advice and have been building out some smaller demos. I hope you find this tutorial useful and learned something new.",[5909,163265,163267],{"id":163266},"vue-30-smaller-faster-and-stronger","Vue 3.0 Smaller, Faster and Stronger",[651,163269,163270,163271,163275],{},"My talk from CodeMash was recorded and it has been [published on Pluralsight](Vue 3.0 Better, Faster, and Stronger: CodeMash). You should be able to watch the presentation even if you aren't a Pluralsight subscriber. I also gave this presentation to the ",[812,163272,163274],{"href":163060,"rel":163273},[816],"Vue CLE Meetup"," this week and was happy to see a great turnout and some really good conversation about Vue 3.",[5909,163277,163279],{"id":163278},"personal-news","Personal News",[651,163281,163282],{},"In a bit of personal news, I am excited to finally share that our family will be growing in August. We had a gender reveal party this weekend with family and found out that Bella is going to have a baby sister. I can't wait to meet this little angel and we are officially on the clock!",[651,163284,163285],{},[660,163286],{"alt":163287,"src":163288},"Gender Party","/images/newsletter/2020/02/09/gender-party.jpeg",[4542,163290,157550],{"id":157549},[651,163292,163293],{},"I think I have simplified my screencasting setup and hope to produce more videos. My plan for this week is to release 2 new videos on YouTube and I am going to be adding some new content to my Getting Started with Vue.js course. Stay tuned!!",[4542,163295,157574],{"id":157573},[651,163297,157577],{},[5909,163299,69849],{"id":69848},[5316,163301,163302,163309,163316,163323],{},[5332,163303,163304],{},[812,163305,163308],{"href":163306,"rel":163307},"https://mattstauffer.com/blog/setting-up-your-webcam-lights-and-audio-for-remote-work-podcasting-videos-and-streaming/",[816],"Setting Up Your Webcam, Lights, and Audio for Remote Work, Podcasting, Videos, and Streaming",[5332,163310,163311],{},[812,163312,163315],{"href":163313,"rel":163314},"https://www.smashingmagazine.com/2020/02/headless-wordpress-site-jamstack/",[816],"How To Create A Headless WordPress Site On The JAMstack — Smashing Magazine",[5332,163317,163318],{},[812,163319,163322],{"href":163320,"rel":163321},"https://www.raymondcamden.com/2020/02/01/building-table-sorting-and-pagination-in-vuejs-with-async-data",[816],"Building Table Sorting and Pagination in Vue.js - with Async Data",[5332,163324,163325],{},[812,163326,163329],{"href":163327,"rel":163328},"https://www.cypress.io/blog/2020/02/06/introducing-firefox-and-edge-support-in-cypress-4-0",[816],"Introducing Firefox and Edge Support in Cypress 4.0",[5909,163331,120990],{"id":157591},[5316,163333,163334,163341,163348],{},[5332,163335,163336],{},[812,163337,163340],{"href":163338,"rel":163339},"https://www.youtube.com/watch?v=d4I35tkZnNg",[816],"Mirage JS with Vue",[5332,163342,163343],{},[812,163344,163347],{"href":163345,"rel":163346},"https://www.youtube.com/watch?v=QpJOpgsS_SA",[816],"Groovy Podcast ep. 78 (S04E04)",[5332,163349,163350],{},[812,163351,163354],{"href":163352,"rel":163353},"https://www.youtube.com/watch?v=VHVKYDIEtaI",[816],"Building sustainable enterprise apps with Vue.js - Chris Fritz",[5909,163356,26378],{"id":39439},[5316,163358,163359,163366,163373],{},[5332,163360,163361],{},[812,163362,163365],{"href":163363,"rel":163364},"https://enjoythevue.io/episodes/1/",[816],"Enjoy the Vue: Welcome to Vue: Meet your panel",[5332,163367,163368],{},[812,163369,163372],{"href":163370,"rel":163371},"https://enjoythevue.io/episodes/2/",[816],"Enjoy the Vue: What We Love About Vue CLI",[5332,163374,163375],{},[812,163376,163379],{"href":163377,"rel":163378},"https://changelog.com/gotime/115",[816],"Grokking Go.dev",[5909,163381,157635],{"id":133422},[5316,163383,163384,163391,163398],{},[5332,163385,163386],{},[812,163387,163390],{"href":163388,"rel":163389},"https://github.com/tailwindcss/tailwindcss/releases/tag/v1.2.0#user-content-css-grid-support",[816],"Tailwind CSS: Release v1.2.0",[5332,163392,163393],{},[812,163394,163397],{"href":163395,"rel":163396},"https://go.dev/",[816],"Go.dev: Getting Started with Go",[5332,163399,163400],{},[812,163401,163404],{"href":163402,"rel":163403},"https://code.visualstudio.com/updates/v1_42#_contributions-to-extensions",[816],"Visual Studio Code January 2020",[5909,163406,21973],{"id":21972},[5316,163408,163409],{},[5332,163410,163411],{},[812,163412,163415],{"href":163413,"rel":163414},"https://www.freecodecamp.org/news/here-are-380-ivy-league-courses-you-can-take-online-right-now-for-free-9b3ffcbd7b8c/",[816],"Here are 450 Ivy League courses you can take online right now for free",[5909,163417,160020],{"id":160019},[5316,163419,163420,163426,163433],{},[5332,163421,163422],{},[812,163423,43525],{"href":163424,"rel":163425},"https://news.vuejs.org/issues/173",[816],[5332,163427,163428],{},[812,163429,163432],{"href":163430,"rel":163431},"https://kenkousen.substack.com/p/tales-from-the-jar-side-kotlin-android",[816],"Tales from the jar side: Kotlin, Android, Spring, and LIV",[5332,163434,163435],{},[812,163436,163439],{"href":163437,"rel":163438},"https://www.baeldung.com/java-weekly-319",[816],"Java Weekly, Issue 319 | Baeldung",[5909,163441,79609],{"id":79608},[5316,163443,163444],{},[5332,163445,163446],{},[812,163447,163450],{"href":163448,"rel":163449},"https://www.pluralsight.com/search?q=CodeMash&categories=course",[816],"CodeMash Conference Recordings",[4542,163452,157704],{"id":157703},[651,163454,157910],{},[651,163456,41105,163457,69920,163459,163461,163463,163465],{},[41107,163458],{},[41107,163460],{},[812,163462,161560],{"href":161111},[41107,163464],{},[812,163466,53869],{"href":82688,"rel":163467},[816],{"title":674,"searchDepth":790,"depth":790,"links":163469},[163470,163475,163476,163485],{"id":157510,"depth":790,"text":157511,"children":163471},[163472,163473,163474],{"id":163253,"depth":892,"text":163254},{"id":163266,"depth":892,"text":163267},{"id":163278,"depth":892,"text":163279},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":163477},[163478,163479,163480,163481,163482,163483,163484],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":160019,"depth":892,"text":160020},{"id":79608,"depth":892,"text":79609},{"id":157703,"depth":790,"text":157704},{"slug":163487,"issue":3169,"date":163488},"coffee-and-code-newsletter-25","2020-02-09T07:00:00.000Z","/newsletter/2020/02/09/25",{"title":163234,"description":163239},"newsletter/2020/02/09/25","rFVdOYHxHz6VKC1mHLSbsGKNsE2JdRwBD-ODI9HpgYU",{"id":163494,"title":163495,"body":163496,"description":163500,"extension":793,"meta":163769,"navigation":797,"path":163772,"seo":163773,"stem":163774,"__hash__":163775},"content/newsletter/2020/02/16/26.md","Coffee & Code Newsletter: #26",{"type":648,"value":163497,"toc":163755},[163498,163501,163507,163509,163517,163520,163522,163529,163535,163542,163548,163550,163557,163580,163582,163584,163586,163616,163618,163648,163650,163673,163675,163698,163700,163709,163711,163720,163722,163738,163740,163742],[651,163499,163500],{},"Welcome to Coffee and Code with me, Dan Vega for the week of Feb 9 - Feb 15, 2020. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you don't already have one grab a cup and your favorite beverage and let's get to it.",[651,163502,163503],{},[660,163504],{"alt":163505,"src":163506},"Coffee and Code 26","/images/newsletter/2020/02/16/coffee_and_code_26.png",[4542,163508,157511],{"id":157510},[651,163510,163511,163512,23026,163514,163516],{},"This week I was able to publish a detailed article and video on ",[676,163513,70948],{},[676,163515,71550],{}," in Vue 3.",[651,163518,163519],{},"I think what I am most excited about Vue 3 is to see how other developers embrace it and use it. While I have had a chance to play with it over the last few months I know that isn't the case for everyone.",[651,163521,70556],{},[651,163523,163524,163525,163528],{},"I have ",[812,163526,70562],{"href":70106,"rel":163527},[816]," of presentations on Vue 3 recently and one question that keeps coming up is when do I use Ref vs Reactive to declare a reactive property. I never had a great answer for this so over the past couple of weeks I set out to answer this question and this article is the result of that research.",[651,163530,163531],{},[660,163532],{"alt":163533,"src":163534},"Vue 3: Ref vs Reactive","/images/newsletter/2020/02/16/vue3-ref-vs-reactive-cover.png",[651,163536,163537,163538,664],{},"You can read the rest of the article using the link below or check out the video on the same topic ",[812,163539,18263],{"href":163540,"rel":163541},"https://youtu.be/cRwG96MOHp0",[816],[651,163543,163544],{},[812,163545,163546],{"href":163546,"rel":163547},"https://www.danvega.dev/blog/2020/02/12/vue3-ref-vs-reactive/",[816],[4542,163549,157550],{"id":157549},[651,163551,163552,163553,163556],{},"I am going to try something new this week. I have content ready to go for the following topics and I would like you to help me pick which video to work on next. All you have to do is respond to this email or let me know on ",[812,163554,51474],{"href":44086,"rel":163555},[816],". You can also contact me If you would like to see something that is not on this list.",[5316,163558,163559,163562,163565,163568,163574,163577],{},[5332,163560,163561],{},"Groovy 3 has been released, let's take a look at the new features (Multiple videos)",[5332,163563,163564],{},"What is my Visual Studio Code theme and some tips on how to find new ones.",[5332,163566,163567],{},"HankerRank 2020 Developer Report",[5332,163569,40060,163570,163573],{},[676,163571,163572],{},"watch()"," method in Vue 3 Composition API.",[5332,163575,163576],{},"Converting a Vue 2 component into a Vue 3 Component",[5332,163578,163579],{},"Using JSON-Server in a Vue app as an API to prototype an application.",[4542,163581,157574],{"id":157573},[651,163583,157577],{},[5909,163585,69849],{"id":69848},[5316,163587,163588,163595,163602,163609],{},[5332,163589,163590],{},[812,163591,163594],{"href":163592,"rel":163593},"https://www.sanity.io/blog/introducing-squizzy-our-totally-serverless-kahoot-clone-powered-by-sanity-io",[816],"Introducing Squizzy: Our totally serverless Kahoot clone powered by Sanity.io",[5332,163596,163597],{},[812,163598,163601],{"href":163599,"rel":163600},"https://www.vuemastery.com/blog/Refresh-Proof-Vue-Forms-with-Local-Storage/",[816],"Refresh-Proof Vue Forms with Local Storage | Vue Mastery",[5332,163603,163604],{},[812,163605,163608],{"href":163606,"rel":163607},"https://www.smashingmagazine.com/2020/02/api-mocking-mirage-vue-javascript/",[816],"Setting Up API Mocking With Mirage JS And Vue.js — Smashing Magazine",[5332,163610,163611],{},[812,163612,163615],{"href":163613,"rel":163614},"https://spring.io/blog/2020/02/14/announcing-the-new-spring-website",[816],"Announcing: The NEW Spring Website!",[5909,163617,120990],{"id":157591},[5316,163619,163620,163627,163634,163641],{},[5332,163621,163622],{},[812,163623,163626],{"href":163624,"rel":163625},"https://www.youtube.com/watch?v=wbp_ro-eWwQ",[816],"Vuex visually explained by Adam Jahr",[5332,163628,163629],{},[812,163630,163633],{"href":163631,"rel":163632},"https://www.youtube.com/watch?v=1M9PzQlH1Nk",[816],"10 Tips to Edit 10x Faster in Premiere Pro",[5332,163635,163636],{},[812,163637,163640],{"href":163638,"rel":163639},"https://www.youtube.com/watch?v=2EmYw-O-WLI&feature=emb_title",[816],"Vue.js: The Documentary (TRAILER)",[5332,163642,163643],{},[812,163644,163647],{"href":163645,"rel":163646},"https://www.youtube.com/watch?v=BI3xnQ_iJ3I&feature=emb_title",[816],"Vue 3 for library authors by Damian Dulisz - YouTube",[5909,163649,26378],{"id":39439},[5316,163651,163652,163659,163666],{},[5332,163653,163654],{},[812,163655,163658],{"href":163656,"rel":163657},"https://changelog.com/jsparty/113",[816],"JS Party: Fullstack D3 with Amelia Wattenberger",[5332,163660,163661],{},[812,163662,163665],{"href":163663,"rel":163664},"https://shoptalkshow.com/398/",[816],"Shop Talk Show: An Event Apart, Subgrid, Grid, Chrome engine, & more with Eric Meyer",[5332,163667,163668],{},[812,163669,163672],{"href":163670,"rel":163671},"https://changelog.com/gotime/116",[816],"Go Time: Unusual uses for Go: GUIs",[5909,163674,157635],{"id":133422},[5316,163676,163677,163684,163691],{},[5332,163678,163679],{},[812,163680,163683],{"href":163681,"rel":163682},"http://groovy-lang.org/releasenotes/groovy-3.0.html",[816],"Groovy 3.0 Released",[5332,163685,163686],{},[812,163687,163690],{"href":163688,"rel":163689},"https://github.blog/2020-02-12-supercharge-your-command-line-experience-github-cli-is-now-in-beta",[816],"Supercharge your command line experience: GitHub CLI is now in beta - The GitHub Blog",[5332,163692,163693],{},[812,163694,163697],{"href":163695,"rel":163696},"https://blog.angular.io/version-9-of-angular-now-available-project-ivy-has-arrived-23c97b63cfa3",[816],"Version 9 of Angular Now Available — Project Ivy has arrived!",[5909,163699,6016],{"id":39340},[5316,163701,163702],{},[5332,163703,163704],{},[812,163705,163708],{"href":163706,"rel":163707},"https://leanpub.com/reactive-spring",[816],"Reactive Spring by Josh Long",[5909,163710,21973],{"id":21972},[5316,163712,163713],{},[5332,163714,163715],{},[812,163716,163719],{"href":163717,"rel":163718},"https://egghead.io/playlists/building-a-serverless-jamstack-todo-app-with-netlify-gatsby-graphql-and-faunadb-53bb",[816],"Building a Serverless JAMStack Todo app with Netlify, Gatsby, GraphQL, and FaunaDB on @eggheadio",[5909,163721,160020],{"id":160019},[5316,163723,163724,163731],{},[5332,163725,163726],{},[812,163727,163730],{"href":163728,"rel":163729},"http://groovycalamari.com/issues/173#start",[816],"Groovy Calamari - Issue 173",[5332,163732,163733],{},[812,163734,163737],{"href":163735,"rel":163736},"https://blog.jetbrains.com/idea/2020/02/java-annotated-monthly-february-2020",[816],"Java Annotated Monthly – February 2020",[4542,163739,157704],{"id":157703},[651,163741,157910],{},[651,163743,41105,163744,69920,163746,163748,163750,163752],{},[41107,163745],{},[41107,163747],{},[812,163749,161560],{"href":161111},[41107,163751],{},[812,163753,53869],{"href":82688,"rel":163754},[816],{"title":674,"searchDepth":790,"depth":790,"links":163756},[163757,163758,163759,163768],{"id":157510,"depth":790,"text":157511},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":163760},[163761,163762,163763,163764,163765,163766,163767],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":39340,"depth":892,"text":6016},{"id":21972,"depth":892,"text":21973},{"id":160019,"depth":892,"text":160020},{"id":157703,"depth":790,"text":157704},{"slug":163770,"issue":3185,"date":163771},"coffee-and-code-newsletter-26","2020-02-16T07:00:00.000Z","/newsletter/2020/02/16/26",{"title":163495,"description":163500},"newsletter/2020/02/16/26","_i8y-mEmMHC7Gb44YQHBB23wOcmQgM27pCXC0Se4uE8",{"id":163777,"title":163778,"body":163779,"description":163783,"extension":793,"meta":164065,"navigation":797,"path":164068,"seo":164069,"stem":164070,"__hash__":164071},"content/newsletter/2020/02/23/27.md","Coffee & Code Newsletter: #27",{"type":648,"value":163780,"toc":164049},[163781,163784,163786,163803,163811,163821,163824,163828,163831,163837,163846,163852,163854,163868,163870,163873,163875,163877,163879,163909,163911,163934,163936,163959,163961,163998,164000,164009,164011,164032,164034,164036],[651,163782,163783],{},"Welcome to Coffee and Code with me, Dan Vega for the week of Feb 17 - Feb 23, 2020. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you don't already have one grab a cup and your favorite beverage and let's get to it.",[4542,163785,157511],{"id":157510},[651,163787,159616,163788,163793,163794,163799,163800,163802],{},[812,163789,163792],{"href":163790,"rel":163791},"https://youtu.be/BkiM3qu-2vo",[816],"posted a new video"," where I discussed the results of the ",[812,163795,163798],{"href":163796,"rel":163797},"https://research.hackerrank.com/developer-skills/2020",[816],"2020 HackerRank Developer Skills Report",". In this report, one of the metrics that I thought was really interesting was that ",[2939,163801,79540],{}," was the #1 programming language that developers wanted to learn this year. I started learning Go towards the end of last year and I couldn't agree more, it's a fascinating language and I hope to share more of that journey soon.",[651,163804,163805,163806,163810],{},"The title for the video that I posted was ",[812,163807,163809],{"href":163790,"rel":163808},[816],"The #1 Programming Language that Developers want to learn in 2020",". I had a couple of people mention to me that it sounded clickbaity and while I would agree with that I need to defend my thought process and offer up an alternative way of thinking about it.",[651,163812,163813,163814,23212,163817,163820],{},"I think there is a clear distinction between ",[2939,163815,163816],{},"click bait",[2939,163818,163819],{},"clickable titles",". When it comes to click bait you are deceiving the viewer for clicks and I don't ever want to do that. As a small YouTuber who really wants to grow his channel I need to do a couple of things. First and this is the big one, I need to be more consistent with the tutorials. Second, I also need to create content that will reach a larger audience.",[651,163822,163823],{},"This is why you might see more experiments with this type of content so I would love to hear your feedback.",[4542,163825,163827],{"id":163826},"traversy-media","Traversy Media",[651,163829,163830],{},"While we are on the subject of YouTube I need to take a minute and congratulate Brad Traversy. If you don't know Brad is a YouTuber who teaches a wide variety of programming topics on YouTube. Brad creates some amazing tutorials and is the epitome of consistency. He crossed 1 million subscribers this week and it is a well-deserved milestone. Congrats Brad and looking forward to you hitting 2 million.",[651,163832,163833],{},[660,163834],{"alt":163835,"src":163836},"Brad Traversy","/images/newsletter/2020/02/23/traversymedia.png",[651,163838,163839,163840,163845],{},"I watched a tutorial from Brad this week on creating a clone of ",[812,163841,163844],{"href":163842,"rel":163843},"http://microsoft.com",[816],"Microsoft.com"," using CSS Grid, Flex, and media queries. It was really good and got it me thinking about creating a similar tutorial using Tailwind CSS. I have had a lot of fun building layouts with Tailwind lately and I thought I would like to share those. I have an initial version on my Github and if you're interested in YouTube tutorials on Tailwind please let me know.",[651,163847,163848],{},[812,163849,163850],{"href":163850,"rel":163851},"https://github.com/danvega/microsoft-wind",[816],[5909,163853,43866],{"id":44169},[651,163855,163856,163857,163862,163863,23026,163865,163867],{},"A huge thank you to Anthony Gore for including me in this weeks ",[812,163858,163861],{"href":163859,"rel":163860},"https://vuejsdevelopers.com/newsletter/issue/156/",[816],"Vue.js Developers Newsletter",". My article on when to use ",[676,163864,70948],{},[676,163866,71550],{}," was including in the Newsletter and I am grateful for being included with so many great articles.",[4542,163869,157550],{"id":157549},[651,163871,163872],{},"My focus is on YouTube and I should be able to release two videos this week. I want to try and get on a consistent schedule so my thought is Tuesday and Friday but as you know plans can always change. Look out for a video on how to connect IntelliJ's Database Tools to an H2 database in a Spring Boot application & Visual Studio Code themes.",[4542,163874,157574],{"id":157573},[651,163876,157577],{},[5909,163878,69849],{"id":69848},[5316,163880,163881,163888,163895,163902],{},[5332,163882,163883],{},[812,163884,163887],{"href":163885,"rel":163886},"https://www.raymondcamden.com/2020/02/17/ionicons-in-vue",[816],"Ray Camden: Ionicons in Vue.js",[5332,163889,163890],{},[812,163891,163894],{"href":163892,"rel":163893},"https://blog.logrocket.com/charting-with-vue-a-comparison/",[816],"Ray Camden: Charting with Vue: A comparison",[5332,163896,163897],{},[812,163898,163901],{"href":163899,"rel":163900},"https://joelhooks.com/self-paced-email-course",[816],"Creating a self-paced email course with ConvertKit, Typeform, and Zapier",[5332,163903,163904],{},[812,163905,163908],{"href":163906,"rel":163907},"https://css-tricks.com/getting-acquainted-with-svelte-the-new-framework-on-the-block",[816],"Getting Acquainted With Svelte, the New Framework on the Block",[5909,163910,120990],{"id":157591},[5316,163912,163913,163920,163927],{},[5332,163914,163915],{},[812,163916,163919],{"href":163917,"rel":163918},"https://www.youtube.com/watch?v=eiUgVa2Td_k",[816],"The State of Vue.js in 2020 - Why You Should Make The Leap - Gwendolyn Faraday - YouTube",[5332,163921,163922],{},[812,163923,163926],{"href":163924,"rel":163925},"https://www.youtube.com/watch?v=uKgn-To1C4Q",[816],"Microsoft Homepage Clone - CSS Grid, Flex & Media Queries",[5332,163928,163929],{},[812,163930,163933],{"href":163931,"rel":163932},"https://www.youtube.com/watch?v=6zIuAyLZPH0",[816],"Get Started with Tailwind CSS in 15 Minutes",[5909,163935,26378],{"id":39439},[5316,163937,163938,163945,163952],{},[5332,163939,163940],{},[812,163941,163944],{"href":163942,"rel":163943},"https://enjoythevue.io/episodes/4/",[816],"Enjoy the Vue: JAMming, MCing, Vuex & More with Divya Sasidharan",[5332,163946,163947],{},[812,163948,163951],{"href":163949,"rel":163950},"https://changelog.com/gotime/118",[816],"Go Time: Quack like a wha-?",[5332,163953,163954],{},[812,163955,163958],{"href":163956,"rel":163957},"https://syntax.fm/show/224/serverless-cloud-functions-part-1",[816],"Syntax.fm: Serverless / Cloud Functions - Part 1",[5909,163960,157635],{"id":133422},[5316,163962,163963,163970,163977,163984,163991],{},[5332,163964,163965],{},[812,163966,163969],{"href":163967,"rel":163968},"https://devblogs.microsoft.com/typescript/announcing-typescript-3-8/",[816],"Announcing TypeScript 3.8 | TypeScript",[5332,163971,163972],{},[812,163973,163976],{"href":163974,"rel":163975},"https://shopify.github.io/draggable/",[816],"Draggable JS – JavaScript drag and drop library",[5332,163978,163979],{},[812,163980,163983],{"href":163981,"rel":163982},"https://ishadeed.com/article/learn-css-positioning",[816],"Learn CSS Positioning with 😸",[5332,163985,163986],{},[812,163987,163990],{"href":163988,"rel":163989},"https://github.com/evanw/esbuild/",[816],"esbuild: JavaScript Bundler written in Go",[5332,163992,163993],{},[812,163994,163997],{"href":163995,"rel":163996},"https://gitexplorer.com/",[816],"Git Command Explorer",[5909,163999,21973],{"id":21972},[5316,164001,164002],{},[5332,164003,164004],{},[812,164005,164008],{"href":164006,"rel":164007},"https://www.jetbrains.com/lp/academy/",[816],"JetBrains Academy: Java, Kotlin & Python Courses",[5909,164010,160020],{"id":160019},[5316,164012,164013,164020,164026],{},[5332,164014,164015],{},[812,164016,164019],{"href":164017,"rel":164018},"https://kenkousen.substack.com/p/tales-from-the-jar-side-spring-training",[816],"Tales from the jar side: Spring training, Groovy 3, and other updates",[5332,164021,164022],{},[812,164023,164025],{"href":163859,"rel":164024},[816],"Vue.js Developers Newsletter Issue #156",[5332,164027,164028],{},[812,164029,160556],{"href":164030,"rel":164031},"http://groovycalamari.com/issues/174?#start",[816],[4542,164033,157704],{"id":157703},[651,164035,157910],{},[651,164037,41105,164038,69920,164040,164042,164044,164046],{},[41107,164039],{},[41107,164041],{},[812,164043,161560],{"href":161111},[41107,164045],{},[812,164047,53869],{"href":82688,"rel":164048},[816],{"title":674,"searchDepth":790,"depth":790,"links":164050},[164051,164052,164055,164056,164064],{"id":157510,"depth":790,"text":157511},{"id":163826,"depth":790,"text":163827,"children":164053},[164054],{"id":44169,"depth":892,"text":43866},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":164057},[164058,164059,164060,164061,164062,164063],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":21972,"depth":892,"text":21973},{"id":160019,"depth":892,"text":160020},{"id":157703,"depth":790,"text":157704},{"slug":164066,"issue":3194,"date":164067},"coffee-and-code-newsletter-27","2020-02-23T07:00:00.000Z","/newsletter/2020/02/23/27",{"title":163778,"description":163783},"newsletter/2020/02/23/27","Vc_c9508pasXyjyvDcvdqdN-SqJDy4AqosFwdxM8fm0",{"id":164073,"title":164074,"body":164075,"description":164079,"extension":793,"meta":164351,"navigation":797,"path":164354,"seo":164355,"stem":164356,"__hash__":164357},"content/newsletter/2020/03/01/28.md","Coffee & Code Newsletter: #28",{"type":648,"value":164076,"toc":164335},[164077,164080,164082,164085,164091,164100,164104,164110,164113,164119,164123,164129,164132,164135,164141,164143,164146,164148,164150,164152,164189,164191,164214,164216,164239,164241,164264,164268,164291,164293,164316,164318,164320],[651,164078,164079],{},"Welcome to Coffee and Code with me, Dan Vega for the week of Feb 23 - Feb 29, 2020. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you don't already have one grab a cup and your favorite beverage and let's get to it.",[4542,164081,157511],{"id":157510},[651,164083,164084],{},"This week I added two new videos to my YouTube channel. If you have been following along I have some big goals for my channel and I have had a lot of fun learning about what does and doesn't work on YouTube. In a surprise to nobody, I have noticed an increase in all the important metrics on my channel over the last month in which I have been more consistent with uploading videos.",[651,164086,164087],{},[660,164088],{"alt":164089,"src":164090},"YouTube Analytics","/images/newsletter/2020/03/01/youtube_analytics.png",[651,164092,164093,164094,164099],{},"So now the only question is how do I get more consistent with limited time to work on it. My goal for March is to upload 2-3 videos and see if this trend continues. If you haven't already ",[812,164095,164098],{"href":164096,"rel":164097},"https://www.youtube.com/danvega",[816],"subscribed to my channel"," what are you waiting for 🤔",[5909,164101,164103],{"id":164102},"spring-boot-java-h2-database-setup-in-intellij","Spring Boot Java H2 Database Setup in IntelliJ",[651,164105,164106],{},[660,164107],{"alt":164108,"src":164109},"Spring Boot H2 IntelliJ Database Tools","/images/newsletter/2020/03/01/spring-boot-h2-database-thumbnail.png",[651,164111,164112],{},"In this tutorial, you are going to learn how to connect to an H2 database using IntelliJ's database tools. We will start by creating a new Spring Boot application with the Web, H2, JPA and DevTools dependencies. We will learn how to configure the H2 database and provide a schema and seed data. Finally, we will create an H2 file database and connect to it from IntelliJ's database tools.",[651,164114,164115],{},[812,164116,164117],{"href":164117,"rel":164118},"https://youtu.be/8QBJMxyXIqc",[816],[5909,164120,164122],{"id":164121},"how-to-find-new-and-popular-vscode-themes-and-my-favorite-vscode-theme","How to find new and popular vscode themes and my favorite vscode theme",[651,164124,164125],{},[660,164126],{"alt":164127,"src":164128},"Visual Studio Code Theme","/images/newsletter/2020/03/01/favorite_vscode_theme.png",[651,164130,164131],{},"In this video, you are going to learn about my favorite Visual Studio Code theme and how you can find yours. We make a big deal about customizing themes in our IDE and text editors because it's the first thing that we see.",[651,164133,164134],{},"There are so many great vscode themes out there and I am excited to talk about my favorite theme, Synthwave 84. We will also take a look at how to find the most popular, recent and highest rated themes. Finally, I will show you a website that will display popular vscode themes in a thumbnail view so you can easily find a theme that matches your style.",[651,164136,164137],{},[812,164138,164139],{"href":164139,"rel":164140},"https://youtu.be/NuHCSe_VAX4",[816],[4542,164142,157550],{"id":157549},[651,164144,164145],{},"I will have at least two new videos going up this week and maybe a third. In one video I am going to get back to some basics of Spring Boot with a new quick start guide walkthrough. In another, I sat down and recording my presentation from CodeMash, Vue 3: Smaller, Faster & Stronger. As always, If you have any suggestions on videos you would like to see please let me know.",[4542,164147,157574],{"id":157573},[651,164149,157577],{},[5909,164151,69849],{"id":69848},[5316,164153,164154,164161,164168,164175,164182],{},[5332,164155,164156],{},[812,164157,164160],{"href":164158,"rel":164159},"https://dev.to/lemuelogbunude/introducing-java-14-records-3od3",[816],"Introducing Java 14 Records",[5332,164162,164163],{},[812,164164,164167],{"href":164165,"rel":164166},"https://buttercms.com/blog/how-to-build-a-plugin-with-gridsome",[816],"How To Build A Plugin With Gridsome",[5332,164169,164170],{},[812,164171,164174],{"href":164172,"rel":164173},"https://www.smashingmagazine.com/2020/02/music-manager-nuxtjs-expressjs/",[816],"How To Build A Music Manager With Nuxt.js And Express.js",[5332,164176,164177],{},[812,164178,164181],{"href":164179,"rel":164180},"https://techcrunch.com/2020/02/26/hasura-raises-9-9m-series-a-to-simplify-graphql-for-developers/",[816],"Hasura raises $9.9M Series A to simplify GraphQL for developers",[5332,164183,164184],{},[812,164185,164188],{"href":164186,"rel":164187},"https://www.engadget.com/2020/02/27/35-raspberry-pi-4-double-ram-2-GB/",[816],"The $35 Raspberry Pi 4 now comes with double the RAM",[5909,164190,120990],{"id":157591},[5316,164192,164193,164200,164207],{},[5332,164194,164195],{},[812,164196,164199],{"href":164197,"rel":164198},"https://www.youtube.com/watch?v=6m3OdxgtKpI",[816],"One Simple Thing Big YouTubers Do to Grow (That Most of Us Don't)",[5332,164201,164202],{},[812,164203,164206],{"href":164204,"rel":164205},"https://www.youtube.com/watch?v=OrxmtDw4pVI",[816],"Vue.js: The Documentary",[5332,164208,164209],{},[812,164210,164213],{"href":164211,"rel":164212},"https://www.youtube.com/watch?v=7dUTsi0Kxuw&t=0s",[816],"Scott Tries Whimsical - Flowcharts, Wireframes, Sticky Notes, and Mind Maps",[5909,164215,26378],{"id":39439},[5316,164217,164218,164225,164232],{},[5332,164219,164220],{},[812,164221,164224],{"href":164222,"rel":164223},"https://enjoythevue.io/episodes/5",[816],"Enjoy the Vue: Productivity Tools, Workflows & Tabs vs Spaces",[5332,164226,164227],{},[812,164228,164231],{"href":164229,"rel":164230},"https://nofluffjuststuff.com/podcast/1/java_is_not_dead",[816],"NFJS: Is Java Dead?",[5332,164233,164234],{},[812,164235,164238],{"href":164236,"rel":164237},"https://changelog.com/gotime/119",[816],"Go Time: Stop the presses",[5909,164240,157635],{"id":133422},[5316,164242,164243,164250,164257],{},[5332,164244,164245],{},[812,164246,164249],{"href":164247,"rel":164248},"https://tailwindui.com/",[816],"Tailwind UI Early Access",[5332,164251,164252],{},[812,164253,164256],{"href":164254,"rel":164255},"https://codepen.io/sdras/full/RwwQapa",[816],"How does the Virtual DOM work?",[5332,164258,164259],{},[812,164260,164263],{"href":164261,"rel":164262},"https://www.heroicons.com/",[816],"Heroicons are a unique set of icons for your marketing website that make it easy to customize with CSS to better align to your brand.",[5909,164265,164267],{"id":164266},"training","Training",[5316,164269,164270,164277,164284],{},[5332,164271,164272],{},[812,164273,164276],{"href":164274,"rel":164275},"https://learning.oreilly.com/live-training/courses/reactive-spring-and-spring-boot/0636920357261/",[816],"O'Reilly Live Training: Reactive Spring and Spring Boot (Ken Kousen)",[5332,164278,164279],{},[812,164280,164283],{"href":164281,"rel":164282},"https://learning.oreilly.com/live-training/courses/kotlin-spring-boot-essentials/0636920370468/",[816],"O'Reilly Live Training: Kotlin + Spring Boot Essentials (Ken Kousen)",[5332,164285,164286],{},[812,164287,164290],{"href":164288,"rel":164289},"https://learning.oreilly.com/live-training/courses/practical-go-modules/0636920365570/",[816],"O'Reilly Live Training: Practical Go Modules (Benjamin Muschko)",[5909,164292,160020],{"id":160019},[5316,164294,164295,164302,164309],{},[5332,164296,164297],{},[812,164298,164301],{"href":164299,"rel":164300},"https://kenkousen.substack.com/p/tales-from-the-jar-side-devnexus",[816],"Tales from the jar side: DevNexus, Null Pointers, and Hunting the Wild Kotlin",[5332,164303,164304],{},[812,164305,164308],{"href":164306,"rel":164307},"https://news.vuejs.org/issues/176",[816],"Official Vue.js Newsletter: #176",[5332,164310,164311],{},[812,164312,164315],{"href":164313,"rel":164314},"https://www.baeldung.com/java-weekly-322",[816],"Java Weekly, Issue 322",[4542,164317,157704],{"id":157703},[651,164319,157910],{},[651,164321,41105,164322,69920,164324,164326,164328,164330,164333],{},[41107,164323],{},[41107,164325],{},[812,164327,161560],{"href":161111},[41107,164329],{},[812,164331,53869],{"href":82688,"rel":164332},[816],[41107,164334],{},{"title":674,"searchDepth":790,"depth":790,"links":164336},[164337,164341,164342,164350],{"id":157510,"depth":790,"text":157511,"children":164338},[164339,164340],{"id":164102,"depth":892,"text":164103},{"id":164121,"depth":892,"text":164122},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":164343},[164344,164345,164346,164347,164348,164349],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":164266,"depth":892,"text":164267},{"id":160019,"depth":892,"text":160020},{"id":157703,"depth":790,"text":157704},{"slug":164352,"issue":3199,"date":164353},"coffee-and-code-newsletter-28","2020-03-01T07:00:00.000Z","/newsletter/2020/03/01/28",{"title":164074,"description":164079},"newsletter/2020/03/01/28","MVzfK9raDOTTJcbVNFRnD-Qr9sD3YM1KO--2GKthDSo",{"id":164359,"title":164360,"body":164361,"description":164365,"extension":793,"meta":164617,"navigation":797,"path":164620,"seo":164621,"stem":164622,"__hash__":164623},"content/newsletter/2020/03/08/29.md","Coffee & Code Newsletter: #29",{"type":648,"value":164362,"toc":164603},[164363,164366,164368,164371,164377,164383,164387,164390,164396,164399,164404,164407,164413,164417,164419,164425,164430,164433,164439,164441,164444,164446,164448,164450,164480,164482,164491,164493,164516,164518,164548,164550,164586,164588,164590],[651,164364,164365],{},"Welcome to Coffee and Code with me, Dan Vega for the week of Mar 1 - Mar 7, 2020. This newsletter is a little insight into what I was up to this week, things I found interesting and anything on my radar for the upcoming week. If you don't already have one grab a cup and your favorite beverage and let's get to it.",[4542,164367,157511],{"id":157510},[651,164369,164370],{},"The news this week was dominated by the Coronavirus and rightfully so. My Twitter timeline was filled with news of so many conferences canceling. While it was probably a hard decision for the conference organizers, it was the right one.",[651,164372,164373,164374,664],{},"Next, I want to say thank you to my friends over at JS & Friends Conference for mentioning me in the ",[2939,164375,164376],{},"Developer Community Spotlight",[651,164378,164379],{},[812,164380,164381],{"href":164381,"rel":164382},"https://twitter.com/JSFriendsConf/status/1235792222986989568",[816],[5909,164384,164386],{"id":164385},"youtube-channel-updates","YouTube Channel Updates",[651,164388,164389],{},"I mentioned this last week but to nobody's surprise when you get more consistent with your content you can expect to see improvements in your analytics. I am really encouraged and excited to see these numbers going up and I hope this trend continues. If you have been following along I have some pretty big goals and goal #1 of 10,000 subscribers is insight.",[651,164391,164392],{},[660,164393],{"alt":164394,"src":164395},"Analytics","/images/newsletter/2020/03/08/analytics.png",[651,164397,164398],{},"I produced three new videos this week, I hope you enjoy them!",[651,164400,164401],{},[2939,164402,164403],{},"Spring Boot Quick Start: Spring Boot Tutorial for Beginners",[651,164405,164406],{},"In this presentation, I am going to talk to you about some of the new and exciting features coming to Vue 3. I originally put this presentation together for a conference here in Ohio called CodeMash. I put a lot of work into this presentation and have only given it twice now so I decided to sit down and record it for YouTube.",[651,164408,164409],{},[812,164410,164411],{"href":164411,"rel":164412},"https://youtu.be/df7Dso9q700",[816],[651,164414,164415],{},[2939,164416,70086],{},[651,164418,164406],{},[651,164420,164421],{},[812,164422,164423],{"href":164423,"rel":164424},"https://youtu.be/HmdKqXP8JR8",[816],[651,164426,164427],{},[2939,164428,164429],{},"Creating Your First Github Pull Request",[651,164431,164432],{},"In this tutorial, I am going to show you how to create a simple pull request by submitting your first pull request to Github. After we walk through submitting your first pull request to Github I am going to show you how to find issues that you can work on in some of your favorite open-source projects. If you want to contribute on open-source projects these days it is important to understand how to find issues that you can work on and understand the process for getting your changes into a project.",[651,164434,164435],{},[812,164436,164437],{"href":164437,"rel":164438},"https://youtu.be/TWPXAbPVcio",[816],[4542,164440,157550],{"id":157549},[651,164442,164443],{},"This week I am working on something really exciting that I hoping to share with you very soon. I was able to record 3 videos this weekend so as long as I can get them edited I will have them published",[4542,164445,157574],{"id":157573},[651,164447,157577],{},[5909,164449,69849],{"id":69848},[5316,164451,164452,164459,164466,164473],{},[5332,164453,164454],{},[812,164455,164458],{"href":164456,"rel":164457},"https://blog.golang.org/a-new-go-api-for-protocol-buffers",[816],"A new Go API for Protocol Buffers",[5332,164460,164461],{},[812,164462,164465],{"href":164463,"rel":164464},"https://kousenit.org/2020/03/03/why-use-mocks/",[816],"Why Use Mocks?",[5332,164467,164468],{},[812,164469,164472],{"href":164470,"rel":164471},"https://www.raymondcamden.com/2020/03/04/vue-quick-shot-using-a-loading-message",[816],"Vue Quick Shot - Using a Loading Message",[5332,164474,164475],{},[812,164476,164479],{"href":164477,"rel":164478},"https://www.netlify.com/press/after-onboarding-800000-developers-netlify-raises-53m-in-series-c-funding-to-fuel-enterprise-growth/",[816],"After Onboarding 800,000 Developers, Netlify Raises $53M in Series C Funding to Fuel Enterprise Growth | Netlify",[5909,164481,120990],{"id":157591},[5316,164483,164484],{},[5332,164485,164486],{},[812,164487,164490],{"href":164488,"rel":164489},"https://www.vuemastery.com/free-weekend/",[816],"Vue Mastery Free Weekend",[5909,164492,26378],{"id":39439},[5316,164494,164495,164502,164509],{},[5332,164496,164497],{},[812,164498,164501],{"href":164499,"rel":164500},"http://www.fullstackradio.com/135",[816],"Full Stack Radio: 35: Lessons Learned Building Tailwind UI",[5332,164503,164504],{},[812,164505,164508],{"href":164506,"rel":164507},"https://www.thatcreative.life/episodes/stephenhackett",[816],"That Creative Life: Stephen Hackett - Co-founder of Relay FM",[5332,164510,164511],{},[812,164512,164515],{"href":164513,"rel":164514},"https://syntax.fm/show/228/more-on-severless-databases-files-secrets-auth-more",[816],"More on Severless - Databases × Files × Secrets × Auth × More!",[5909,164517,157635],{"id":133422},[5316,164519,164520,164527,164534,164541],{},[5332,164521,164522],{},[812,164523,164526],{"href":164524,"rel":164525},"https://github.com/quarkusio/quarkus/releases/tag/1.3.0.CR1",[816],"Quarkus 1.3.0",[5332,164528,164529],{},[812,164530,164533],{"href":164531,"rel":164532},"https://www.notion.vip/icons/",[816],"Notion Icons",[5332,164535,164536],{},[812,164537,164540],{"href":164538,"rel":164539},"http://knutsynstad.com/fauxcode/",[816],"Faux Code Generator",[5332,164542,164543],{},[812,164544,164547],{"href":164545,"rel":164546},"https://pattle.github.io/simpsons-in-css/",[816],"The Simpsons in CSS",[5909,164549,160020],{"id":160019},[5316,164551,164552,164559,164566,164573,164580],{},[5332,164553,164554],{},[812,164555,164558],{"href":164556,"rel":164557},"https://kenkousen.substack.com/p/tales-from-the-jar-side-the-start",[816],"Tales from the jar side: The Start of the No Fluff, Just Stuff Season",[5332,164560,164561],{},[812,164562,164565],{"href":164563,"rel":164564},"https://blog.jetbrains.com/idea/2020/03/java-annotated-monthly-march-2020/",[816],"Java Annotated Monthly – March 2020",[5332,164567,164568],{},[812,164569,164572],{"href":164570,"rel":164571},"https://www.baeldung.com/java-weekly-323",[816],"Java Weekly, Issue 323",[5332,164574,164575],{},[812,164576,164579],{"href":164577,"rel":164578},"https://blogs.oracle.com/javamagazine/",[816],"Java Magazine",[5332,164581,164582],{},[812,164583,160556],{"href":164584,"rel":164585},"http://groovycalamari.com/issues/175",[816],[4542,164587,157704],{"id":157703},[651,164589,157910],{},[651,164591,41105,164592,69920,164594,164596,164598,164600],{},[41107,164593],{},[41107,164595],{},[812,164597,161560],{"href":161111},[41107,164599],{},[812,164601,53869],{"href":82688,"rel":164602},[816],{"title":674,"searchDepth":790,"depth":790,"links":164604},[164605,164608,164609,164616],{"id":157510,"depth":790,"text":157511,"children":164606},[164607],{"id":164385,"depth":892,"text":164386},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":164610},[164611,164612,164613,164614,164615],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":160019,"depth":892,"text":160020},{"id":157703,"depth":790,"text":157704},{"slug":164618,"issue":3212,"date":164619},"coffee-and-code-newsletter-29","2020-03-08T07:00:00.000Z","/newsletter/2020/03/08/29",{"title":164360,"description":164365},"newsletter/2020/03/08/29","PAE45mGem-MF_FQkkcOZrXM-fesfrlzUR6RN-iirxvs",{"id":164625,"title":164626,"body":164627,"description":164631,"extension":793,"meta":164874,"navigation":797,"path":164877,"seo":164878,"stem":164879,"__hash__":164880},"content/newsletter/2020/03/15/30.md","Coffee & Code Newsletter: #30",{"type":648,"value":164628,"toc":164859},[164629,164632,164635,164637,164640,164646,164649,164652,164655,164658,164664,164669,164672,164674,164677,164700,164704,164717,164719,164722,164724,164726,164728,164758,164760,164776,164778,164801,164803,164819,164821,164837,164843,164846],[651,164630,164631],{},"Welcome to Coffee and Code with me, Dan Vega for the week of Mar 8 - Feb 14, 2020.",[651,164633,164634],{},"It's a scary time in my home, city, state, country, and world. I know that this won't last forever but it is a reminder to all of us that the problems we have on a day to day basis are pretty minor in comparison. I hope all of you are doing well and it's my job to take your mind off of what's going on and do what I love to do, teach. If you don't already have one grab a cup and your favorite beverage and let's get to it.",[4542,164636,157511],{"id":157510},[651,164638,164639],{},"This week started with a couple of tweets from ✌🏻YouTubers that I look up to.",[651,164641,164642],{},[812,164643,164644],{"href":164644,"rel":164645},"https://twitter.com/MKBHD/status/1236710481441763335",[816],[651,164647,164648],{},"This was a great reminder from two channels that have over 30 million subscribers combined. I know my videos from a production standpoint aren't great but what they lack in transitions and effects they make up for with my passion for the topic. I am confident that I have a unique way of teaching and 20 years of programming experience to lean on.",[651,164650,164651],{},"After reading these tweets and spending a little time reflecting on them I have some thoughts on how I can improve going forward. Every single creator I look up to is constantly reminding me that it is my job to tell a story. I need to stop looking at a tutorial as a series of steps to resolution and an opportunity to put my personal experience behind it. There is a story in every tutorial, review or discussion, I just need to find it.",[651,164653,164654],{},"It's hard to do 20 years later but I need to remind myself of just how lost I felt at times when I started programming. I would often search for answers to my problems and come across very technical articles that were supposed to answer my question but left me scratching my head.",[651,164656,164657],{},"That is part of the reason I started a blog 15 years ago. I knew there was a better way to explain solutions to those of us who aren't computer science geniuses. I was reminded this week by my friend Brian on Twitter that I was able to take something that can be complicated and break it down to a few easy steps (Thanks, Brian!)",[651,164659,164660],{},[812,164661,164662],{"href":164662,"rel":164663},"https://twitter.com/Crashbox/status/1237754447666823168",[816],[1004,164665,164666],{},[651,164667,164668],{},"There are no unique messages, only unique messengers.",[651,164670,164671],{},"If you can't tell I am really into this. I love to learn and I love helping others. Something strange happened over the last year though, I have started to fall in love with the entire video creation process. I am currently a sponge and trying to soak up everything I can. I know that was a bit of a tangent but what it comes down to is you will probably see me doing a variety of experiments so I can see what works.",[5909,164673,15432],{"id":61224},[651,164675,164676],{},"I was able to upload 3 new videos this week and I am pretty happy with the way they turned out.",[5316,164678,164679,164686,164693],{},[5332,164680,164681],{},[812,164682,164685],{"href":164683,"rel":164684},"https://youtu.be/pNiRNRgi5Ws",[816],"Spring Boot Unit vs Integration Tests",[5332,164687,164688],{},[812,164689,164692],{"href":164690,"rel":164691},"https://youtu.be/RboyQ6IQDyI",[816],"Create A Full Fake REST API With JSON Server in a Vue application",[5332,164694,164695],{},[812,164696,164699],{"href":164697,"rel":164698},"https://youtu.be/5MmlRZZxTqk",[816],"Java 11 HttpClient",[5909,164701,164703],{"id":164702},"mentions","Mentions",[651,164705,164706,164707,164711,164712,164716],{},"Thank you to Anthony Gore and the ",[812,164708,163861],{"href":164709,"rel":164710},"https://vuejsdevelopers.com/newsletter/issue/159/",[816]," for featuring my ",[812,164713,163052],{"href":164714,"rel":164715},"https://www.youtube.com/watch?v=HmdKqXP8JR8",[816]," video.",[4542,164718,157550],{"id":157549},[651,164720,164721],{},"This newsletter is a little bit of a milestone for me as we hit #30. It might not seem like a lot but committing to a newsletter each week for 30 weeks (minus a couple of weeks off during the holidays) is something I am really proud of 🥳 With that said I am not sure how much longer I am going to continue it in this format. I am going to take a break from my weekly cadence and figure out what I want this newsletter to look like. If you have any feedback please reach out to me.",[4542,164723,157574],{"id":157573},[651,164725,157577],{},[5909,164727,69849],{"id":69848},[5316,164729,164730,164737,164744,164751],{},[5332,164731,164732],{},[812,164733,164736],{"href":164734,"rel":164735},"https://www.netlify.com/blog/2020/03/10/reactivity-in-vue-3",[816],"Reactivity in Vue 3",[5332,164738,164739],{},[812,164740,164743],{"href":164741,"rel":164742},"https://insomnia.rest/blog/plugin-hub",[816],"Insomnia REST Client: Plugin Hub 1.0",[5332,164745,164746],{},[812,164747,164750],{"href":164748,"rel":164749},"https://techcrunch.com/2020/03/04/twitter-starts-testing-its-own-version-of-stories-called-fleets-which-disappear-after-24-hours/",[816],"Twitter \"Fleets\" that disappear after 24 hours",[5332,164752,164753],{},[812,164754,164757],{"href":164755,"rel":164756},"https://joelhooks.com/dSLR-webcam-for-live-streaming",[816],"Joel Hooks: Using a dSLR as a Webcam for Live Streaming",[5909,164759,120990],{"id":157591},[5316,164761,164762,164769],{},[5332,164763,164764],{},[812,164765,164768],{"href":164766,"rel":164767},"https://www.youtube.com/watch?v=sqt04KPErTY",[816],"Two Big Announcements: Academind Pro & A Brand-New Course",[5332,164770,164771],{},[812,164772,164775],{"href":164773,"rel":164774},"https://www.freecodecamp.org/news/jamstack-full-course/",[816],"JAMstack Tutorial – How to Build Fast, Secure Websites – a Free 4-hour course",[5909,164777,26378],{"id":39439},[5316,164779,164780,164787,164794],{},[5332,164781,164782],{},[812,164783,164786],{"href":164784,"rel":164785},"https://changelog.com/gotime/121",[816],"Go Time: Pow! Pow! Power tools!",[5332,164788,164789],{},[812,164790,164793],{"href":164791,"rel":164792},"https://www.thatcreative.life/episodes/mkbhd",[816],"That Creative Life: MKBHD - His Expanding Tech Empire, 10 Million Subscribers and Are We At \"Peak Smartphone\"?",[5332,164795,164796],{},[812,164797,164800],{"href":164798,"rel":164799},"https://www.ecpodcast.io/episodes/23-eve-porcello-alex-banks-how-to-build-a-business-as-a-workshop-instructor",[816],"The Entrepreneurial Coder Podcast: Eve Porcello & Alex Banks - How to Build a Business as a Workshop Instructor",[5909,164802,157635],{"id":133422},[5316,164804,164805,164812],{},[5332,164806,164807],{},[812,164808,164811],{"href":164809,"rel":164810},"https://code.visualstudio.com/updates/v1_43",[816],"Visual Studio Code: February 2020 (version 1.43)",[5332,164813,164814],{},[812,164815,164818],{"href":164816,"rel":164817},"https://twitter.com/vuejs/status/1237753080885710858",[816],"Vue 3 Roadmap Updates",[5909,164820,160020],{"id":160019},[5316,164822,164823,164830],{},[5332,164824,164825],{},[812,164826,164829],{"href":164827,"rel":164828},"https://kenkousen.substack.com/p/tales-from-the-jar-side-reactive",[816],"Tales from the jar side: Reactive Spring, Kotlin, and Coronavirus Jokes",[5332,164831,164832],{},[812,164833,164836],{"href":164834,"rel":164835},"https://content.pivotal.io/josh-blog/this-month-in-spring-march-2020",[816],"This Month in Spring - March 2020",[4542,164838,164839,164840],{"id":157703},"Until Next ",[104514,164841,164842],{},"Week",[651,164844,164845],{},"Thank you for reading my words, watching my videos and sharing this journey with me friends. I hope you have a great week and as always friends...",[651,164847,41105,164848,69920,164850,164852,164854,164856],{},[41107,164849],{},[41107,164851],{},[812,164853,161560],{"href":161111},[41107,164855],{},[812,164857,53869],{"href":82688,"rel":164858},[816],{"title":674,"searchDepth":790,"depth":790,"links":164860},[164861,164865,164866,164873],{"id":157510,"depth":790,"text":157511,"children":164862},[164863,164864],{"id":61224,"depth":892,"text":15432},{"id":164702,"depth":892,"text":164703},{"id":157549,"depth":790,"text":157550},{"id":157573,"depth":790,"text":157574,"children":164867},[164868,164869,164870,164871,164872],{"id":69848,"depth":892,"text":69849},{"id":157591,"depth":892,"text":120990},{"id":39439,"depth":892,"text":26378},{"id":133422,"depth":892,"text":157635},{"id":160019,"depth":892,"text":160020},{"id":157703,"depth":790,"text":157704},{"slug":164875,"issue":3217,"date":164876},"coffee-and-code-newsletter-30","2020-03-15T07:00:00.000Z","/newsletter/2020/03/15/30",{"title":164626,"description":164631},"newsletter/2020/03/15/30","b85_Y8_vlV2iIKGK1IZpSgF4jOqCi3S54gaYKoS70Is",{"id":164882,"title":164883,"body":164884,"description":164888,"extension":793,"meta":165053,"navigation":797,"path":165056,"seo":165057,"stem":165058,"__hash__":165059},"content/newsletter/2021/09/27/im-back.md","I'm back",{"type":648,"value":164885,"toc":165039},[164886,164889,164892,164901,164903,164912,164914,164917,164922,164926,164935,164940,164947,164953,164955,164957,164960,164969,164972,164981,164984,164993,164996,165003,165006,165014,165018,165021,165023,165025],[651,164887,164888],{},"Hello friends,",[651,164890,164891],{},"After a long absence from the newsletter I am excited to be back and writing to you. The reason I started this newsletter is because I enjoy writing and this keeps me doing so on weekly basis. I ended up stopping in March of 2020 right when the pandemic hit and until recently I honestly didn't feel like writing. The last year and half was tough on me mentally but we won't get into that here.",[651,164893,164894,164895,164900],{},"The week started out great with a ",[812,164896,164899],{"href":164897,"rel":164898},"https://twitter.com/therealdanvega/status/1439945285371891712",[816],"Victory Monday"," as my Cleveland Browns got a win on Sunday. The game was at home and I was able to attend my first sporting event in a long long time which was exciting.",[4542,164902,82997],{"id":82996},[651,164904,164905,164906,164911],{},"I recently gave a presentation at SpringOne on full-stack development with Spring Boot and VueJS. The recording for that talk has been posted on the ",[812,164907,164910],{"href":164908,"rel":164909},"https://www.youtube.com/watch?v=VkrGHqwSPVA&t=1866s",[816],"Spring Developer YouTube Channel",". If you didn't happen to catch it live there was a problem with Zoom and I ended up presenting to myself for about four minutes. If you notice a little bit of a gap, that is why. I put together this blog post that contains more info about the presentation along with links to the slides and code.",[4542,164913,15432],{"id":61224},[651,164915,164916],{},"This week I put together a tutorial on a cool project called Java Faker. This project allows you to create sample data, quickly and easily. I build a lot of demos and I often need some good data to test out certain functionality. In this tutorial I create a simple Spring Boot application and use Faker to populate 200 records in my H2 database.",[651,164918,164919],{},[812,164920,84805],{"href":84805,"rel":164921},[816],[4542,164923,164925],{"id":164924},"currently-reading","Currently Reading",[651,164927,164928,164929,164934],{},"I'm currently reading a new book, ",[812,164930,164933],{"href":164931,"rel":164932},"https://amzn.to/3AC45k8",[816],"Building Microservices 2nd Edition by Sam Newman",". Sam is an amazing author and speaker and I have been looking forward to getting into this book for awhile. I'm currently in the first section of the book titled \"What are Microservices\". I thought Sam's description of Microservices was succinct.",[1004,164936,164937],{},[651,164938,164939],{},"Microservices are independently releasable services that are modeled around a business domain. A service encapsulates functionality and makes it accessible to other services via networks—you construct a more complex system from these building blocks.",[651,164941,164942,164943,164946],{},"I have spent a lot of time in Microservices this year and its an architectural pattern that I am continuing to expand my knowledge on. I will share more with you as I make my way through this book but in the meantime I threw this out on ",[812,164944,51474],{"href":44086,"rel":164945},[816]," this week, what books are you currently reading?",[651,164948,164949],{},[812,164950,164951],{"href":164951,"rel":164952},"https://twitter.com/therealdanvega/status/1441141090006933504",[816],[4542,164954,157574],{"id":157573},[651,164956,157577],{},[5909,164958,164959],{"id":69848},"📝 Articles",[5316,164961,164962],{},[5332,164963,164964],{},[812,164965,164968],{"href":164966,"rel":164967},"https://blogs.oracle.com/java/post/free-java-license",[816],"Introducing the Free Java License",[5909,164970,164971],{"id":157591},"🎬 Videos",[5316,164973,164974],{},[5332,164975,164976],{},[812,164977,164980],{"href":164978,"rel":164979},"https://www.youtube.com/watch?v=GBTdnfD6s5Q",[816],"When to use Microservices (And When Not To) - Sam Newman and Martin Fowler",[5909,164982,164983],{"id":39439},"🎙 Podcasts",[5316,164985,164986],{},[5332,164987,164988],{},[812,164989,164992],{"href":164990,"rel":164991},"https://workingcode.dev/episodes/041-the-third-age-of-javascript-with-shawn-swyx-wang/",[816],"Working Code Podcast: 041: The Third Age of JavaScript, with Shawn @swyx Wang",[5909,164994,164995],{"id":133422},"💻 Projects",[5316,164997,164998],{},[5332,164999,165000],{},[812,165001,84795],{"href":84800,"rel":165002},[816],[5909,165004,165005],{"id":39340},"📚 Books",[5316,165007,165008],{},[5332,165009,165010],{},[812,165011,165013],{"href":164931,"rel":165012},[816],"Building Microservices, 2nd Edition - Sam Newman",[5909,165015,165017],{"id":165016},"️-quote-of-the-week","✍️ Quote of the week",[651,165019,165020],{},"“I have no special talents. I am only passionately curious.” – Albert Einstein",[4542,165022,157704],{"id":157703},[651,165024,157910],{},[651,165026,41105,165027,165029,165030,165032,13517,165034,165036],{},[41107,165028],{},"\nDan Vega ",[41107,165031],{},[812,165033,161560],{"href":161111},[41107,165035],{},[812,165037,53869],{"href":82688,"rel":165038},[816],{"title":674,"searchDepth":790,"depth":790,"links":165040},[165041,165042,165043,165044,165052],{"id":82996,"depth":790,"text":82997},{"id":61224,"depth":790,"text":15432},{"id":164924,"depth":790,"text":164925},{"id":157573,"depth":790,"text":157574,"children":165045},[165046,165047,165048,165049,165050,165051],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":165054,"date":165055},"im-back","2021-09-27T07:00:00.000Z","/newsletter/2021/09/27/im-back",{"title":164883,"description":164888},"newsletter/2021/09/27/im-back","pNdyH89f8Ko0rwDrPlu2FSBdB-nebCb5otAMqyN0LVE",{"id":165061,"title":165062,"body":165063,"description":165067,"extension":793,"meta":165220,"navigation":797,"path":165223,"seo":165224,"stem":165225,"__hash__":165226},"content/newsletter/2021/10/03/my-first-youtube-short.md","I uploaded my first YouTube Short",{"type":648,"value":165064,"toc":165209},[165065,165068,165076,165090,165093,165096,165099,165102,165105,165108,165111,165114,165118,165121,165124,165131,165133,165135,165137,165146,165148,165164,165166,165175,165177,165185,165187,165190,165192,165194],[651,165066,165067],{},"I absolutely love making videos, it's become quite the passion of mine over the last few years. If you haven't uploaded a tutorial to YouTube I don't think you can appreciate just how much time goes into making a single video. In this weeks newsletter I thought I would share with you my video creation process for a normal video and how that compares to a YouTube short.",[651,165069,165070,165071,165075],{},"Each video starts with an idea and I keep a collection of these ideas on a Kanban board in ",[812,165072,56661],{"href":165073,"rel":165074},"https://www.notion.so/",[816],". Once I have decided I want to make a video on a particular topic it's time to start planning out the video. I have a template that breaks down my planning into the following sections:",[5316,165077,165078,165081,165084,165087],{},[5332,165079,165080],{},"Research",[5332,165082,165083],{},"Metadata (title, description, tags, resources)",[5332,165085,165086],{},"Scripts (hook, bumper, intro, screencast, conclusion)",[5332,165088,165089],{},"Shot List (a-roll, b-roll, screencast)",[651,165091,165092],{},"It's not just enough to have an idea, is this idea something that your audience will be interested in.",[651,165094,165095],{},"Once I have made that decision on a topic it's time to record it. The first challenge in my household is trying to find some time to actually record the video. I have 2 young kids and if one of them doesn't keep me up half the night, the other one does. I work my full time job from 8-5 and after work I spend some time with the family, have dinner, give baths and get the kids to bed. I can't record after everyone goes to be because my office is in the basement right below everyone's bedrooms in a split level house. So as you can see finding the time to record is challenging to say the least.",[651,165097,165098],{},"If there are talking head components to the video I need to make sure I look presentable which means finding something nice to wear and doing my hair. My talking head videos are usually just an intro to the tutorial but I feel like they give it a personal touch and connection to the audience.",[651,165100,165101],{},"The screencast portion of the video is usually what people have clicked on the video for. This starts with some thought and planning by putting together a demo and determining what the audience wants to get out of it. After I have the demo code it's time to record the screencast and upload that code to Github.",[651,165103,165104],{},"After all of the recording has been completed it's time to edit. I use Adobe Premiere Pro and this is where I bring in all of my recordings and begin to trim them down. You see each recording is full of mistakes and different takes and I need to trim them down to tell the story I want told. After that I need to add in any music, fix the audio so it's not too loud or soft, perform any color corrections and add in any graphical elements. I then export the media file to disk and prepare to upload the video.",[651,165106,165107],{},"It's finally time to upload the video to YouTube and add fill in all the details. I start with a title, description, custom thumbnail and tags. There are other items that need to be created like cards and end screens. When it's done rendering and ready to publish I change the visibility to Public and hit save.",[651,165109,165110],{},"You might think that is the end of a video lifecycle but it isn't. I need to promote this on my different social media platforms. This usually just involves coming up with a good post and adding it to Twitter and LinkedIn. Depending on the content I might post it on my Spring Boot Developers Facebook group and If I think it's really good I might try and submit it on Reddit.",[651,165112,165113],{},"I realize that was a lot and if you made it this far, congratulations. The point I wanted to make is that for a simple 10 minute tutorial I usually spend anywhere from 8-10 hours from start to end.",[4542,165115,165117],{"id":165116},"youtube-shorts","YouTube Shorts",[651,165119,165120],{},"Shorts is the spot to shoot, share, and binge short videos (think 60 seconds or less) on YouTube. I have been intrigued by shorts because YouTube really seems to be pushing them and they seem like a great way to build an audience. I know that subscribers to my channel like my in depth tutorials and this isn't going to replace that. This is a chance for me to create short videos and or tutorials to help grow my audience.",[651,165122,165123],{},"This week I decided to create my first YouTube short around the new Oracle Licensing for Java 17. The video isn't ground breaking by any means but for me the process was. Because this video had to be 60 seconds or less I was able to research, film, edit and upload this video on my lunch break inside of an hour. In 5 days the video has received 550 views, 47 likes, 1 dislike and 2 comments. You can find a link to the video below and as I make more shorts I will gladly share my process with you if you're interested. What are your thoughts on consuming or creating shorts? I would love to hear from you.",[651,165125,165126],{},[812,165127,165130],{"href":165128,"rel":165129},"https://www.youtube.com/watch?v=ZddzOO_ovz8",[816],"Java is now FREE for everyone #shorts",[4542,165132,157574],{"id":157573},[651,165134,157577],{},[5909,165136,164971],{"id":157591},[5316,165138,165139],{},[5332,165140,165141],{},[812,165142,165145],{"href":165143,"rel":165144},"https://www.youtube.com/watch?v=Xkh5sa3vjTE",[816],"Making use of Sealed classes in Java",[5909,165147,164983],{"id":39439},[5316,165149,165150,165157],{},[5332,165151,165152],{},[812,165153,165156],{"href":165154,"rel":165155},"https://inside.java/2021/09/14/podcast-019/",[816],"Inside Java: Java 17 is Here! Part 1",[5332,165158,165159],{},[812,165160,165163],{"href":165161,"rel":165162},"https://inside.java/2021/09/27/podcast-020/",[816],"Inside Java: Java 17 is Here! Part 2",[5909,165165,164995],{"id":133422},[5316,165167,165168],{},[5332,165169,165170],{},[812,165171,165174],{"href":165172,"rel":165173},"https://9elements.github.io/fancy-border-radius/#72.42.44.12--.",[816],"Fancy Border Radius Generator",[5909,165176,165005],{"id":39340},[5316,165178,165179],{},[5332,165180,165181],{},[812,165182,165013],{"href":165183,"rel":165184},"https://www.notion.so/I-m-back-7e3f721217514dfa9968c7bac2ef3d13",[816],[5909,165186,165017],{"id":165016},[651,165188,165189],{},"“Live as if you were to die tomorrow. Learn as if you were to live forever.” – Mahatma Gandhi",[4542,165191,157704],{"id":157703},[651,165193,157910],{},[651,165195,41105,165196,165198,165199,165201,165203,165204,165206],{},[41107,165197],{},"\nDan Vega ",[41107,165200],{},[812,165202,161560],{"href":161111}," ",[41107,165205],{},[812,165207,53869],{"href":82688,"rel":165208},[816],{"title":674,"searchDepth":790,"depth":790,"links":165210},[165211,165212,165219],{"id":165116,"depth":790,"text":165117},{"id":157573,"depth":790,"text":157574,"children":165213},[165214,165215,165216,165217,165218],{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":165221,"date":165222},"my-first-youtube-short","2021-10-03T10:00:00.000Z","/newsletter/2021/10/03/my-first-youtube-short",{"title":165062,"description":165067},"newsletter/2021/10/03/my-first-youtube-short","2pen8ZYbk655V4SKRJ0ugsQcDqGCQFWhevxOnaC2Foc",{"id":165228,"title":165229,"body":165230,"description":165234,"extension":793,"meta":165468,"navigation":797,"path":165471,"seo":165472,"stem":165473,"__hash__":165474},"content/newsletter/2021/10/24/hello-m1-max.md","Hello, M1 Max",{"type":648,"value":165231,"toc":165455},[165232,165235,165239,165242,165245,165248,165251,165254,165272,165281,165284,165302,165305,165308,165316,165318,165320,165343,165345,165375,165377,165393,165395,165410,165413,165427,165431,165438,165440,165442],[651,165233,165234],{},"In this week's newsletter, I'm going to talk about the new MacBook Pro's and I'll give a quick review of a book that I just finished.",[4542,165236,165238],{"id":165237},"new-macbook-pros","New MacBook Pros",[651,165240,165241],{},"I happen to be heavily invested in the Apple ecosystem. I own a lot of their products and I often advocate on behalf of them. Somehow this often starts a heated discussion about Android vs iPhone or PC vs Mac and there is just no room for it. Different products just connect to different people and that is ok.",[651,165243,165244],{},"When I started out writing software 20+ years ago I did it on a desktop computer that I built myself. I ordered all of the parts from a local computer shop or New Egg and I put it together myself. I didn't have YouTube to watch a guided tutorial, I messed up and learned from those mistakes. When I was done, I had a machine that would power my passion, programming.",[651,165246,165247],{},"I'm not sure when I bought my first MacBook but I think it was around 2013. This would be the first laptop I would ever purchase on my own. I had some others through work but this was the first laptop that I could call mine, and I fell in love with it. This wasn't my first trip around Unix, in fact, I started with a Linux Laptop early on in my career. As a developer, I enjoyed the terminal experience vs Windows at the time. It was a well-designed machine that was less susceptible to bugs and lasted longer than its PC counterpart.",[651,165249,165250],{},"At the end of last year, Apple announced its new MacBook & MacBook Air based on their own silicon chips. This was exciting on a number of levels but for me, the processor world was slowing down. Moore's Law of doubling chip densities every two years just isn't happening anymore. This gave Apple a chance to come in and control the whole experience from the hardware to the software. They have done this well with the iPhone and it appears they have taken what they learned there to the mac.",[651,165252,165253],{},"Let's fast forward to the announcement this week of the new 14\" & 16\" MacBook Pros. I knew before these were announced that one of them would be my next machine but what size and configuration are where my decision would lie. I was asked by someone on Twitter why I went with the 14\" model and I share my answer with you. Apple usually gives you a reason to move up in models and in this case, they didn't. You could get the same configurations in both the 14\" and 16\" models. After I heard that it was a pretty easy choice for me. My laptop is hooked up to some external monitors 99% of the time and when it's not that means I'm on the go. When I am on the go whether it's doing some work at a coffee shop or traveling I like the smaller form factor. This is the configuration I ended up going with:",[5316,165255,165256,165258,165260,165262,165264,165266,165268,165270],{},[5332,165257,82292],{},[5332,165259,82295],{},[5332,165261,82298],{},[5332,165263,82301],{},[5332,165265,82304],{},[5332,165267,82307],{},[5332,165269,82310],{},[5332,165271,82313],{},[651,165273,165274,165275,165280],{},"The expected delivery date is not until November 11-18th. So my question for you is do you have any interest in a live stream where I set it up as a development machine? I feel like a live stream is a big commitment. I could easily slim that down into a condensed tutorial for YouTube. I have a detailed ",[812,165276,165279],{"href":165277,"rel":165278},"https://www.danvega.dev/blog/2018/12/21/macbook-pro-setup-my-setup-with-detailed-instructions/",[816],"MacBook Pro setup guide on Github",". I will probably create a new version of this for M1 and update that along with the video tutorial.",[4542,165282,79463],{"id":165283},"fundamentals-of-software-architecture",[651,165285,165286,165287,165291,165292,165296,165297,165301],{},"I finally got around to finishing ",[812,165288,79463],{"href":165289,"rel":165290},"https://amzn.to/3pDbjSp",[816]," by Mark Richards and Neal Ford. I plan on dedicating a post on my ",[812,165293,43724],{"href":165294,"rel":165295},"https://www.danvega.dev/blog/",[816]," and or a video on my ",[812,165298,101297],{"href":165299,"rel":165300},"http://www.youtube.com/danvega",[816]," so consider this my first impression.",[651,165303,165304],{},"Anyone who follows Mark or Neal already knows this but for those of you who don't they are simply brilliant. This book is one of the very best technical books I have ever read. If you're interested in getting into architecture it is a must-read. I thought they did a really good job of explaining what architecture is and what is expected of you in that role.",[651,165306,165307],{},"Everything in software architecture is a trade-off. This was the big theme of the book for me and I thought they did a great job of reminding you of that throughout the book. I really enjoyed how they taught me how to identify architectural characteristics and more importantly how to measure them. I also enjoyed the breakdown of taking those architectural characteristics and using them to make a decision on what architecture style to use (monolith vs distributed).",[651,165309,165310,165311,664],{},"Those are my initial thoughts. Fantastic book and would highly recommend it to anyone wanting to get into software architecture. Thank you to Mark and Neal for putting so much thought and detail into this book and I can wait for ",[812,165312,165315],{"href":165313,"rel":165314},"https://amzn.to/2XJIloi",[816],"their next one",[4542,165317,157574],{"id":157573},[5909,165319,164959],{"id":69848},[5316,165321,165322,165329,165336],{},[5332,165323,165324],{},[812,165325,165328],{"href":165326,"rel":165327},"https://developer.okta.com/blog/2021/10/04/spring-boot-spa",[816],"Learn How to Build a Single-Page App with Vue and Spring Boot | Okta Developer",[5332,165330,165331],{},[812,165332,165335],{"href":165333,"rel":165334},"https://tanzu.vmware.com/content/blog/vmware-tanzu-community-edition-announcement",[816],"Introducing VMware Tanzu Community Edition",[5332,165337,165338],{},[812,165339,165342],{"href":165340,"rel":165341},"https://leaddev.com/culture-engagement-motivation/why-flow-matters-more-passion",[816],"Why flow matters more than passion | LeadDev",[5909,165344,164971],{"id":157591},[5316,165346,165347,165354,165361,165368],{},[5332,165348,165349],{},[812,165350,165353],{"href":165351,"rel":165352},"https://www.youtube.com/watch?v=kVSYVhmvNCI",[816],"Spring Tips: GraphQL",[5332,165355,165356],{},[812,165357,165360],{"href":165358,"rel":165359},"https://www.youtube.com/watch?v=wKXfOLfAN04",[816],"VMware Tanzu Community Edition: First Look with Amanda and Josh",[5332,165362,165363],{},[812,165364,165367],{"href":165365,"rel":165366},"https://www.youtube.com/watch?v=sVP8xb9BrhM",[816],"Kubernetes: The documentary trailer",[5332,165369,165370],{},[812,165371,165374],{"href":165372,"rel":165373},"https://www.youtube.com/watch?v=ChYIhEEOfrk",[816],"How to Docker with Spring Boot",[5909,165376,164995],{"id":133422},[5316,165378,165379,165386],{},[5332,165380,165381],{},[812,165382,165385],{"href":165383,"rel":165384},"https://github.com/vmware-tanzu/community-edition",[816],"VMware Tanzu Community Edition",[5332,165387,165388],{},[812,165389,165392],{"href":165390,"rel":165391},"https://github.com/testcontainers/testcontainers-java/releases/tag/1.16.1",[816],"TestContainers 1.16.1",[5909,165394,165005],{"id":39340},[5316,165396,165397,165403],{},[5332,165398,165399],{},[812,165400,79463],{"href":165401,"rel":165402},"https://amzn.to/3vNGnjm",[816],[5332,165404,165405],{},[812,165406,165409],{"href":165407,"rel":165408},"https://amzn.to/3ni4zXa",[816],"Software Architecture: The Hard Parts",[5909,165411,165412],{"id":79608},"💻 Conferences",[5316,165414,165415],{},[5332,165416,165417,4193,165422,50653],{},[812,165418,165421],{"href":165419,"rel":165420},"https://jamstackconf.com/",[816],"Jamstack Conf 2021",[812,165423,165426],{"href":165424,"rel":165425},"https://www.youtube.com/c/JAMstackConf/videos",[816],"Recordings",[5909,165428,165430],{"id":165429},"tweets","🐦 Tweets",[651,165432,165433],{},[812,165434,165437],{"href":165435,"rel":165436},"https://twitter.com/graphql_java/status/1450195999373205506",[816],"GraphQL Java is now serving 500k requests per second for Twitter.",[4542,165439,157704],{"id":157703},[651,165441,157910],{},[651,165443,41105,165444,69920,165446,165448,165450,165452],{},[41107,165445],{},[41107,165447],{},[812,165449,161560],{"href":161111},[41107,165451],{},[812,165453,53869],{"href":82688,"rel":165454},[816],{"title":674,"searchDepth":790,"depth":790,"links":165456},[165457,165458,165459,165467],{"id":165237,"depth":790,"text":165238},{"id":165283,"depth":790,"text":79463},{"id":157573,"depth":790,"text":157574,"children":165460},[165461,165462,165463,165464,165465,165466],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":79608,"depth":892,"text":165412},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":165469,"date":165470},"hello-m1-max","2021-10-24T14:00:00.000Z","/newsletter/2021/10/24/hello-m1-max",{"title":165229,"description":165234},"newsletter/2021/10/24/hello-m1-max","jLlqY8gE4w5TwVXw_YtHBCTiUhyevdMkJCoOxjO4MXY",{"id":165476,"title":165477,"body":165478,"description":165784,"extension":793,"meta":165785,"navigation":797,"path":165788,"seo":165789,"stem":165790,"__hash__":165791},"content/newsletter/2021/10/31/happy-halloween.md","Happy Halloween",{"type":648,"value":165479,"toc":165770},[165480,165489,165492,165495,165498,165501,165505,165507,165510,165516,165519,165525,165528,165534,165537,165542,165545,165551,165563,165567,165576,165579,165599,165602,165608,165614,165620,165624,165638,165644,165651,165655,165658,165674,165676,165678,165687,165689,165705,165707,165723,165725,165748,165750,165753,165755,165757],[651,165481,165482,165483,165488],{},"Happy Halloween, I hope you have a spooktacular day! If you're a Visual Studio Code user ",[812,165484,165487],{"href":165485,"rel":165486},"https://www.youtube.com/watch?v=tqd3O5RpAHQ",[816],"James Q Quick has a Halloween theme"," perfect for nighttime coding.",[651,165490,165491],{},"I have been a big fan of Microsoft for a long time now. It hasn't always been that way but I think for me it started when Satya Nadella took over as CEO. They really began to embrace Open Source and have done some amazing work on tools and the cloud.",[651,165493,165494],{},"When we talk about open source software we as developers use on a daily basis Git & Github are at the top of that list for me. In 2018 Microsoft purchased Github for 7.5 billion and I remember a lot of fear from developers that they would run it into the ground.",[651,165496,165497],{},"Fast forward to 2021 and that could be nothing further from the truth. They have done nothing but improve a tool that is vital to most of us. I'm old enough to remember when we had to pay for private repositories. In fact what we tend to forget is this tool that helps us maintain, version, share, and learn code is completely FREE. I think we take this for granted sometimes. Thank you, Microsoft 🙏",[651,165499,165500],{},"Github Universe 2021 was this week so I thought I would mention a couple of highlights from the conference that I am really excited about.",[4542,165502,165504],{"id":165503},"github-copilot-for-intellij","Github Copilot for IntelliJ",[651,165506,82204],{},[651,165508,165509],{},"This week GitHub announced support for IntelliJ and I was excited to get my hands on this. To get started go to Preferences > Plugins and search for Github Copilot.",[651,165511,165512],{},[660,165513],{"alt":165514,"src":165515},"IntelliJ Plugin","/images/newsletter/2021/10/31/intellij_plugin.png",[651,165517,165518],{},"To get started create a new Java class called Test. If you place your cursor inside of the class Copilot will make a suggestion for a new main method that prints out \"Hello, World!\" to the console. If you hit the tab key, that method will be completed for you.",[651,165520,165521],{},[660,165522],{"alt":165523,"src":165524},"Test Class","/images/newsletter/2021/10/31/test_class.png",[651,165526,165527],{},"Another way to trigger Copilot is by typing the name of a method and based on the name you will get suggestions of how to complete that method. Again, hit the tab key to complete the code.",[651,165529,165530],{},[660,165531],{"alt":165532,"src":165533},"Sum Method","/images/newsletter/2021/10/31/sum_method.png",[651,165535,165536],{},"Finally, you can use a comment to let Copilot know what you're trying to accomplish. In the example below I would like to write a method that will check to see if a string is a palindrome.",[651,165538,165539],{},[660,165540],{"alt":122065,"src":165541},"/images/newsletter/2021/10/31/comment.png",[651,165543,165544],{},"The real question you might have without reading through that code is \"Does it work?\". I generated a new Test and Copilot is suggesting an assertion and after running my test it passes.",[651,165546,165547],{},[660,165548],{"alt":165549,"src":165550},"Palindrome Test","/images/newsletter/2021/10/31/palindrome_test.png",[651,165552,165553,165554,165558,165559,165562],{},"It might not be up to the standard of the palindrome king ",[812,165555,83164],{"href":165556,"rel":165557},"https://twitter.com/kenkousen",[816],", but it works! This is still very much a technical preview and I think there is room for improvement. I'm also not sure how valuable this is in my day-to-day work but I will test it out and report back. I hope to put together a video on this next week for my ",[812,165560,101297],{"href":165299,"rel":165561},[816]," that will include some Spring Boot examples so if you're not a subscriber what are you waiting for!",[4542,165564,165566],{"id":165565},"github-command-palette","Github Command Palette",[651,165568,165569,165570,165575],{},"GitHub launched a ",[812,165571,165574],{"href":165572,"rel":165573},"https://docs.github.com/en/get-started/using-github/github-command-palette",[816],"command palette"," to navigate, search and run commands directly from your keyboard.",[651,165577,165578],{},"Open the command palette using one of the following keyboard shortcuts:",[5316,165580,165581,165590],{},[5332,165582,165583,165584,86626,165587],{},"Windows and Linux: ",[676,165585,165586],{},"Ctl +k",[676,165588,165589],{},"Ctl + alt + k",[5332,165591,165592,165593,86626,165596],{},"Mac: ",[676,165594,165595],{},"⌘ + k",[676,165597,165598],{},"⌘ + option + k",[651,165600,165601],{},"From my personal GitHub page, it lets me quickly jump to repositories, projects & packages.",[651,165603,165604],{},[660,165605],{"alt":165606,"src":165607},"Command Palette","/images/newsletter/2021/10/31/command_palette.png",[651,165609,165610,165611,165613],{},"If you use ",[676,165612,5860],{}," you can run commands. In this example, I want to quickly switch over to the dark dimmed theme.",[651,165615,165616],{},[660,165617],{"alt":165618,"src":165619},"Command Palette Actions","/images/newsletter/2021/10/31/cp_actions.png",[4542,165621,165623],{"id":165622},"githubdev","github.dev",[651,165625,165626,165627,165629,165630,165633,165634,165637],{},"This is entirely new but since we are talking about GitHub I thought I would share this little tip with you. If you're looking at a particular file in your repo you can edit the raw file but you lose out on all that code assistance and completion that we all love. Press the",[676,165628,664],{}," or swap ",[676,165631,165632],{},".com"," with ",[676,165635,165636],{},".dev"," in the URL and you will be taken directly to a Visual Studio Code environment in your browser.",[651,165639,165640],{},[660,165641],{"alt":165642,"src":165643},"github_dev.jpg","/images/newsletter/2021/10/31/github_dev.jpeg",[651,165645,165646,165647,664],{},"It’s a quick way to edit and navigate code. It's especially useful if you want to edit multiple files at a time or take advantage of all the powerful code editing features of Visual Studio Code when making a quick change. For more information, see our ",[812,165648,40844],{"href":165649,"rel":165650},"https://github.co/codespaces-editor-help",[816],[4542,165652,165654],{"id":165653},"devops-p","DevOps P",[651,165656,165657],{},"Emily Freeman who is a keynote speaker, best-selling author, and DevOps wizard was the feature of two amazing videos. I thought both of these were well thought out and the quality is great.",[5316,165659,165660,165667],{},[5332,165661,165662],{},[812,165663,165666],{"href":165664,"rel":165665},"https://www.youtube.com/watch?v=Z66-us_VDu8",[816],"Rethinking the SDLC - GitHub Universe 2021",[5332,165668,165669],{},[812,165670,165673],{"href":165671,"rel":165672},"https://www.youtube.com/watch?v=kBV8gPVZNEE",[816],"What is DevOps",[4542,165675,157574],{"id":157573},[5909,165677,164959],{"id":69848},[5316,165679,165680],{},[5332,165681,165682],{},[812,165683,165686],{"href":165684,"rel":165685},"https://teachable.com/blog/handling-inconsistent-revenue-streams",[816],"Creator tip: Handling inconsistent revenue streams as a full-time course creator",[5909,165688,164971],{"id":157591},[5316,165690,165691,165698],{},[5332,165692,165693],{},[812,165694,165697],{"href":165695,"rel":165696},"https://www.youtube.com/watch?v=G8aq4n9F9E4",[816],"The Best of Both Worlds: ISG for Nuxt by Ishan Anand",[5332,165699,165700],{},[812,165701,165704],{"href":165702,"rel":165703},"https://portal.gitnation.org/events/vuejs-london-2021",[816],"VueJS London 2021 Recordings",[5909,165706,164983],{"id":39439},[5316,165708,165709,165716],{},[5332,165710,165711],{},[812,165712,165715],{"href":165713,"rel":165714},"https://www.javascriptjam.com/",[816],"JavaScript Jam",[5332,165717,165718],{},[812,165719,165722],{"href":165720,"rel":165721},"https://www.youtube.com/watch?v=onN4Ecm2RXM",[816],"Groovy Podcast, S05E03 (83, with Paul King)",[5909,165724,164995],{"id":133422},[5316,165726,165727,165734,165741],{},[5332,165728,165729],{},[812,165730,165733],{"href":165731,"rel":165732},"https://v3.nuxtjs.org/",[816],"Nuxt 3 Beta",[5332,165735,165736],{},[812,165737,165740],{"href":165738,"rel":165739},"https://kaleidoscope.app/",[816],"Kaleidoscope App",[5332,165742,165743],{},[812,165744,165747],{"href":165745,"rel":165746},"https://templatesurf.com/",[816],"Templatesurf",[5909,165749,165017],{"id":165016},[651,165751,165752],{},"The answer in Chief Executive Officer Satya Nadella’s mind is clear. “Creation, creation, creation—the next 10 years is going to be as much about creation as it is about consumption and about the community around it, so it’s not creating alone,”",[4542,165754,157704],{"id":157703},[651,165756,157910],{},[651,165758,41105,165759,69920,165761,165763,165765,165767],{},[41107,165760],{},[41107,165762],{},[812,165764,161560],{"href":161111},[41107,165766],{},[812,165768,53869],{"href":82688,"rel":165769},[816],{"title":674,"searchDepth":790,"depth":790,"links":165771},[165772,165773,165774,165775,165776,165783],{"id":165503,"depth":790,"text":165504},{"id":165565,"depth":790,"text":165566},{"id":165622,"depth":790,"text":165623},{"id":165653,"depth":790,"text":165654},{"id":157573,"depth":790,"text":157574,"children":165777},[165778,165779,165780,165781,165782],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},"Happy Halloween, I hope you have a spooktacular day! If you're a Visual Studio Code user James Q Quick has a Halloween theme perfect for nighttime coding.",{"slug":165786,"date":165787},"happy-halloween","2021-10-31T13:00:00.000Z","/newsletter/2021/10/31/happy-halloween",{"title":165477,"description":165784},"newsletter/2021/10/31/happy-halloween","REQAi3AsMrdy8auY4W_ETlCn4WOntwuzeMzhIYkMDK0",{"id":165793,"title":165794,"body":165795,"description":165799,"extension":793,"meta":166020,"navigation":797,"path":166022,"seo":166023,"stem":166024,"__hash__":166025},"content/newsletter/2021/11/14/new-macbook-pro.md","New MacBook Pro",{"type":648,"value":165796,"toc":166008},[165797,165800,165804,165807,165813,165820,165823,165828,165831,165865,165868,165873,165876,165888,165890,165892,165915,165917,165940,165942,165958,165960,165983,165985,165991,165993,165995],[651,165798,165799],{},"After missing a week due to mostly not having anything to talk to you about I'm back with the newsletter so let's do it. First, we celebrated Veterans Day here in the US this week and I just want to take this opportunity to thank anyone who has or is serving. Your sacrifice and commitment to this country are greatly appreciated.",[4542,165801,165803],{"id":165802},"macbook-pro","MacBook Pro",[651,165805,165806],{},"I ordered my new MacBook Pro the day after the announcement event and I kid you not I have been checking the tracking updates every day since then. This week it finally arrived and I was excited to get it in my hands and test it out. Here is a shot of it still in the box in front of my Cleveland print in my office.",[651,165808,165809],{},[660,165810],{"alt":165811,"src":165812},"New MacBook Pro in my office","/images/newsletter/2021/10/31/new-macbook-pro-office.JPG",[651,165814,165815,165816,165819],{},"Before it arrived I had all of these plans to create videos for my ",[812,165817,101297],{"href":164096,"rel":165818},[816]," on how I set up a new machine as a developer. I always found those types of videos useful and I thought I might be able to help others find new tools or just learn something new.",[651,165821,165822],{},"Mapping out each of those videos would take a long time though and I just wanted to get it set up and start using it and can you blame me? What I did though was document each of the steps I took to set up my new laptop and put it on Github. I am going to use this and my review of the MacBook Pro and create a video and or blog post on my setup soon.",[651,165824,165825],{},[812,165826,82507],{"href":82505,"rel":165827},[816],[651,165829,165830],{},"Full review coming soon but here are a few notes:",[5316,165832,165833,165836,165839,165842,165845,165856,165859,165862],{},[5332,165834,165835],{},"I love the 14\" size, it's a little heavier and I honestly I appreciate that.",[5332,165837,165838],{},"The display is amazing and no the notch doesn't bother me at all.",[5332,165840,165841],{},"When you open the laptop it powers on and responds to you instantly.",[5332,165843,165844],{},"The keyboard is new and so far it's been a great experience. Usually, I prefer using my external keyboard (Apple Magic Keyboard) but I actually prefer typing on this one.",[5332,165846,165847,165848],{},"I was never a fan of the touch bar and I am happy to see the physical keys return.\n",[5316,165849,165850],{},[5332,165851,165852,165853,165855],{},"The lower ",[676,165854,82413],{}," key also serves as an Emoji Picker 🥳",[5332,165857,165858],{},"Ports - I was really happy to see the 3 USB-C ports, SD, HDMI, and MagSafe.",[5332,165860,165861],{},"The battery performance has been really good so far.",[5332,165863,165864],{},"Performance - Everything just feels smooth, fast, and snappy.",[651,165866,165867],{},"Speaking of performance I sent out this tweet this week. I pulled down the Spring Pet Clinic project and I was able to run it in 1.95 seconds.",[651,165869,165870],{},[812,165871,82449],{"href":82449,"rel":165872},[816],[4542,165874,97502],{"id":165875},"github-copilot",[651,165877,165878,165879,23212,165883,165887],{},"In my last newsletter, I talked about Github Copilot for Java Developers. Since then I wrote a ",[812,165880,24879],{"href":165881,"rel":165882},"https://www.danvega.dev/blog/2021/11/08/github-copilot-java-developers/",[816],[812,165884,165886],{"href":82643,"rel":165885},[816],"created a video"," tutorial. It's pretty crazy how good Github Copilot can recommend lines or even entire methods.",[4542,165889,157574],{"id":157573},[5909,165891,164959],{"id":69848},[5316,165893,165894,165901,165908],{},[5332,165895,165896],{},[812,165897,165900],{"href":165898,"rel":165899},"https://www.atomicjar.com/2021/11/announcing-testcontainers-cloud/",[816],"Announcing Testcontainers Cloud: Integration Testing has never been easier - AtomicJar, Inc.",[5332,165902,165903],{},[812,165904,165907],{"href":165905,"rel":165906},"https://9to5mac.com/2021/11/08/heres-why-twitter-uber-and-more-are-giving-fully-loaded-m1-max-macbook-pros-to-engineers/",[816],"Here's why Twitter, Uber, and more are giving fully loaded M1 Max MacBook Pros to engineers",[5332,165909,165910],{},[812,165911,165914],{"href":165912,"rel":165913},"https://blog.youtube/news-and-events/update-to-youtube/",[816],"An update to dislikes on YouTube",[5909,165916,164971],{"id":157591},[5316,165918,165919,165926,165933],{},[5332,165920,165921],{},[812,165922,165925],{"href":165923,"rel":165924},"https://www.youtube.com/watch?v=ebj0VU92YrA",[816],"Introduction to Shortcuts on macOS",[5332,165927,165928],{},[812,165929,165932],{"href":165930,"rel":165931},"https://www.youtube.com/c/AllThingsOpen/videos",[816],"All Things Open Conference Recordings",[5332,165934,165935],{},[812,165936,165939],{"href":165937,"rel":165938},"https://www.youtube.com/channel/UCEWZUAC6afuExvl-V-vbRGw",[816],"Micronaut has a YouTube Channel",[5909,165941,164983],{"id":39439},[5316,165943,165944,165951],{},[5332,165945,165946],{},[812,165947,165950],{"href":165948,"rel":165949},"https://bootifulpodcast.fm/#/episodes/ba1180c6-9faf-4820-8c78-c1e9dbd757dd",[816],"JMS and Messaging legend, HornetQ and Artemis cofounder, Clebert Suconic",[5332,165952,165953],{},[812,165954,165957],{"href":165955,"rel":165956},"https://share.transistor.fm/s/01493f8c",[816],"Money Lab Podcast: YouTube publishing challenge",[5909,165959,164995],{"id":133422},[5316,165961,165962,165969,165976],{},[5332,165963,165964],{},[812,165965,165968],{"href":165966,"rel":165967},"https://codepen.io/chris__sev/pen/vYJWLxM?editors=1100",[816],"Text Portrait",[5332,165970,165971],{},[812,165972,165975],{"href":165973,"rel":165974},"https://www.producthunt.com/posts/thunder-client",[816],"Thunder Client - REST API Client for VS Code",[5332,165977,165978],{},[812,165979,165982],{"href":165980,"rel":165981},"https://icons8.com/lunacy",[816],"Lunacy – Free Design Software for Win, Mac, Linux",[5909,165984,165430],{"id":165429},[651,165986,165987],{},[812,165988,165989],{"href":165989,"rel":165990},"https://twitter.com/marcushellberg/status/1458819884792434690",[816],[4542,165992,157704],{"id":157703},[651,165994,157910],{},[651,165996,41105,165997,69920,165999,166001,166003,166005],{},[41107,165998],{},[41107,166000],{},[812,166002,161560],{"href":161111},[41107,166004],{},[812,166006,53869],{"href":82688,"rel":166007},[816],{"title":674,"searchDepth":790,"depth":790,"links":166009},[166010,166011,166012,166019],{"id":165802,"depth":790,"text":165803},{"id":165875,"depth":790,"text":97502},{"id":157573,"depth":790,"text":157574,"children":166013},[166014,166015,166016,166017,166018],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":166021,"date":165787},"new-macbook-pro","/newsletter/2021/11/14/new-macbook-pro",{"title":165794,"description":165799},"newsletter/2021/11/14/new-macbook-pro","tcA_ME0UUj2ZF_JXd5GF961fBVvQlJ-FAyvnXI-AbaQ",{"id":166027,"title":166028,"body":166029,"description":166033,"extension":793,"meta":166305,"navigation":797,"path":166307,"seo":166308,"stem":166309,"__hash__":166310},"content/newsletter/2021/11/22/content-creation.md","Content Creation & My MacBook Pro Review & Setup",{"type":648,"value":166030,"toc":166285},[166031,166034,166038,166050,166053,166056,166061,166064,166066,166069,166071,166080,166083,166090,166103,166106,166114,166117,166120,166122,166125,166127,166136,166139,166142,166145,166151,166154,166157,166160,166166,166169,166171,166173,166196,166198,166221,166223,166239,166241,166250,166252,166255,166257,166263,166265,166272],[651,166032,166033],{},"It's hard to believe that it's Thanksgiving this week here in the US and that 2021 is almost over. If you're in the US and celebrating I would like to wish you and your family a Happy Thanksgiving. I don't mind fall but it's quickly moving from fall to winter here in Northeast Ohio where we recently saw some snow ❄️ and temps in the 20s 😩",[4542,166035,166037],{"id":166036},"macbook-review-setup-guide","MacBook Review & Setup Guide",[651,166039,166040,166041,23212,166044,166049],{},"This week I had a chance to put together a ",[812,166042,24879],{"href":82623,"rel":166043},[816],[812,166045,166048],{"href":166046,"rel":166047},"https://youtu.be/t-hEOyUnaXQ",[816],"YouTube video"," on my M1 MacBook Pro review and setup. I actually got all the way through the video and started editing it and realized that I was using the wrong microphone.",[651,166051,166052],{},"This is because I use a program called Krisp which does a really good job at filtering out background noise. Apparently, it selected the wrong microphone and was using the onboard one. I swear I didn't do this on purpose and it doesn't sound as good as my USB microphone but it turned out ok and is just another thing that impresses me about this laptop.",[651,166054,166055],{},"I received the following email from someone who read my review:",[1004,166057,166058],{},[651,166059,166060],{},"Hi, Dan:\nThanks for sharing information about setting up your new Mac Book. I have never used a Mac before and was hesitant to buy an expensive laptop and not have setup information for development work. As soon as I saw the title of your post, I decided to buy it. Thanks again. - Jorge",[651,166062,166063],{},"I just want to say thank you for taking the time to send that in Jorge. Sometimes It can get lonely on this side of the keyboard and your feedback means a lot to me.",[4542,166065,43654],{"id":43653},[651,166067,166068],{},"I have been thinking about content creation a lot lately. As we head into the holidays and the new year quickly approaches I want to clean things up a bit so I can set (and hopefully accomplish) some goals next year.",[5909,166070,67442],{"id":67441},[651,166072,166073,166074,166079],{},"I am working on a video this week where I will show you how I create this newsletter that you are reading. Each week I start with a new template in Notion and as the week goes on I try and add content to it. At the end of the week, I will publish the newsletter on my website and send it off to you using ",[812,166075,166078],{"href":166076,"rel":166077},"https://app.convertkit.com/referrals/l/ad254512-ed8b-4542-85d0-a02af83e2f7c",[816],"ConvertKit",". I'm hoping someone finds this useful so look out for this early this week.",[5909,166081,166078],{"id":166082},"convertkit",[651,166084,166085,166086,166089],{},"Speaking of ",[812,166087,166078],{"href":166076,"rel":166088},[816],", it is an amazing tool that I am not using to my advantage. To do so I started off by cleaning up my account and removing all of the old forms and landing pages that I was no longer using. Next, I removed all of my old rules, tags, segments, and sequences. The only way you can land on my email list now is by signing up for this newsletter and I want to add a sequence (welcome email) around that.",[651,166091,166092,166093,166097,166098,166102],{},"I learned this week that ConvertKit had a feature called Tip Jars. This allows creators to create a simple form and earn tips from their audience. I decided to ",[812,166094,166096],{"href":82470,"rel":166095},[816],"create one"," and you will start to see this embedded in blog posts. I might move this form to my website soon but for now, I will just host it on ConvertKit. If you want to ",[812,166099,166101],{"href":82470,"rel":166100},[816],"buy me a coffee",", this is the easiest way to do so 😉",[5909,166104,26194],{"id":166105},"teachable",[651,166107,166108,166109,166113],{},"For anyone new around here, I ",[812,166110,166112],{"href":100862,"rel":166111},[816],"teach courses online",". When I started out creating courses I did so on Udemy. When you don't have an audience a marketplace like Udemy is a great place to start. After I started to gain a following I thought that I would self host my courses and instead of splitting the profits with Udemy I would keep them for myself, well more specifically for my wife's Amazon addiction.",[651,166115,166116],{},"The problem with that was now I have to maintain my courses on two different platforms. When you don't have a marketplace you have to do all of the marketing for your courses yourself and on top of not having a lot of experience doing that, I just don't have the time. Most of my courses are still selling well on Udemy but I don't sell any courses on Teachable. For this reason, I decided that to downgrade to a free account which means I won't be selling courses on that platform anymore.",[651,166118,166119],{},"I'm not sure what this means for the future of my courses. I had a plan to release a free course on building REST APIs in Spring Boot that would turn into a full course. I will figure out how to make that happen but for now and sticking with my theme of simplifying things as we head into the new year this just seemed like the right move.",[5909,166121,26164],{"id":11128},[651,166123,166124],{},"Speaking of Udemy, this is another part of my business that I need to get cleaned up heading into the new year. Trying to keep up with student questions on Udemy is not an easy task. In the past, I found some great people on Upwork to help me with that workload. I currently don't have anyone helping me and I need to fix that very soon. There is also a need to update course content and I just need to find some time to determine what needs updating and get it done. If you're a Udemy student of mine, thank you for your patience.",[5909,166126,15432],{"id":61224},[651,166128,166129,166130,166135],{},"In addition to the MacBook Review and setup video I also released a tutorial on turning a ",[812,166131,166134],{"href":166132,"rel":166133},"https://youtu.be/EObFO5ohikg",[816],"stream into a list"," in Java 16. For those counting that is 2 videos in a week and I haven't done that in about a year.",[651,166137,166138],{},"You know that question people ask \"If money wasn't an issue and you could do anything for work what would you do?\" for me that's easy, I would learn and teach what I've learned to others. The best place to teach is YouTube and I'm hoping to do some big things in 2022. To do so I realized that I have to have a better system for creating videos.",[651,166140,166141],{},"I have been using Notion to organize my video ideas for a while now but I haven't done a great job of turning those ideas into videos. With that, I decided to take an approach from the software development world and spend time \"grooming the backlog\".",[651,166143,166144],{},"I flush out an idea with as much detail as possible and when I feel like it's ready to go I move it into the ready-to-film column. Now when I sit down to record I don't really have to think about what that video is going to look like. That has already been figured out and I can just start creating!",[651,166146,166147],{},[660,166148],{"alt":166149,"src":166150},"Notion Video Backlog","/images/newsletter/2021/11/22/notion_video_backlog.png",[651,166152,166153],{},"If anyone is interested in seeing my Notion setup for creating videos let me know and I will put that together.",[4542,166155,43833],{"id":166156},"netlify",[651,166158,166159],{},"I am a huge fan of Netlify and this week they announced that they raised $105 million in funding. They already make building for the web easy and I am excited to see what they do next!",[651,166161,166162],{},[812,166163,166164],{"href":166164,"rel":166165},"https://twitter.com/netlify/status/1460973163823697922",[816],[651,166167,166168],{},"If anyone actually reads this whole newsletter you deserve a prize 🥳",[4542,166170,157574],{"id":157573},[5909,166172,164959],{"id":69848},[5316,166174,166175,166182,166189],{},[5332,166176,166177],{},[812,166178,166181],{"href":166179,"rel":166180},"https://blog.jetbrains.com/idea/2021/11/kubernetes-and-docker-updates-2021-3/",[816],"Kubernetes and Docker Updates in IntelliJ IDEA 2021.3",[5332,166183,166184],{},[812,166185,166188],{"href":166186,"rel":166187},"https://deno.com/blog/slack",[816],"Slack Introduces New Platform With Help From Deno",[5332,166190,166191],{},[812,166192,166195],{"href":166193,"rel":166194},"https://www.netlify.com/blog/2021/11/17/first-look-announcing-api-authentication-on-netlify/",[816],"First Look: Announcing API Authentication on Netlify",[5909,166197,164971],{"id":157591},[5316,166199,166200,166207,166214],{},[5332,166201,166202],{},[812,166203,166206],{"href":166204,"rel":166205},"https://www.youtube.com/watch?v=m_uo9sUvVGA",[816],"New Notion Feature - Simple Tables",[5332,166208,166209],{},[812,166210,166213],{"href":166211,"rel":166212},"https://www.youtube.com/watch?v=WnBjGNWdayw&t=226s",[816],"Those Who Can Should Also Teach",[5332,166215,166216],{},[812,166217,166220],{"href":166218,"rel":166219},"https://www.youtube.com/watch?v=wYk0JrNdb8g",[816],"Spring Cloud Gateway: Resilience and Security • Thomas Vitale • GOTO 2021",[5909,166222,164983],{"id":39439},[5316,166224,166225,166232],{},[5332,166226,166227],{},[812,166228,166231],{"href":166229,"rel":166230},"https://www.youtube.com/watch?v=PcwGUtf95JY",[816],"Between Chair & Keyboard with Sharat Chander",[5332,166233,166234],{},[812,166235,166238],{"href":166236,"rel":166237},"https://www.youtube.com/watch?v=qB9Zae87RbE",[816],"Deep Dive with Ali Abdaal - Krissy Cela On Finding Your Vision, Mission and Why",[5909,166240,164995],{"id":133422},[5316,166242,166243],{},[5332,166244,166245],{},[812,166246,166249],{"href":166247,"rel":166248},"https://top10.netflix.com/",[816],"Netflix Top 10 (Built with Tailwind CSS 🥳)",[5909,166251,165017],{"id":165016},[651,166253,166254],{},"\"If you don't believe in yourself, who will?\" - Krissy Cela",[5909,166256,165430],{"id":165429},[651,166258,166259],{},[812,166260,166261],{"href":166261,"rel":166262},"https://twitter.com/mkbhd/status/1460976089933787140",[816],[4542,166264,157704],{"id":157703},[651,166266,166267,166268,166271],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of Coffee & Code and I will see you next Monday morning. If you have any links you would like me to include please ",[812,166269,41499],{"href":44086,"rel":166270},[816]," and I might add them to a future newsletter. I hope you have a great week and as always friends...",[651,166273,41105,166274,69920,166276,166278,166280,166282],{},[41107,166275],{},[41107,166277],{},[812,166279,161560],{"href":161111},[41107,166281],{},[812,166283,53869],{"href":82688,"rel":166284},[816],{"title":674,"searchDepth":790,"depth":790,"links":166286},[166287,166288,166295,166296,166304],{"id":166036,"depth":790,"text":166037},{"id":43653,"depth":790,"text":43654,"children":166289},[166290,166291,166292,166293,166294],{"id":67441,"depth":892,"text":67442},{"id":166082,"depth":892,"text":166078},{"id":166105,"depth":892,"text":26194},{"id":11128,"depth":892,"text":26164},{"id":61224,"depth":892,"text":15432},{"id":166156,"depth":790,"text":43833},{"id":157573,"depth":790,"text":157574,"children":166297},[166298,166299,166300,166301,166302,166303],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":43653,"date":166306},"2021-11-22T09:00:00.000Z","/newsletter/2021/11/22/content-creation",{"title":166028,"description":166033},"newsletter/2021/11/22/content-creation","yWDHDaWOcdAswXbOD3QGgYTCUQCR8WU_rToE4guFbbI",{"id":166312,"title":166313,"body":166314,"description":166318,"extension":793,"meta":166658,"navigation":797,"path":166661,"seo":166662,"stem":166663,"__hash__":166664},"content/newsletter/2021/12/06/thankful.md","What I'm thankful for, AWS, Course Graphics & YouTube",{"type":648,"value":166315,"toc":166637},[166316,166319,166323,166326,166330,166333,166336,166342,166346,166349,166352,166355,166358,166367,166370,166373,166377,166380,166383,166386,166389,166397,166405,166419,166422,166425,166434,166443,166447,166455,166464,166484,166487,166493,166495,166503,166508,166511,166513,166515,166545,166547,166570,166572,166588,166590,166597,166599,166602,166611,166614,166617,166619,166624],[651,166317,166318],{},"After taking a week off due to Thanksgiving I'm back with another edition of the newsletter. I had some time to think about what I am thankful for over the holiday and I figured I would start by sharing that with you.",[4542,166320,166322],{"id":166321},"thankful-for","Thankful for",[651,166324,166325],{},"It's been a crazy couple of years on this planet. I have been so focused this year on things in my life that I would like to change that I lost sight of so many things I am grateful for. This isn't the entire list, but it is the important ones.",[5909,166327,166329],{"id":166328},"you","You",[651,166331,166332],{},"I am thankful for each and every one of you. Thank you for signing up for this newsletter, reading my articles, watching my videos, and supporting the content that I put out there. Some of you pay for my content and honestly I'm humbled that you choose to learn from me. I love to learn and I love to teach the things that I find interesting back to you.",[651,166334,166335],{},"I love hearing from you so please reach out to me by replying to this email or like Chol did by tweeting at me. Thank you for the kind words Chol and I am glad to hear that my monolothic solution worked for you.",[651,166337,166338],{},[812,166339,166340],{"href":166340,"rel":166341},"https://twitter.com/actualsudo/status/1467470501400354818",[816],[5909,166343,166345],{"id":166344},"my-family","My Family",[651,166347,166348],{},"My family is the reason I get out of bed every single day. They are my rock and they push me to be a better Husband, Father, and person. I want them to be proud of me and I think about that often. I would be lying if I said that raising 2 babies through a pandemic has been easy but I think we are all stronger and closer because of it. It's been a dream come true watching them learn and grow and I am thankful for that ❤️",[5909,166350,166351],{"id":22781},"Health",[651,166353,166354],{},"In the time of a global pandemic, I don't know how you don't think about your health. My family has remained healthy through these tough times and I am so thankful for that.",[651,166356,166357],{},"Personally, it's been a rough year for me. For those who don't know, I have Type 2 diabetes. Outside of my family and very close friends I don't think I have ever told anyone that. It probably has something to do with me being ashamed or embarrassed but if you can't tell 4,000+ of your closest friends on a newsletter, who can you tell 🤷♂️",[651,166359,166360,166361,166366],{},"I don't want to get into the specifics on how I got here but I will share with you what has been going on lately. This year between stress, not sleeping (baby), lack of exercise (see previous 2), not testing every day and bad nutritional choices on my part my A1C reached an 11.1. For those of you that don't know, that is ",[7300,166362,166363],{},[2939,166364,166365],{},"very"," bad.",[651,166368,166369],{},"This was a huge wake-up call for me and a reminder that nothing I do is more important than my health. Your health is everything and if you don't get that right it will affect everything else in your life. I have always loved working out, it is my escape and something I get to do for myself. There was a huge lack of that this year and that has been affecting me mentally and physically. Over the last month or so I have gotten back to running and It's amazing what it can do for your mind and body!",[651,166371,166372],{},"I'm sharing this with you as a reminder that nothing is more important than your health.",[5909,166374,166376],{"id":166375},"my-career","My Career",[651,166378,166379],{},"I get to do what I love each and every day and I don't know that everyone gets to say that. When I put some headphones on and sit down to write code or create content I can't tell you how happy I am. Life is too short friends, If you're not happy it's up to fix that.",[651,166381,166382],{},"I'm not telling you to go quit your job because at the end of the day most of us need to work but you need to find something that gets you out of bed in the morning and that inspires you.",[4542,166384,43827],{"id":166385},"amazon-web-services-aws",[651,166387,166388],{},"I have some experience with AWS but mostly building tutorials and demos. I signed up for a free account about 5 years, know my way around the console and at high level can tell you what a lot of the common services are used for.",[651,166390,166391,166392,166396],{},"I put together a nice demo for my ",[812,166393,166395],{"href":79477,"rel":166394},[816],"Spring Boot 2 course"," on how to deploy a Spring Boot Application on AWS using Elastic Beanstalk. AWS is something I have wanted to level up on and there is no time like the present.",[651,166398,166399,166400,166404],{},"With all of the Black Friday sales going on I decided to invest in myself. ",[812,166401,43636],{"href":166402,"rel":166403},"https://acloudguru.com/",[816]," was running a 40% off promotion (it's still valid) so I decided to pickup a yearly subscription. I had a subscription to them a couple of years ago so I already knew about the amazing content they produce. My main goal with this subscription is to start preparing for certifications. These are the certs I have my eye on but I honestly have no long this will take me.",[5316,166406,166407,166410,166413,166416],{},[5332,166408,166409],{},"Cloud Practitioner",[5332,166411,166412],{},"Developer Associate",[5332,166414,166415],{},"Solutions Architect Associate",[5332,166417,166418],{},"Solutions Architect Professional",[651,166420,166421],{},"If anyone has all four of these certs I would love to know how long it took you to prepare and take the exam for all of them. I am hoping to finish these quickly but I'm not sure what a realistic goal should be.",[651,166423,166424],{},"The second part to this puzzle is architecting, building and deploying solutions on AWS. I have a couple of ideas for projects and the first one would make a great fit on AWS. To accomplish this you really need to understand the different technologies used to build out these solutions.",[651,166426,166427,166428,166433],{},"As you know I'm also a huge fan of Java & Spring Boot so to learn how to go from zero to production with Spring Boot and AWS I picked up this book ",[812,166429,166432],{"href":166430,"rel":166431},"https://leanpub.com/stratospheric",[816],"Stratospheric"," which does a great job of covering that. I know 2 of the authors on this book Tom & Phillip and I love everything the put out there so I am really excited to dive into this one.",[651,166435,166436,166437,166442],{},"If you have any resources for AWS or tips & tricks please reach out to me. I got really jealous of all the tweets from ",[7300,166438,166439],{},[2939,166440,166441],{},"re:invent"," this week and I hope to be there next year!",[4542,166444,166446],{"id":166445},"course-graphics","Course Graphics",[651,166448,166449,166450,166454],{},"I have a bunch of courses that I have created over at ",[812,166451,166453],{"href":100862,"rel":166452},[816],"danvega.dev/courses",". Each of the course graphics are something that I put together and they are mostly just stock graphics I found over the years. I want to get some custom graphics designed for each of the courses and I also wanted them to follow some type of theme.",[651,166456,166457,166458,166463],{},"With that I went over to ",[812,166459,166462],{"href":166460,"rel":166461},"https://upwork.com/",[816],"Upwork"," and put up a job description of what I was looking for. In there I said that these were 3 examples of course graphics that I really love.",[5316,166465,166466,166472,166478],{},[5332,166467,166468],{},[812,166469,166470],{"href":166470,"rel":166471},"https://vueschool.io/courses",[816],[5332,166473,166474],{},[812,166475,166476],{"href":166476,"rel":166477},"https://m.academy/courses/",[816],[5332,166479,166480],{},[812,166481,166482],{"href":166482,"rel":166483},"https://egghead.io/q/typescript",[816],[651,166485,166486],{},"I wanted something unique and I excited to share with you one of the results. The following graphic was created for my Groovy Course. What do you think?",[651,166488,166489],{},[660,166490],{"alt":166491,"src":166492},"Groovy Course Cover","/images/newsletter/2021/12/06/groovy-course-cover.jpg",[4542,166494,15432],{"id":61224},[651,166496,166497,166498,166502],{},"Back in April of this year I created a video on how to ",[812,166499,166501],{"href":82636,"rel":166500},[816],"Create Your First Spring App",". The idea here was to create a spring application from scratch without Spring Boot. I think this is a really great way to understand what Spring Boot is doing for you behind the scenes. A new subscriber, John left this comment and it made my day.",[1004,166504,166505],{},[651,166506,166507],{},"This is really helpful because as a beginner I now have done a lot of research until I finally had created my little big picture (LOL) in the world of Spring and what all to learn. I truly must say that without understanding whats going on under the hood of spring boot you have a much longer way to understand what this framework is about. Dependency Injection (Bean Factory vs. ApplicationContext), Annotations, Beans, (also Generics, Reflections etc.) and my conclusion is you should learn Spring before diving into Spring Boot. There are a lot of fancy courses and videos out there but without understanding the internals ... well, it's more difficult. I would like to see a video were you build a bigger Spring app with Spring MVC, JDBC, REST etc.. Thanks for the content, this helped me so much, this is exactly the maximum level of complexity I need. Hope to see more about Spring internals from you in the future. Subscribed!",[651,166509,166510],{},"I know I'm going to sound like a broken record here but I love hearing you from you guys. This comment made my day and helps validate that the content I am creating is helping people. Thanks John 👏🏻👨🏫",[4542,166512,157574],{"id":157573},[5909,166514,164959],{"id":69848},[5316,166516,166517,166524,166531,166538],{},[5332,166518,166519],{},[812,166520,166523],{"href":166521,"rel":166522},"https://devblogs.microsoft.com/java/say-goodbye-to-project-files-in-1-1-0/",[816],"Say goodbye to “.project” files in Language Support for Java™ 1.1.0",[5332,166525,166526],{},[812,166527,166530],{"href":166528,"rel":166529},"https://applitools.com/blog/whats-new-cypress-9/",[816],"What’s New in Cypress 9",[5332,166532,166533],{},[812,166534,166537],{"href":166535,"rel":166536},"https://www.peelar.dev/posts/first-look-at-remix",[816],"First look at Remix",[5332,166539,166540],{},[812,166541,166544],{"href":166542,"rel":166543},"https://blog.plasmic.app/posts/why-remix-is-worth-your-attention/",[816],"Why Remix is worth your attention",[5909,166546,164971],{"id":157591},[5316,166548,166549,166556,166563],{},[5332,166550,166551],{},[812,166552,166555],{"href":166553,"rel":166554},"https://www.youtube.com/watch?v=AeiBk2hqZTI",[816],"Three Steps to Accelerate Java Application Performance (Scott Seighman)",[5332,166557,166558],{},[812,166559,166562],{"href":166560,"rel":166561},"https://www.youtube.com/watch?v=ow5kdhDa_pk",[816],"Meet JetBrains Fleet — The Next-generation IDE by JetBrains",[5332,166564,166565],{},[812,166566,166569],{"href":166567,"rel":166568},"https://www.youtube.com/watch?v=8_Xs8Ik0h1w&t=3327s",[816],"AWS Amplify Studio Announcement",[5909,166571,164995],{"id":133422},[5316,166573,166574,166581],{},[5332,166575,166576],{},[812,166577,166580],{"href":166578,"rel":166579},"https://remix.run/",[816],"Remix - Build Better Websites",[5332,166582,166583],{},[812,166584,166587],{"href":166585,"rel":166586},"https://supabase.com/",[816],"Open Source PosgreSQL Studio",[5909,166589,165005],{"id":39340},[5316,166591,166592],{},[5332,166593,166594],{},[812,166595,166432],{"href":166430,"rel":166596},[816],[5909,166598,165017],{"id":165016},[5909,166600,166601],{"id":21972},"👨🏼💻 Courses",[5316,166603,166604],{},[5332,166605,166606],{},[812,166607,166610],{"href":166608,"rel":166609},"https://acloudguru.com/course/aws-certified-cloud-practitioner-2020",[816],"AWS Certified Cloud Practitioner 2020",[5909,166612,165017],{"id":166613},"️-quote-of-the-week-1",[651,166615,166616],{},"“Physical fitness is the first requisite of happiness.” – Joseph Pilates.",[4542,166618,157704],{"id":157703},[651,166620,166267,166621,166271],{},[812,166622,41499],{"href":44086,"rel":166623},[816],[651,166625,41105,166626,69920,166628,166630,166632,166634],{},[41107,166627],{},[41107,166629],{},[812,166631,161560],{"href":161111},[41107,166633],{},[812,166635,53869],{"href":82688,"rel":166636},[816],{"title":674,"searchDepth":790,"depth":790,"links":166638},[166639,166645,166646,166647,166648,166657],{"id":166321,"depth":790,"text":166322,"children":166640},[166641,166642,166643,166644],{"id":166328,"depth":892,"text":166329},{"id":166344,"depth":892,"text":166345},{"id":22781,"depth":892,"text":166351},{"id":166375,"depth":892,"text":166376},{"id":166385,"depth":790,"text":43827},{"id":166445,"depth":790,"text":166446},{"id":61224,"depth":790,"text":15432},{"id":157573,"depth":790,"text":157574,"children":166649},[166650,166651,166652,166653,166654,166655,166656],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":21972,"depth":892,"text":166601},{"id":166613,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":166659,"date":166660},"thankful","2021-12-06T07:30:00.000Z","/newsletter/2021/12/06/thankful",{"title":166313,"description":166318},"newsletter/2021/12/06/thankful","DMnr2QAELwrbSMtbIVP42tQXaDvhn0M7TckTT7dBIoo",{"id":166666,"title":166667,"body":166668,"description":166672,"extension":793,"meta":166992,"navigation":797,"path":166995,"seo":166996,"stem":166997,"__hash__":166998},"content/newsletter/2021/12/13/new-contract.md","New Contract, Log4J Vulnerability, Egghead & Tailwind v3",{"type":648,"value":166669,"toc":166975},[166670,166673,166677,166680,166683,166693,166699,166702,166705,166709,166723,166726,166735,166739,166748,166752,166755,166758,166767,166773,166776,166792,166797,166801,166809,166839,166842,166848,166852,166855,166861,166863,166865,166892,166894,166917,166919,166939,166941,166950,166952,166955,166957,166962],[651,166671,166672],{},"I hope you all had a great week last week and are excited for another productive week. Christmas is coming up on us quickly and with that the end of another year. As I have told you a few times it's been a little of a crazy year for me (and probably all of us) so I am looking forward to the new year. In this edition of the newsletter, I want to tell you about starting a new contract at work, and some exciting things that happened last week.",[4542,166674,166676],{"id":166675},"started-a-new-contract-at-work","Started a new contract at work",[651,166678,166679],{},"Last week I started a new contract at work. If you don't know I work for Briebug Software and I have been with them for about a year now. When I started I was assigned to the biggest logistics company on the planet. I am still with that client but I ended up transferring to another org within the company.",[651,166681,166682],{},"With that transfer, I will have to learn a whole new side of the business and the software that runs it. Part of my responsibilities as an architect is to understand the business, the software and make recommendations where I think we can we can improve the product for both the users and the developers. As a developer, I take that last responsibility to heart. I want to make sure that I am helping anyone on that project be successful.",[651,166684,166685,166686,166688,166689,166692],{},"Most of the new software I am working on is in Spring Boot / Spring Cloud / Java space. I tweeted this out earlier this week but the first thing I do when I am dropped into a new project is to make sure I can build it. After a few minutes, I was able to get this project to build and my next step is to take a peek at the build file. In this case, we are using Maven so I opened up ",[676,166687,81517],{}," to see what I'm working with. The first thing I noticed is there were a TON of dependencies and my first thought was there is no way we are using all of them. A quick run of ",[676,166690,166691],{},"mvn dependency:anaylze"," and my suspicions were true, there was a whole lot of unused dependencies.",[651,166694,166695],{},[812,166696,166697],{"href":166697,"rel":166698},"https://twitter.com/therealdanvega/status/1467920900456697866",[816],[651,166700,166701],{},"These are the easy types of recommendations you can make right away. Removing all of these unused dependencies can speed up startup time for developers and ultimately make the executable we ship much smaller. I created a new branch and removed all of them only to come across a few errors. Remember that this tool is analyzing what dependencies you are using at compile-time and won't know which ones are being used at runtime. After fixing a few of those, we have a much faster build and a lighter JAR that will get shipped to the cloud.",[651,166703,166704],{},"What are some of the things you look for when you are first dropped into a project? I have a few more and maybe I will compile a checklist if that is something that might be helpful to others.",[4542,166706,166708],{"id":166707},"log4j-vulnerability","Log4J Vulnerability",[651,166710,166711,166712,166717,166718,664],{},"If you're in the Java world chances are you have probably heard of this by now. There was an exploit found in the popular logging library, ",[812,166713,166716],{"href":166714,"rel":166715},"https://logging.apache.org/log4j/2.x/",[816],"Log4J",". If you want to learn more about the exploit you can check out ",[812,166719,166722],{"href":166720,"rel":166721},"http://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-44228",[816],"CVE-2021-44228",[651,166724,166725],{},"This zero day vulnerability affects the 2.x line so if you're using the Log4J library explicitly please make sure you update to v2.15 where the behavior and root cause of the issue have been disabled by default. There is a chance you could be using it implicitly if a dependency you declared uses it, also know as a transitive dependency.",[651,166727,166728,166729,166734],{},"There is a really ",[812,166730,166733],{"href":166731,"rel":166732},"https://msrc-blog.microsoft.com/2021/12/11/microsofts-response-to-cve-2021-44228-apache-log4j2/?s=09",[816],"great explanation from Microsoft"," on what this vulnerability is and how it works. This is a serious exploit and you should take the time to investigate if you are using Log4J. If you are using it explicitly or implicitly please take the necessary steps to fixing it.",[5909,166736,166738],{"id":166737},"spring-boot-users","Spring Boot Users",[651,166740,166741,166742,166747],{},"If you're a Spring Boot users this shouldn't affect unless you have moved away from the default logging system, Logback. There is a good article on the ",[812,166743,166746],{"href":166744,"rel":166745},"https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot",[816],"Spring Blog"," that shares some details about this and how to fix it.",[4542,166749,166751],{"id":166750},"egghead-holiday-courses","Egghead holiday courses",[651,166753,166754],{},"Learning is one of the biggest skills we need as software engineers. I think it's one of the main reasons I love what I do. If you heard me talk about this at all you know I love to learn new things.",[651,166756,166757],{},"I think everyone has a different way of learning depending on what your objective is. If you're brand new to language and programming in general you may want to take a depth vs breath approach. In that case you might want to reach for one of those big text books and learn everything about the subject.",[651,166759,166760,166761,166766],{},"In some cases you might want to learn one specific thing. For those instances I have been a big fan of Egghead courses. These courses lose the fluff and drop you right into the subject at hand. Right now they have a ",[812,166762,166765],{"href":166763,"rel":166764},"https://egghead.io/learn",[816],"Holiday Course Release Extravaganza"," going on where they are releasing a new course every day.",[651,166768,166769],{},[660,166770],{"alt":166771,"src":166772},"Egghead Holiday Course Release Extravaganza","/images/newsletter/2021/12/13/egghead.png",[651,166774,166775],{},"I was able to go through a couple courses that I can highly recommend:",[5316,166777,166778,166785],{},[5332,166779,166780],{},[812,166781,166784],{"href":166782,"rel":166783},"https://egghead.io/courses/cloud-infrastructure-fundamentals-with-aws-ee4bb845",[816],"Cloud Infrastructure Fundamentals with AWS by Sam Julien",[5332,166786,166787],{},[812,166788,166791],{"href":166789,"rel":166790},"https://egghead.io/courses/build-a-digital-garden-with-nuxt-and-nuxt-content-module-9b67f0de",[816],"Build a Digital Garden with Nuxt and Nuxt Content Module by Ben Hong",[651,166793,166794],{},[7300,166795,166796],{},"What are some of your favorite resources for learning?",[4542,166798,166800],{"id":166799},"tailwind-css-v30","Tailwind CSS v3.0",[651,166802,166803,166804,166808],{},"Tailwind CSS, a CSS Utility framework that I happen to be a big fan of released v3.0 last week. There is a great roundup here of all the new features ",[812,166805,18263],{"href":166806,"rel":166807},"https://tailwindcss.com/blog/tailwindcss-v3",[816]," but here are a few of my favorite.",[5316,166810,166811,166817,166827,166833],{},[5332,166812,166813,166816],{},[2939,166814,166815],{},"Just-in-Time, all the time"," — lightning fast build times, stackable variants, arbitrary value support, better browser performance, and more.",[5332,166818,166819,166822,166823,166826],{},[2939,166820,166821],{},"Every color out of the box"," — including all of the extended palette colors like cyan, rose, fuchsia, and lime, and fifty shades of ",[104514,166824,166825],{},"grey"," gray.",[5332,166828,166829,166832],{},[2939,166830,166831],{},"Colored box shadows"," — for fun glow and reflection effects, and more natural shadows on colored backgrounds.",[5332,166834,166835,166838],{},[2939,166836,166837],{},"Play CDN"," — the new Just-in-Time engine squeezed into a CDN script that runs right in the browser.",[651,166840,166841],{},"If you have some time check out this video below where Simon walks us through all the cool new features.",[651,166843,166844],{},[812,166845,166846],{"href":166846,"rel":166847},"https://www.youtube.com/watch?v=mSC6GwizOag",[816],[4542,166849,166851],{"id":166850},"matrix-resurrections-official-trailer-2","Matrix Resurrections - Official Trailer 2",[651,166853,166854],{},"The original Matrix will go down as one of my all time favorite movies. I'm also probably in the minority here, but I enjoyed all 3 of the movies. That is why I am so excited for The Matrix Resurrections which comes out later this month. I have a subscription to HBO Max so I can technically watch this one at home but I think this is a good time to return to the theater.",[651,166856,166857],{},[812,166858,166859],{"href":166859,"rel":166860},"https://www.youtube.com/watch?v=nNpvWBuTfrc",[816],[4542,166862,157574],{"id":157573},[5909,166864,164959],{"id":69848},[5316,166866,166867,166874,166879,166886],{},[5332,166868,166869],{},[812,166870,166873],{"href":166871,"rel":166872},"https://vladmihalcea.com/log-sql-spring-boot/",[816],"The best way to log SQL statements with Spring Boot",[5332,166875,166876],{},[812,166877,166800],{"href":166806,"rel":166878},[816],[5332,166880,166881],{},[812,166882,166885],{"href":166883,"rel":166884},"https://spring.io/blog/2021/12/09/new-aot-engine-brings-spring-native-to-the-next-level",[816],"New AOT Engine Brings Spring Native to the Next Level",[5332,166887,166888],{},[812,166889,166890],{"href":166890,"rel":166891},"https://tanzu.vmware.com/content/ebooks/the-state-of-spring-2021",[816],[5909,166893,164971],{"id":157591},[5316,166895,166896,166903,166910],{},[5332,166897,166898],{},[812,166899,166902],{"href":166900,"rel":166901},"https://www.youtube.com/watch?v=Y1bwOL08mqs",[816],"Spring Cloud Stream with RabbitMQ",[5332,166904,166905],{},[812,166906,166909],{"href":166907,"rel":166908},"https://www.youtube.com/watch?v=qEBEo76gKK0",[816],"Building a Course Platform in a Weekend - (Next.js Conf 2021)",[5332,166911,166912],{},[812,166913,166916],{"href":166914,"rel":166915},"https://www.youtube.com/watch?v=W9KdGcL80jQ",[816],"Video Editing 101: How to Edit YouTube Thumbnails",[5909,166918,164983],{"id":39439},[5316,166920,166921,166932],{},[5332,166922,166923,166924],{},"A ",[812,166925,166928,166929],{"href":166926,"rel":166927},"https://bootifulpodcast.fm/#/episodes/5878ef4e-c575-463b-a27e-67548fbad6d0",[816],"Bootiful Podcast - Josh Long with ",[2939,166930,166931],{},"Patrick Chanezon",[5332,166933,166934],{},[812,166935,166938],{"href":166936,"rel":166937},"https://softwareengineeringdaily.com/2021/12/06/building-go-apps-using-encore/",[816],"Building Go Apps Using Encore with André Eriksson",[5909,166940,165005],{"id":39340},[5316,166942,166943],{},[5332,166944,166945],{},[812,166946,166949],{"href":166947,"rel":166948},"https://www.coldstart.com/?twclid=11468056396621139976",[816],"The Cold Start Problem",[5909,166951,165017],{"id":165016},[651,166953,166954],{},"“Patience is not the ability to wait, but the ability to keep a good attitude while waiting.” - Unknown",[4542,166956,157704],{"id":157703},[651,166958,166267,166959,166271],{},[812,166960,41499],{"href":44086,"rel":166961},[816],[651,166963,41105,166964,69920,166966,166968,166970,166972],{},[41107,166965],{},[41107,166967],{},[812,166969,161560],{"href":161111},[41107,166971],{},[812,166973,53869],{"href":82688,"rel":166974},[816],{"title":674,"searchDepth":790,"depth":790,"links":166976},[166977,166978,166981,166982,166983,166984,166991],{"id":166675,"depth":790,"text":166676},{"id":166707,"depth":790,"text":166708,"children":166979},[166980],{"id":166737,"depth":892,"text":166738},{"id":166750,"depth":790,"text":166751},{"id":166799,"depth":790,"text":166800},{"id":166850,"depth":790,"text":166851},{"id":157573,"depth":790,"text":157574,"children":166985},[166986,166987,166988,166989,166990],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":166993,"date":166994},"new-contract","2021-12-13T08:30:00.000Z","/newsletter/2021/12/13/new-contract",{"title":166667,"description":166672},"newsletter/2021/12/13/new-contract","9uS3mqG1tsO4UrDmO0E6PShBlJcmRa4RnJI3iueCN74",{"id":167000,"title":167001,"body":167002,"description":167006,"extension":793,"meta":167935,"navigation":797,"path":167937,"seo":167938,"stem":167939,"__hash__":167940},"content/newsletter/2021/12/20/log4j.md","Log4J Vulnerablity, Merry Christmas & Happy New Year!",{"type":648,"value":167003,"toc":167919},[167004,167007,167011,167014,167033,167162,167173,167396,167403,167442,167446,167460,167632,167639,167712,167726,167776,167780,167783,167788,167791,167797,167799,167801,167824,167826,167849,167851,167860,167863,167872,167876,167886,167888,167890,167896,167900,167903,167916],[651,167005,167006],{},"Welcome to what might very well be the last installment of the newsletter in 2021. I only have a few days left of work for the year and honestly, I don't think I will have much to talk about. I am working on a couple of exciting things for the new year and I can't wait to share those with you. In this week's edition of the newsletter we continue our conversation about the Log4J2 Vulnerability.",[4542,167008,167010],{"id":167009},"are-your-spring-boot-applications-vulnerable-to-the-log4j2-exploit","Are your Spring Boot Applications vulnerable to the Log4J2 Exploit?",[651,167012,167013],{},"In last week's newsletter, I mentioned the Log4J Vulnerability that was going around. I had some more time at the beginning of the week to dig into it and more specifically if we were affected at work. This brought up a lot of questions around how logging works in Spring Boot so I thought I would go through some of that here.",[651,167015,167016,167017,167020,167021,167024,167025,167028,167029,167032],{},"The biggest thing you need to know from a Spring Boot standpoint is that if you don't deviate from the default configuration you are safe. Let's start out with how logging works out of the box. When you add a dependency like ",[676,167018,167019],{},"web (spring-boot-starter-web)"," that will bring in the ",[676,167022,167023],{},"spring-boot-starter"," which brings in ",[676,167026,167027],{},"spring-boot-starter-logging."," If you run ",[676,167030,167031],{},"mvn dependency:tree"," you will see something that looks like this:",[669,167034,167036],{"className":5851,"code":167035,"language":5853,"meta":674,"style":674},"[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.6.1:compile\n[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.6.1:compile\n[INFO] | | +- org.springframework.boot:spring-boot:jar:2.6.1:compile\n[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.6.1:compile\n[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.6.1:compile\n[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.7:compile\n[INFO] | | | | \\- ch.qos.logback:logback-core:jar:1.2.7:compile\n[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.14.1:compile\n[INFO] | | | | \\- org.apache.logging.log4j:log4j-api:jar:2.14.1:compile\n",[676,167037,167038,167043,167057,167071,167084,167097,167112,167130,167145],{"__ignoreMap":674},[679,167039,167040],{"class":681,"line":682},[679,167041,167042],{"class":693},"[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.6.1:compile\n",[679,167044,167045,167048,167051,167054],{"class":681,"line":790},[679,167046,167047],{"class":693},"[INFO] ",[679,167049,167050],{"class":685},"|",[679,167052,167053],{"class":880}," +-",[679,167055,167056],{"class":689}," org.springframework.boot:spring-boot-starter:jar:2.6.1:compile\n",[679,167058,167059,167061,167063,167066,167068],{"class":681,"line":892},[679,167060,167047],{"class":693},[679,167062,167050],{"class":685},[679,167064,167065],{"class":685}," |",[679,167067,167053],{"class":880},[679,167069,167070],{"class":689}," org.springframework.boot:spring-boot:jar:2.6.1:compile\n",[679,167072,167073,167075,167077,167079,167081],{"class":681,"line":901},[679,167074,167047],{"class":693},[679,167076,167050],{"class":685},[679,167078,167065],{"class":685},[679,167080,167053],{"class":880},[679,167082,167083],{"class":689}," org.springframework.boot:spring-boot-autoconfigure:jar:2.6.1:compile\n",[679,167085,167086,167088,167090,167092,167094],{"class":681,"line":909},[679,167087,167047],{"class":693},[679,167089,167050],{"class":685},[679,167091,167065],{"class":685},[679,167093,167053],{"class":880},[679,167095,167096],{"class":689}," org.springframework.boot:spring-boot-starter-logging:jar:2.6.1:compile\n",[679,167098,167099,167101,167103,167105,167107,167109],{"class":681,"line":918},[679,167100,167047],{"class":693},[679,167102,167050],{"class":685},[679,167104,167065],{"class":685},[679,167106,167065],{"class":685},[679,167108,167053],{"class":880},[679,167110,167111],{"class":689}," ch.qos.logback:logback-classic:jar:1.2.7:compile\n",[679,167113,167114,167116,167118,167120,167122,167124,167127],{"class":681,"line":935},[679,167115,167047],{"class":693},[679,167117,167050],{"class":685},[679,167119,167065],{"class":685},[679,167121,167065],{"class":685},[679,167123,167065],{"class":685},[679,167125,167126],{"class":880}," \\-",[679,167128,167129],{"class":689}," ch.qos.logback:logback-core:jar:1.2.7:compile\n",[679,167131,167132,167134,167136,167138,167140,167142],{"class":681,"line":944},[679,167133,167047],{"class":693},[679,167135,167050],{"class":685},[679,167137,167065],{"class":685},[679,167139,167065],{"class":685},[679,167141,167053],{"class":880},[679,167143,167144],{"class":689}," org.apache.logging.log4j:log4j-to-slf4j:jar:2.14.1:compile\n",[679,167146,167147,167149,167151,167153,167155,167157,167159],{"class":681,"line":959},[679,167148,167047],{"class":693},[679,167150,167050],{"class":685},[679,167152,167065],{"class":685},[679,167154,167065],{"class":685},[679,167156,167065],{"class":685},[679,167158,167126],{"class":880},[679,167160,167161],{"class":689}," org.apache.logging.log4j:log4j-api:jar:2.14.1:compile\n",[651,167163,167164,167165,167168,167169,167172],{},"Spring Boot uses SLF4J which is a facade that builds on top of logging frameworks like Logback and Log4J2. If you take a look at the dependencies in ",[676,167166,167167],{},"spring-boot-starter-logging"," you will notice that ",[676,167170,167171],{},"logback-classic"," is the first dependency.",[669,167174,167176],{"className":9101,"code":167175,"language":9103,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>ch.qos.logback\u003C/groupId>\n \u003CartifactId>logback-classic\u003C/artifactId>\n \u003Cversion>1.2.7\u003C/version>\n \u003Cscope>compile\u003C/scope>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.apache.logging.log4j\u003C/groupId>\n \u003CartifactId>log4j-to-slf4j\u003C/artifactId>\n \u003Cversion>2.14.1\u003C/version>\n \u003Cscope>compile\u003C/scope>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.slf4j\u003C/groupId>\n \u003CartifactId>jul-to-slf4j\u003C/artifactId>\n \u003Cversion>1.7.32\u003C/version>\n \u003Cscope>compile\u003C/scope>\n \u003C/dependency>\n\u003C/dependencies>\n",[676,167177,167178,167186,167194,167207,167220,167233,167246,167254,167262,167275,167288,167301,167313,167321,167329,167342,167355,167368,167380,167388],{"__ignoreMap":674},[679,167179,167180,167182,167184],{"class":681,"line":682},[679,167181,4505],{"class":693},[679,167183,129682],{"class":4508},[679,167185,4519],{"class":693},[679,167187,167188,167190,167192],{"class":681,"line":790},[679,167189,11738],{"class":693},[679,167191,119838],{"class":4508},[679,167193,4519],{"class":693},[679,167195,167196,167198,167200,167203,167205],{"class":681,"line":892},[679,167197,4524],{"class":693},[679,167199,119847],{"class":4508},[679,167201,167202],{"class":693},">ch.qos.logback\u003C/",[679,167204,119847],{"class":4508},[679,167206,4519],{"class":693},[679,167208,167209,167211,167213,167216,167218],{"class":681,"line":901},[679,167210,4524],{"class":693},[679,167212,119861],{"class":4508},[679,167214,167215],{"class":693},">logback-classic\u003C/",[679,167217,119861],{"class":4508},[679,167219,4519],{"class":693},[679,167221,167222,167224,167226,167229,167231],{"class":681,"line":909},[679,167223,4524],{"class":693},[679,167225,107166],{"class":4508},[679,167227,167228],{"class":693},">1.2.7\u003C/",[679,167230,107166],{"class":4508},[679,167232,4519],{"class":693},[679,167234,167235,167237,167239,167242,167244],{"class":681,"line":918},[679,167236,4524],{"class":693},[679,167238,155358],{"class":4508},[679,167240,167241],{"class":693},">compile\u003C/",[679,167243,155358],{"class":4508},[679,167245,4519],{"class":693},[679,167247,167248,167250,167252],{"class":681,"line":935},[679,167249,11840],{"class":693},[679,167251,119838],{"class":4508},[679,167253,4519],{"class":693},[679,167255,167256,167258,167260],{"class":681,"line":944},[679,167257,11738],{"class":693},[679,167259,119838],{"class":4508},[679,167261,4519],{"class":693},[679,167263,167264,167266,167268,167271,167273],{"class":681,"line":959},[679,167265,4524],{"class":693},[679,167267,119847],{"class":4508},[679,167269,167270],{"class":693},">org.apache.logging.log4j\u003C/",[679,167272,119847],{"class":4508},[679,167274,4519],{"class":693},[679,167276,167277,167279,167281,167284,167286],{"class":681,"line":964},[679,167278,4524],{"class":693},[679,167280,119861],{"class":4508},[679,167282,167283],{"class":693},">log4j-to-slf4j\u003C/",[679,167285,119861],{"class":4508},[679,167287,4519],{"class":693},[679,167289,167290,167292,167294,167297,167299],{"class":681,"line":977},[679,167291,4524],{"class":693},[679,167293,107166],{"class":4508},[679,167295,167296],{"class":693},">2.14.1\u003C/",[679,167298,107166],{"class":4508},[679,167300,4519],{"class":693},[679,167302,167303,167305,167307,167309,167311],{"class":681,"line":982},[679,167304,4524],{"class":693},[679,167306,155358],{"class":4508},[679,167308,167241],{"class":693},[679,167310,155358],{"class":4508},[679,167312,4519],{"class":693},[679,167314,167315,167317,167319],{"class":681,"line":988},[679,167316,11840],{"class":693},[679,167318,119838],{"class":4508},[679,167320,4519],{"class":693},[679,167322,167323,167325,167327],{"class":681,"line":993},[679,167324,11738],{"class":693},[679,167326,119838],{"class":4508},[679,167328,4519],{"class":693},[679,167330,167331,167333,167335,167338,167340],{"class":681,"line":2129},[679,167332,4524],{"class":693},[679,167334,119847],{"class":4508},[679,167336,167337],{"class":693},">org.slf4j\u003C/",[679,167339,119847],{"class":4508},[679,167341,4519],{"class":693},[679,167343,167344,167346,167348,167351,167353],{"class":681,"line":2140},[679,167345,4524],{"class":693},[679,167347,119861],{"class":4508},[679,167349,167350],{"class":693},">jul-to-slf4j\u003C/",[679,167352,119861],{"class":4508},[679,167354,4519],{"class":693},[679,167356,167357,167359,167361,167364,167366],{"class":681,"line":2145},[679,167358,4524],{"class":693},[679,167360,107166],{"class":4508},[679,167362,167363],{"class":693},">1.7.32\u003C/",[679,167365,107166],{"class":4508},[679,167367,4519],{"class":693},[679,167369,167370,167372,167374,167376,167378],{"class":681,"line":2154},[679,167371,4524],{"class":693},[679,167373,155358],{"class":4508},[679,167375,167241],{"class":693},[679,167377,155358],{"class":4508},[679,167379,4519],{"class":693},[679,167381,167382,167384,167386],{"class":681,"line":2159},[679,167383,11840],{"class":693},[679,167385,119838],{"class":4508},[679,167387,4519],{"class":693},[679,167389,167390,167392,167394],{"class":681,"line":2164},[679,167391,4586],{"class":693},[679,167393,129682],{"class":4508},[679,167395,4519],{"class":693},[651,167397,167398,167399,167402],{},"If we go back to that tree view there are 2 dependencies with the name log4j in them. I want to make sure we are clear here, these 2 dependencies by themselves will not make you vulnerable to the exploit. You need to also have the ",[676,167400,167401],{},"log4j-core.jar"," on the classpath and current it is not.",[669,167404,167406],{"className":5851,"code":167405,"language":5853,"meta":674,"style":674},"\n[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.14.1:compile\n[INFO] | | | | \\- org.apache.logging.log4j:log4j-api:jar:2.14.1:compile\n\n\n",[676,167407,167408,167412,167426],{"__ignoreMap":674},[679,167409,167410],{"class":681,"line":682},[679,167411,889],{"emptyLinePlaceholder":797},[679,167413,167414,167416,167418,167420,167422,167424],{"class":681,"line":790},[679,167415,167047],{"class":693},[679,167417,167050],{"class":685},[679,167419,167065],{"class":685},[679,167421,167065],{"class":685},[679,167423,167053],{"class":880},[679,167425,167144],{"class":689},[679,167427,167428,167430,167432,167434,167436,167438,167440],{"class":681,"line":892},[679,167429,167047],{"class":693},[679,167431,167050],{"class":685},[679,167433,167065],{"class":685},[679,167435,167065],{"class":685},[679,167437,167065],{"class":685},[679,167439,167126],{"class":880},[679,167441,167161],{"class":689},[5909,167443,167445],{"id":167444},"switching-to-log4j2","Switching to Log4J2",[651,167447,167448,167449,167451,167452,167455,167456,167459],{},"So if we aren’t vulnerable by default, what actions will make us open to this exploit and how do we fix it. Remember that by default the ",[676,167450,167023],{}," will bring our ",[676,167453,167454],{},"spring-boot-logging"," starter that configures Logback for us. If you want to use a different framework like Log4J2, you need to first exclude the logging starter. Next you can include the ",[676,167457,167458],{},"spring-boot-starter-log4j2"," which will configure Log4J2.",[669,167461,167463],{"className":5851,"code":167462,"language":5853,"meta":674,"style":674},"\u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter\u003C/artifactId>\n \u003Cexclusions>\n \u003Cexclusion>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-logging\u003C/artifactId>\n \u003C/exclusion>\n \u003C/exclusions>\n \u003C/dependency>\n \u003Cdependency>\n \u003CgroupId>org.springframework.boot\u003C/groupId>\n \u003CartifactId>spring-boot-starter-log4j2\u003C/artifactId>\n \u003C/dependency>\n\n\u003C/dependencies>\n",[676,167464,167465,167473,167481,167495,167508,167517,167527,167540,167552,167561,167570,167579,167587,167599,167611,167619,167623],{"__ignoreMap":674},[679,167466,167467,167469,167471],{"class":681,"line":682},[679,167468,4505],{"class":685},[679,167470,129682],{"class":693},[679,167472,4519],{"class":685},[679,167474,167475,167477,167479],{"class":681,"line":790},[679,167476,11738],{"class":685},[679,167478,119838],{"class":880},[679,167480,4519],{"class":693},[679,167482,167483,167485,167487,167489,167492],{"class":681,"line":892},[679,167484,46026],{"class":685},[679,167486,119847],{"class":880},[679,167488,5860],{"class":693},[679,167490,167491],{"class":689},"org.springframework.boot",[679,167493,167494],{"class":693},"\u003C/groupId>\n",[679,167496,167497,167499,167501,167503,167505],{"class":681,"line":901},[679,167498,46026],{"class":685},[679,167500,119861],{"class":880},[679,167502,5860],{"class":693},[679,167504,167023],{"class":689},[679,167506,167507],{"class":693},"\u003C/artifactId>\n",[679,167509,167510,167512,167515],{"class":681,"line":909},[679,167511,46026],{"class":685},[679,167513,167514],{"class":880},"exclusions",[679,167516,4519],{"class":693},[679,167518,167519,167522,167525],{"class":681,"line":918},[679,167520,167521],{"class":685}," \u003C",[679,167523,167524],{"class":880},"exclusion",[679,167526,4519],{"class":693},[679,167528,167529,167532,167534,167536,167538],{"class":681,"line":935},[679,167530,167531],{"class":685}," \u003C",[679,167533,119847],{"class":880},[679,167535,5860],{"class":693},[679,167537,167491],{"class":689},[679,167539,167494],{"class":693},[679,167541,167542,167544,167546,167548,167550],{"class":681,"line":944},[679,167543,167531],{"class":685},[679,167545,119861],{"class":880},[679,167547,5860],{"class":693},[679,167549,167167],{"class":689},[679,167551,167507],{"class":693},[679,167553,167554,167556,167559],{"class":681,"line":959},[679,167555,167521],{"class":685},[679,167557,167558],{"class":880},"/exclusion",[679,167560,4519],{"class":693},[679,167562,167563,167565,167568],{"class":681,"line":964},[679,167564,46026],{"class":685},[679,167566,167567],{"class":880},"/exclusions",[679,167569,4519],{"class":693},[679,167571,167572,167574,167577],{"class":681,"line":977},[679,167573,11738],{"class":685},[679,167575,167576],{"class":880},"/dependency",[679,167578,4519],{"class":693},[679,167580,167581,167583,167585],{"class":681,"line":982},[679,167582,11738],{"class":685},[679,167584,119838],{"class":880},[679,167586,4519],{"class":693},[679,167588,167589,167591,167593,167595,167597],{"class":681,"line":988},[679,167590,46026],{"class":685},[679,167592,119847],{"class":880},[679,167594,5860],{"class":693},[679,167596,167491],{"class":689},[679,167598,167494],{"class":693},[679,167600,167601,167603,167605,167607,167609],{"class":681,"line":993},[679,167602,46026],{"class":685},[679,167604,119861],{"class":880},[679,167606,5860],{"class":693},[679,167608,167458],{"class":689},[679,167610,167507],{"class":693},[679,167612,167613,167615,167617],{"class":681,"line":2129},[679,167614,11738],{"class":685},[679,167616,167576],{"class":880},[679,167618,4519],{"class":693},[679,167620,167621],{"class":681,"line":2140},[679,167622,889],{"emptyLinePlaceholder":797},[679,167624,167625,167627,167630],{"class":681,"line":2145},[679,167626,4505],{"class":685},[679,167628,167629],{"class":693},"/dependencies",[679,167631,4519],{"class":685},[651,167633,167634,167635,167638],{},"This will result in the ",[676,167636,167637],{},"log4j-core"," dependency which is now going to make us vulnerable to the exploit.",[669,167640,167642],{"className":5851,"code":167641,"language":5853,"meta":674,"style":674},"\u003Cdependency>\n \u003CgroupId>org.apache.logging.log4j\u003C/groupId>\n \u003CartifactId>log4j-core\u003C/artifactId>\n \u003Cversion>2.14.1\u003C/version>\n \u003Cscope>compile\u003C/scope>\n\u003C/dependency>\n",[676,167643,167644,167652,167665,167677,167691,167704],{"__ignoreMap":674},[679,167645,167646,167648,167650],{"class":681,"line":682},[679,167647,4505],{"class":685},[679,167649,119838],{"class":693},[679,167651,4519],{"class":685},[679,167653,167654,167656,167658,167660,167663],{"class":681,"line":790},[679,167655,11738],{"class":685},[679,167657,119847],{"class":880},[679,167659,5860],{"class":693},[679,167661,167662],{"class":689},"org.apache.logging.log4j",[679,167664,167494],{"class":693},[679,167666,167667,167669,167671,167673,167675],{"class":681,"line":892},[679,167668,11738],{"class":685},[679,167670,119861],{"class":880},[679,167672,5860],{"class":693},[679,167674,167637],{"class":689},[679,167676,167507],{"class":693},[679,167678,167679,167681,167683,167685,167688],{"class":681,"line":901},[679,167680,11738],{"class":685},[679,167682,107166],{"class":880},[679,167684,5860],{"class":693},[679,167686,167687],{"class":689},"2.14.1",[679,167689,167690],{"class":693},"\u003C/version>\n",[679,167692,167693,167695,167697,167699,167701],{"class":681,"line":909},[679,167694,11738],{"class":685},[679,167696,155358],{"class":880},[679,167698,5860],{"class":693},[679,167700,7134],{"class":689},[679,167702,167703],{"class":693},"\u003C/scope>\n",[679,167705,167706,167708,167710],{"class":681,"line":918},[679,167707,4505],{"class":685},[679,167709,167576],{"class":693},[679,167711,4519],{"class":685},[651,167713,167714,167715,167718,167719,167722,167723,167725],{},"This doesn’t mean that you can’t use Log4J2, it just means you can’t use that specific version. Anything \u003C ",[676,167716,167717],{},"2.15.0"," is vulnerable and at the time of writing this newsletter the current version is ",[676,167720,167721],{},"2.17.0",". All you need to do to bump the version is to add a new property to the properties section in your ",[676,167724,81517],{}," that defines the version of Log4j2 that you want to use.",[669,167727,167729],{"className":5851,"code":167728,"language":5853,"meta":674,"style":674},"\u003Cproperties>\n \u003Cjava.version>17\u003C/java.version>\n \u003Clog4j2.version>2.17.0\u003C/log4j2.version>\n\u003C/properties>\n",[676,167730,167731,167739,167753,167767],{"__ignoreMap":674},[679,167732,167733,167735,167737],{"class":681,"line":682},[679,167734,4505],{"class":685},[679,167736,35538],{"class":693},[679,167738,4519],{"class":685},[679,167740,167741,167743,167746,167748,167750],{"class":681,"line":790},[679,167742,4524],{"class":685},[679,167744,167745],{"class":880},"java.version",[679,167747,5860],{"class":693},[679,167749,51609],{"class":689},[679,167751,167752],{"class":693},"\u003C/java.version>\n",[679,167754,167755,167757,167760,167762,167764],{"class":681,"line":892},[679,167756,4524],{"class":685},[679,167758,167759],{"class":880},"log4j2.version",[679,167761,5860],{"class":693},[679,167763,167721],{"class":689},[679,167765,167766],{"class":693},"\u003C/log4j2.version>\n",[679,167768,167769,167771,167774],{"class":681,"line":901},[679,167770,4505],{"class":685},[679,167772,167773],{"class":693},"/properties",[679,167775,4519],{"class":685},[5909,167777,167779],{"id":167778},"spring-boot-log4j2-vulnerability-video","Spring Boot Log4J2 Vulnerability Video",[651,167781,167782],{},"Everything that we just talked about here is in the video below.",[651,167784,167785],{},[676,167786,167787],{},"youtube:https://www.youtube.com/watch?v=nGqVYiwu8uo",[651,167789,167790],{},"As a small YouTuber you need to try and jump on trends as they are happening. I know these are numbers are all relative but for me it was best performing video of all time in the first 1,3, and 5 days. At the time of this writing I have had over 2,000 views in the first 5 videos. If you were one of those 2,000 viewers, thank you!",[651,167792,167793],{},[660,167794],{"alt":167795,"src":167796},"log4j2_video_stats.png","/images/newsletter/2021/12/13/log4j2_video_stats.png",[4542,167798,157574],{"id":157573},[5909,167800,164959],{"id":69848},[5316,167802,167803,167810,167817],{},[5332,167804,167805],{},[812,167806,167809],{"href":167807,"rel":167808},"https://joshlong.com/jl/blogPost/kubernetes-java-client-and-spring-native-and-graalvm.html",[816],"The Kubernetes Java Client and GraalVM support with Spring Native 0.11.x",[5332,167811,167812],{},[812,167813,167816],{"href":167814,"rel":167815},"https://go.dev/blog/go1.18beta1",[816],"Go 1.18 Beta 1 is available, with generics",[5332,167818,167819],{},[812,167820,167823],{"href":167821,"rel":167822},"https://spring.io/blog/2021/12/17/client-side-development-with-spring-boot-applications",[816],"Client Side Development with Spring Boot Applications",[5909,167825,164971],{"id":157591},[5316,167827,167828,167835,167842],{},[5332,167829,167830],{},[812,167831,167834],{"href":167832,"rel":167833},"https://www.youtube.com/watch?v=7MKVCmNKT1c",[816],"Introducing AWS Amplify Studio",[5332,167836,167837],{},[812,167838,167841],{"href":167839,"rel":167840},"https://www.youtube.com/watch?v=GEYkwfYytAM",[816],"Effortless typography, even in dark mode — Tailwind CSS Typography v0.5",[5332,167843,167844],{},[812,167845,167848],{"href":167846,"rel":167847},"https://www.youtube.com/watch?v=khh6Xo8YNvY",[816],"Podcasting coming to YouTube…",[5909,167850,164983],{"id":39439},[5316,167852,167853],{},[5332,167854,167855],{},[812,167856,167859],{"href":167857,"rel":167858},"https://workingcode.dev/episodes/053-product-management-with-adam-lehman/",[816],"Working Podcast: Episode 053: Product Management with Adam Lehman",[5909,167861,167862],{"id":160019},"📰 Newsletters",[5316,167864,167865],{},[5332,167866,167867],{},[812,167868,167871],{"href":167869,"rel":167870},"https://jamesclear.com/3-2-1",[816],"James Clear",[5909,167873,167875],{"id":167874},"jobs","💻 Jobs",[5316,167877,167878],{},[5332,167879,167880,167885],{},[812,167881,167884],{"href":167882,"rel":167883},"https://axoniq.io/job-overview/frontend-developer-cloud-team",[816],"Frontend developer - Cloud Team (Remote)"," ****",[5909,167887,165017],{"id":165016},[5909,167889,165430],{"id":165429},[651,167891,167892],{},[812,167893,167894],{"href":167894,"rel":167895},"https://twitter.com/NotionHQ/status/1470824658941341696",[816],[4542,167897,167899],{"id":167898},"until-next-year","Until Next Year",[651,167901,167902],{},"Thank you for taking the time to let me ramble. I hope you enjoyed this and all of the newsletters that preceded it this year. I want to take this opportunity to wish you and your family a very happy, healthy holidays and New Year. As always friends ...",[651,167904,41105,167905,69920,167907,167909,167911,167913],{},[41107,167906],{},[41107,167908],{},[812,167910,161560],{"href":161111},[41107,167912],{},[812,167914,53869],{"href":82688,"rel":167915},[816],[786,167917,167918],{},"html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sibLe, html code.shiki .sibLe{--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#D73A49}html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .suaqH, html code.shiki .suaqH{--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#22863A}",{"title":674,"searchDepth":790,"depth":790,"links":167920},[167921,167925,167934],{"id":167009,"depth":790,"text":167010,"children":167922},[167923,167924],{"id":167444,"depth":892,"text":167445},{"id":167778,"depth":892,"text":167779},{"id":157573,"depth":790,"text":157574,"children":167926},[167927,167928,167929,167930,167931,167932,167933],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":160019,"depth":892,"text":167862},{"id":167874,"depth":892,"text":167875},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":167898,"depth":790,"text":167899},{"slug":167936,"date":166994},"log4j","/newsletter/2021/12/20/log4j",{"title":167001,"description":167006},"newsletter/2021/12/20/log4j","SjH0jaGcsqD4SiCY60ErzOGGXGl6bqsJ-0iVorj1cWg",{"id":167942,"title":167943,"body":167944,"description":167948,"extension":793,"meta":168229,"navigation":797,"path":168231,"seo":168232,"stem":168233,"__hash__":168234},"content/newsletter/2022/01/03/happy-new-year-2022.md","Happy New Year!",{"type":648,"value":167945,"toc":168211},[167946,167949,167953,167958,167966,167969,167982,167984,167988,167990,167992,167995,168001,168005,168010,168018,168020,168032,168035,168040,168045,168047,168049,168086,168088,168103,168105,168142,168144,168153,168155,168167,168169,168178,168180,168183,168185,168191,168193,168198],[651,167947,167948],{},"Happy New Year friends. Welcome to the first installment of this newsletter in 2022. I hope this finds you and your family healthy and happy. In this edition of the newsletter, I want to talk about New Year's goals and plans to move my website over to Nuxt 3.",[4542,167950,167952],{"id":167951},"new-year-new-me-️","New Year, New Me 🤷♂️",[651,167954,167955],{},[660,167956],{"alt":167943,"src":167957},"https://www.danvega.dev/assets/static/kelly-sikkema-PXl_S152jNM-unsplash.09b17d9.dfdc5c082afa3b8533626a6fa862087f.jpg",[651,167959,167960,167961,167965],{},"I wrote up my annual ",[812,167962,167964],{"href":97275,"rel":167963},[816],"New Years’ post"," in which I looked back at some of my highlights from 2021 and looked ahead to 2022. From a professional standpoint, my focus this year is on producing more quality content.",[651,167967,167968],{},"I took a look at my numbers from last year and they were a little embarrassing. I am giving myself a bit of a break because we are still in the middle of a pandemic and mentally it’s just been an exhausting couple of years. The goals I am setting when it comes to content are this:",[5316,167970,167971,167973,167975,167977,167980],{},[5332,167972,82737],{},[5332,167974,82740],{},[5332,167976,82743],{},[5332,167978,167979],{},"Live Training 👀 (More on this soon)",[5332,167981,82734],{},[5909,167983,15432],{"id":61224},[651,167985,82746,167986,82750],{},[2939,167987,82749],{},[651,167989,82753],{},[4542,167991,21973],{"id":21972},[651,167993,167994],{},"I love creating courses but I haven’t been able to in a while. I have been struggling with what to create and where. The one thing I know for sure is that I want to create smaller courses that are focused on a particular topic which means no more 12-15 hour courses from me. I could create them on my own platform and charge more or host them on something like Udemy and sell them for less. I have also thrown around the idea of creating a platform and inviting some other creators to build on it. Whatever I end up doing I hope to publish at least 2 courses in 2022 and I can’t wait to share those with you. Until then I am extremely blessed to have 138,000 students around the world taking my courses. Thank You 🙏",[651,167996,167997],{},[660,167998],{"alt":167999,"src":168000},"Udemy Stats","/images/newsletter/2022/01/03/udemy-stats.png",[4542,168002,168004],{"id":168003},"moving-my-website-to-nuxt-3","Moving my website to Nuxt 3",[651,168006,168007],{},[660,168008],{"alt":120265,"src":168009},"/images/newsletter/2022/01/03/danvega-dev-homepage.png",[651,168011,82685,168012,82691,168015,82695],{},[812,168013,82690],{"href":82688,"rel":168014},[816],[812,168016,43848],{"href":43854,"rel":168017},[816],[651,168019,82698],{},[5316,168021,168022,168024,168026,168028,168030],{},[5332,168023,82703],{},[5332,168025,74145],{},[5332,168027,82708],{},[5332,168029,82711],{},[5332,168031,82714],{},[651,168033,168034],{},"Nuxt 3 is still in beta and while I love what they are doing there are still some things missing that are going to prevent me from moving over right now. The biggest one of all is static site generation which is coming soon 🤞🏻 Lucky for me I am in no rush to move over which gives me a great opportunity to spend some time learning Nuxt 3. I put together a couple of videos recently on getting started with Nuxt 3 and Nuxt 3 + Tailwind CSS 3.",[651,168036,168037],{},[676,168038,168039],{},"youtube:https://www.youtube.com/watch?v=tdOoKKXlDCQ",[651,168041,168042],{},[676,168043,168044],{},"youtube:https://www.youtube.com/watch?v=0oE2r51HyF0",[4542,168046,157574],{"id":157573},[5909,168048,164959],{"id":69848},[5316,168050,168051,168058,168065,168072,168079],{},[5332,168052,168053],{},[812,168054,168057],{"href":168055,"rel":168056},"https://about.instagram.com/blog/announcements/introducing-better-previews-of-your-content-outside-of-instagram/",[816],"Instagram Previews on Twitter",[5332,168059,168060],{},[812,168061,168064],{"href":168062,"rel":168063},"https://siliconangle.com/2021/12/17/startups-taking-advantage-aws-whole-new-level-reinvent/",[816],"Startups are taking advantage of AWS at a whole new level",[5332,168066,168067],{},[812,168068,168071],{"href":168069,"rel":168070},"https://blog.jetbrains.com/kotlin/2021/12/compose-multiplatform-1-0-is-going-live/",[816],"Compose Multiplatform 1.0 is going live!",[5332,168073,168074],{},[812,168075,168078],{"href":168076,"rel":168077},"https://www.morling.dev/blog/announcing-first-release-of-kcctl/",[816],"Announcing the First Release of kcctl",[5332,168080,168081],{},[812,168082,168085],{"href":168083,"rel":168084},"https://rakyll.org/generics-facilititators/",[816],"Generics facilitators in Go",[5909,168087,164971],{"id":157591},[5316,168089,168090,168097],{},[5332,168091,168092],{},[812,168093,168096],{"href":168094,"rel":168095},"https://www.youtube.com/c/GopherAcademy/videos",[816],"Gopher Con 2021 Session Videos",[5332,168098,168099],{},[812,168100,168101],{"href":168101,"rel":168102},"https://twitter.com/BillyKorando/status/1472959983612358657",[816],[5909,168104,164995],{"id":133422},[5316,168106,168107,168114,168121,168128,168135],{},[5332,168108,168109],{},[812,168110,168113],{"href":168111,"rel":168112},"https://spring.io/blog/2021/12/22/spring-native-0-11-1-available-now",[816],"Spring Native 0.11.1 available now",[5332,168115,168116],{},[812,168117,168120],{"href":168118,"rel":168119},"https://spring.io/blog/2021/12/21/spring-boot-2-6-2-available-now",[816],"Spring Boot 2.6.2 is available now",[5332,168122,168123],{},[812,168124,168127],{"href":168125,"rel":168126},"https://www.jetbrains.com/lp/compose-mpp/",[816],"Compose Multiplatform",[5332,168129,168130],{},[812,168131,168134],{"href":168132,"rel":168133},"https://picocss.com/",[816],"Pico.css Minimal CSS Framework for semantic HTML",[5332,168136,168137],{},[812,168138,168141],{"href":168139,"rel":168140},"https://github.com/GroovyLanguageServer/groovy-language-server",[816],"Groovy Language Server",[5909,168143,165005],{"id":39340},[5316,168145,168146],{},[5332,168147,168148],{},[812,168149,168152],{"href":168150,"rel":168151},"https://www.patterns.dev/",[816],"Patterns.dev",[5909,168154,166601],{"id":21972},[5316,168156,168157],{},[5332,168158,168159],{},[812,168160,168163,168164],{"href":168161,"rel":168162},"https://dev.to/techworld_with_nana/golang-tutorial-for-beginners-free-course-330",[816],"Golang Tutorial for Beginners ",[679,168165,168166],{},"Full Course",[5909,168168,167862],{"id":160019},[5316,168170,168171],{},[5332,168172,168173],{},[812,168174,168177],{"href":168175,"rel":168176},"https://tanzu.vmware.com/content/josh-blog/this-month-in-spring-december-2021",[816],"This month in Spring - December 2021",[5909,168179,165017],{"id":165016},[651,168181,168182],{},"“Perfectionism is the mother of procrastination.” - Michael Hyatt",[5909,168184,165430],{"id":165429},[651,168186,168187],{},[812,168188,168189],{"href":168189,"rel":168190},"https://twitter.com/chrismunns/status/1472960906498035712",[816],[4542,168192,157704],{"id":157703},[651,168194,166267,168195,166271],{},[812,168196,41499],{"href":44086,"rel":168197},[816],[651,168199,41105,168200,69920,168202,168204,168206,168208],{},[41107,168201],{},[41107,168203],{},[812,168205,161560],{"href":161111},[41107,168207],{},[812,168209,53869],{"href":82688,"rel":168210},[816],{"title":674,"searchDepth":790,"depth":790,"links":168212},[168213,168216,168217,168218,168228],{"id":167951,"depth":790,"text":167952,"children":168214},[168215],{"id":61224,"depth":892,"text":15432},{"id":21972,"depth":790,"text":21973},{"id":168003,"depth":790,"text":168004},{"id":157573,"depth":790,"text":157574,"children":168219},[168220,168221,168222,168223,168224,168225,168226,168227],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":21972,"depth":892,"text":166601},{"id":160019,"depth":892,"text":167862},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":82830,"date":168230},"2022-01-03T08:30:00.000Z","/newsletter/2022/01/03/happy-new-year-2022",{"title":167943,"description":167948},"newsletter/2022/01/03/happy-new-year-2022","RI5Ax7S9mqUkljLZBYiXUOQklzslWvM14oS5u5NRGUU",{"id":168236,"title":168237,"body":168238,"description":157776,"extension":793,"meta":168551,"navigation":797,"path":168552,"seo":168553,"stem":168554,"__hash__":168555},"content/newsletter/2022/01/24/im-joining-vmware.md","I'm Joining VMware",{"type":648,"value":168239,"toc":168533},[168240,168246,168249,168251,168260,168265,168269,168272,168275,168280,168284,168291,168296,168302,168306,168313,168343,168345,168347,168370,168372,168395,168397,168420,168422,168445,168447,168456,168458,168467,168469,168478,168481,168502,168504,168507,168513,168515,168520],[651,168241,168242],{},[812,168243,157776],{"href":168244,"rel":168245},"https://www.danvega.dev/newsletter/im-joining-vmware",[816],[651,168247,168248],{},"I apologize for missing the last couple of weeks but I have had a lot going on in my life. As you probably already saw from the title I am joining VMware so we will start there and I will catch you up on what I have been up to the past few weeks.",[4542,168250,83081],{"id":83080},[651,168252,168253,168254,168259],{},"I’m excited to announce that I have joined VMware as a Spring Developer Advocate. This is literally a dream for me on so many levels. First I get to join a world-class organization that continually gets ranked as one of the best places to work. I get to join a team made of so many great developers and teachers and I can’t wait to learn from them. Finally, I get to talk about Java & Spring every single day and create content around languages, frameworks, projects, and tools that I genuinely enjoy using. If you want to read more about my decision you can ",[812,168255,168258],{"href":168256,"rel":168257},"https://www.danvega.dev/blog/2022/01/24/im-joining-v-mware/",[816],"read a blog post here"," where I detailed everything.",[651,168261,168262],{},[660,168263],{"alt":83109,"src":168264},"/images/newsletter/2022/01/24/vmware.png",[4542,168266,168268],{"id":168267},"live-streaming-with-greg-turnquist","Live Streaming with Greg Turnquist",[651,168270,168271],{},"I forgot to mention this in my last newsletter but Greg and I did a live stream on New Year's Eve. If you’re not familiar with Greg, he works on the Spring Team (and now he’s a coworker 🥳) and is an author of some amazing Spring books.",[651,168273,168274],{},"In this stream, we talked about some of our highlights from the year as well as our predictions for 2022. We also tried to answer the question of what makes a “pro” developer. There is a lot to unpack here and certainly not enough time to cover everything. I hope I get a chance to come back and answer some of the questions fully for my blog or YouTube channel in the near future.",[651,168276,168277],{},[676,168278,168279],{},"youtube:https://www.youtube.com/watch?v=Un5wpBy4aJA",[4542,168281,168283],{"id":168282},"nuxt-3-nitro-server-engine","Nuxt 3 Nitro Server Engine",[651,168285,168286,168287,168290],{},"I have spent the last few weeks learning about Nuxt 3 (which is currently in beta) and taking that opportunity to take what I have learned and create some short tutorials around. In this tutorial, I take a look at Nuxt Nitro, the new server engine in Nuxt 3. With Nitro, you can create API routes by simply adding a ",[676,168288,168289],{},"server/api"," folder and creating a JavaScript (or TypeScript) file that exports a default function. Check out the video below if you want to start creating full-stack applications in Nuxt 3.",[651,168292,168293],{},[676,168294,168295],{},"youtube:https://www.youtube.com/watch?v=FeKleFJkKNY",[651,168297,168298,168299,664],{},"If you enjoyed this you might like what I will be releasing this week. I created a tutorial on how to use Nuxt 3 + Notion API to retrieve and update a database stored in Notion. If that sounds fun make sure you subscribed to my ",[812,168300,101297],{"href":165299,"rel":168301},[816],[4542,168303,168305],{"id":168304},"java-champions-conference","Java Champions Conference",[651,168307,40060,168308,168312],{},[812,168309,168305],{"href":168310,"rel":168311},"https://jchampionsconf.com/",[816]," started last week and will continue Monday and Tuesday this week. Each of the presentations is given by a Java Champion which means there are a lot of really smart people talking. Here are a few talks I enjoyed from Thursday & Friday",[5316,168314,168315,168322,168329,168336],{},[5332,168316,168317],{},[812,168318,168321],{"href":168319,"rel":168320},"https://www.youtube.com/watch?v=CxrwIphxsgM",[816],"Getting Started with Serverless Java",[5332,168323,168324],{},[812,168325,168328],{"href":168326,"rel":168327},"https://www.youtube.com/watch?v=3ENintpjAIY",[816],"Functional Programming in Java, Kotlin, and Groovy",[5332,168330,168331],{},[812,168332,168335],{"href":168333,"rel":168334},"https://www.youtube.com/watch?v=cK19rE2V9UY",[816],"The Secrets of the Fastest Java Developers on Earth",[5332,168337,168338],{},[812,168339,168342],{"href":168340,"rel":168341},"https://www.youtube.com/watch?v=6qOsBys7jhQ",[816],"5 tips on creating modern, cloud-native applications",[4542,168344,157574],{"id":157573},[5909,168346,164959],{"id":69848},[5316,168348,168349,168356,168363],{},[5332,168350,168351],{},[812,168352,168355],{"href":168353,"rel":168354},"https://www.entrepreneur.com/article/403948",[816],"Web3 is the Future of the Creator Economy",[5332,168357,168358],{},[812,168359,168362],{"href":168360,"rel":168361},"https://www.raycast.com/blog/no-code-reviews-by-default/",[816],"No code reviews by default",[5332,168364,168365],{},[812,168366,168369],{"href":168367,"rel":168368},"https://blog.jetbrains.com/idea/2022/01/java-annotated-monthly-january-2022/",[816],"Java Annotated Monthly – January 2022",[5909,168371,164971],{"id":157591},[5316,168373,168374,168381,168388],{},[5332,168375,168376],{},[812,168377,168380],{"href":168378,"rel":168379},"https://www.twitch.tv/videos/1268630354",[816],"Tanzu TV - Code with Josh and Deshaun",[5332,168382,168383],{},[812,168384,168387],{"href":168385,"rel":168386},"https://www.youtube.com/watch?v=SDobPYHk_sQ",[816],"Introduction to VMware Tanzu Application Platform (TAP)",[5332,168389,168390],{},[812,168391,168394],{"href":168392,"rel":168393},"https://www.youtube.com/watch?v=0JY8O1LBA7U",[816],"Vue.js Advanced Data Provider Component Patterns Explained",[5909,168396,164983],{"id":39439},[5316,168398,168399,168406,168413],{},[5332,168400,168401],{},[812,168402,168405],{"href":168403,"rel":168404},"https://airhacks.fm/#episode_171",[816],"airhacks.fm - Java, Jakarta EE and MicroProfile on Azure",[5332,168407,168408],{},[812,168409,168412],{"href":168410,"rel":168411},"https://tanzu.vmware.com/developer/tv/bcak/50/",[816],"Between Chair and Keyboard with Glenn Vanderburg",[5332,168414,168415],{},[812,168416,168419],{"href":168417,"rel":168418},"https://tanzu.vmware.com/developer/tv/bcak/51/",[816],"Between Chair and Keyboard with Heidi Waterhouse",[5909,168421,164995],{"id":133422},[5316,168423,168424,168431,168438],{},[5332,168425,168426],{},[812,168427,168430],{"href":168428,"rel":168429},"https://github.com/marketplace/actions/github-action-for-graalvm",[816],"GitHub Action for GraalVM",[5332,168432,168433],{},[812,168434,168437],{"href":168435,"rel":168436},"https://examples.deno.land/",[816],"Deno by Example",[5332,168439,168440],{},[812,168441,168444],{"href":168442,"rel":168443},"https://staging.vuejs.org/",[816],"Vue 3 docs preview",[5909,168446,165005],{"id":39340},[5316,168448,168449],{},[5332,168450,168451],{},[812,168452,168455],{"href":168453,"rel":168454},"https://amzn.to/3GYRryW",[816],"YouTube Formula",[5909,168457,165017],{"id":165016},[1004,168459,168460],{},[651,168461,168462,168463,168466],{},"“If I had an hour to solve a problem ",[2939,168464,168465],{},"I'd spend 55 minutes thinking about the problem and five minutes thinking about solutions",".” - Albert Einstein",[5909,168468,166601],{"id":21972},[5316,168470,168471],{},[5332,168472,168473],{},[812,168474,168477],{"href":168475,"rel":168476},"https://frontendmasters.com/courses/getting-started-css/",[816],"Getting Started with CSS",[5909,168479,168480],{"id":79608},"🎓 Conferences",[5316,168482,168483,168490,168495],{},[5332,168484,168485],{},[812,168486,168489],{"href":168487,"rel":168488},"https://cfe.dev/events/the-jam-2022/",[816],"TheJam.dev 2022",[5332,168491,168492],{},[812,168493,168305],{"href":168310,"rel":168494},[816],[5332,168496,168497],{},[812,168498,168501],{"href":168499,"rel":168500},"https://vuejsnation.com/",[816],"VueJS Nation Conference",[5909,168503,165430],{"id":165429},[651,168505,168506],{},"Evan You create a version of Wordle using Vue",[651,168508,168509],{},[812,168510,168511],{"href":168511,"rel":168512},"https://twitter.com/youyuxi/status/1485202083225616385",[816],[4542,168514,157704],{"id":157703},[651,168516,166267,168517,166271],{},[812,168518,41499],{"href":44086,"rel":168519},[816],[651,168521,41105,168522,69920,168524,168526,168528,168530],{},[41107,168523],{},[41107,168525],{},[812,168527,161560],{"href":161111},[41107,168529],{},[812,168531,53869],{"href":82688,"rel":168532},[816],{"title":674,"searchDepth":790,"depth":790,"links":168534},[168535,168536,168537,168538,168539,168550],{"id":83080,"depth":790,"text":83081},{"id":168267,"depth":790,"text":168268},{"id":168282,"depth":790,"text":168283},{"id":168304,"depth":790,"text":168305},{"id":157573,"depth":790,"text":157574,"children":168540},[168541,168542,168543,168544,168545,168546,168547,168548,168549],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":21972,"depth":892,"text":166601},{"id":79608,"depth":892,"text":168480},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":83235,"date":83236},"/newsletter/2022/01/24/im-joining-vmware",{"title":168237,"description":157776},"newsletter/2022/01/24/im-joining-vmware","T2WxI6Dy5-mlQIdIWhu3UCiWnurYbahPgV1vO6ok8Zs",{"id":168557,"title":168558,"body":168559,"description":168563,"extension":793,"meta":168757,"navigation":797,"path":168760,"seo":168761,"stem":168762,"__hash__":168763},"content/newsletter/2022/01/31/my-first-week-vmware.md","My First Week at VMware",{"type":648,"value":168560,"toc":168749},[168561,168564,168568,168581,168584,168587,168602,168611,168618,168621,168625,168628,168637,168640,168643,168646,168650,168653,168658,168663,168666,168670,168677,168683,168687,168690,168720,168729,168731,168736],[651,168562,168563],{},"I hope you all had a great weekend and are ready to go on this Monday morning. In this edition of the newsletter, I want to talk about my first week at VMware, a demo I put together of a Nuxt 3 application talking to the Notion API, The Kubernetes documentary, The JChampions conference, and some Spring Goodies from last week.",[4542,168565,168567],{"id":168566},"my-first-week-at-vmware","My first week at VMware",[651,168569,168570,168571,168574,168575,168580],{},"If you missed last week's newsletter you missed a pretty big announcement. I joined VMware as a Spring Developer advocate and wrote all about it ",[812,168572,18263],{"href":168256,"rel":168573},[816],". I put together a ",[812,168576,168579],{"href":168577,"rel":168578},"https://youtu.be/TYXdX-EakG8",[816],"video version"," of that blog post to share with my YouTube audience and it gave me a chance to expand on some of my thoughts.",[651,168582,168583],{},"My first week went about as well as I could have hoped. For as large a company as VMware is our onboarding process is really great. I was able to learn more about the company and our processes as well as get my laptop set up with everything I need to get going. The benefits are amazing and a reminder to everyone that you should always take a look at what benefits a company is offering as part of your overall compensation package. The salary is great the place to start but it isn’t everything.",[651,168585,168586],{},"I have also started building out my schedule for the year. I don’t know what I can say publicly yet but like everyone else, we are hoping we can return to some in-person events this year. I started working on what events I would be at as well as submitting to a number of conferences.",[651,168588,168589,168590,168595,168596,168601],{},"I got invited to do a live stream on the ",[812,168591,168594],{"href":168592,"rel":168593},"https://www.youtube.com/channel/UC4ogdcPcIAOOMJktgBMhQnQ",[816],"IntelliJ IDEA YouTube channel"," on February 16th. I will give some more details as we get closer but this is going to be a beginner-focused live coding session where we will take a look at building REST APIs. A huge thank you to Mala Gupta for inviting me and I can’t wait for my 2nd time chatting with her. In case you missed it ",[812,168597,168600],{"href":168598,"rel":168599},"https://www.youtube.com/watch?v=rOUAdUOZk9E",[816],"she interviewed me"," for IntelliJ’s 20th anniversary.",[651,168603,168604,168605,168610],{},"I also want to let you know that I will be a host on Tanzu Tuesday this week. In ",[812,168606,168609],{"href":168607,"rel":168608},"https://tanzu.vmware.com/developer/tv/tanzu-tuesdays/0083/",[816],"this week's episode",", the great Sam Brannen will talk with us about testing with Spring and JUnit 5. This session will be hands-on with live coding to give you an overview of the latest and greatest in the world of testing using JUnit Jupiter (a.k.a. JUnit 5) and the Spring Framework.",[651,168612,168613,168614,168617],{},"I’m going to do a lot more live streaming so stay tuned to this newsletter or follow me on ",[812,168615,51474],{"href":51472,"rel":168616},[816]," to find out when I’m going live. If you would like me to speak at your user group, conference, podcast, or join you on a live coding session please feel free to reach out to me.",[651,168619,168620],{},"I can’t even begin to tell you how excited I am to be a VMware. It was a great first week and look forward to many more.",[4542,168622,168624],{"id":168623},"nuxt-3-notion-api","Nuxt 3 + Notion API",[651,168626,168627],{},"If you pay attention to anything I’m doing you probably know that I am a huge fan of Notion. I use it as a single source of truth for running my life. I schedule out all of my newsletters, YouTube videos, blog posts, courses and so much more.",[651,168629,168630,168631,168636],{},"If you have been following my ",[812,168632,168635],{"href":168633,"rel":168634},"https://www.youtube.com/playlist?list=PLZV0a2jwt22tlxgTQ3ZB2KpRtDTl2K0IP",[816],"YouTube series"," on Nuxt 3 you know I’m planning on moving my personal website over to the framework this year. Something that I have wanted for a while is a way for you to vote on the content you would like to see me produce next. A lot of the time I am just creating content around the work I’m doing or things that I’m interested in. I would like to put some ideas out there and let you vote on what I should work on next.",[651,168638,168639],{},"For that to work through all of the ideas will be created and updated inside of Notion. Lucky for us Notion has an API and a way for us to work with that data. I created a tutorial on how you could create a full-stack application with Nuxt 3 that talks to the Notion API.",[5988,168641],{"id":168642},"JvNhGu5ELgs",[651,168644,168645],{},"If you’re a Spring Boot fan, I have some content coming around using the Notion API in your Spring applications coming soon.",[4542,168647,168649],{"id":168648},"kubernetes-documentary","Kubernetes Documentary",[651,168651,168652],{},"The Kubernetes documentary created by Honeypot was released this week. They do such an amazing job with these documentaries and I have been looking forward to this one for a long time. It was fascinating to see how Kubernetes started and a behind-the-scenes look at the container wars. If you haven’t seen this one it is a must-watch 🤩",[651,168654,168655],{},[676,168656,168657],{},"youtube:https://www.youtube.com/watch?v=BE77h7dmoQU&t=2s",[651,168659,168660],{},[676,168661,168662],{},"youtube:https://www.youtube.com/watch?v=318elIq37PE",[651,168664,168665],{},"What was even crazier is that after watching this I was in a meeting with one of the creators of Kubernetes at VMware. If you didn’t know 2 of the creators work at VMware. Did I tell you how much I love working for this company!",[4542,168667,168669],{"id":168668},"jchampions-conference","JChampions Conference",[651,168671,168672,168673,664],{},"The 2nd Annual JChampions Conference wrapped up last week and wow, what a conference it was. You will be hard-pressed to find a collection of Java developers like this one at any conference. If you want to see the recordings for all of the talks you can watch them on their ",[812,168674,101297],{"href":168675,"rel":168676},"https://www.youtube.com/channel/UChJ6IHM_uy6dWLBiDAwYkpw/videos",[816],[651,168678,168679],{},[812,168680,168681],{"href":168681,"rel":168682},"https://twitter.com/JChampionsConf/status/1486147426587226125",[816],[4542,168684,168686],{"id":168685},"spring-developer-advocate-goodies","Spring Developer Advocate Goodies",[651,168688,168689],{},"Now that I get to focus on Spring and the community I should have more goodies to share with you every week. This week I have some really great recordings from a few of my teammates:",[5316,168691,168692,168699,168706,168713],{},[5332,168693,168694],{},[812,168695,168698],{"href":168696,"rel":168697},"https://www.youtube.com/watch?v=U-4RQ7LXZUk",[816],"Josh Long - Kubernetes Native Java",[5332,168700,168701],{},[812,168702,168705],{"href":168703,"rel":168704},"https://tanzu.vmware.com/developer/tv/tanzu-tuesdays/0082/",[816],"Cora Iberkleid - Building Production Ready Container Images at Scale",[5332,168707,168708],{},[812,168709,168712],{"href":168710,"rel":168711},"https://www.youtube.com/watch?v=694soIproYE",[816],"Code: Cartographer with Josh Long and Cora Iberkleid",[5332,168714,168715],{},[812,168716,168719],{"href":168717,"rel":168718},"https://tanzu.vmware.com/developer/tv/bcak/52/",[816],"Between Chair and Keyboard: The one with Spencer Gibb",[651,168721,168722,168723,168728],{},"If you haven’t had a chance to visit the ",[812,168724,168727],{"href":168725,"rel":168726},"https://tanzu.vmware.com/developer/",[816],"VMware Tanzu Developer Center"," I suggest you check it out. It’s packed full of free content.",[4542,168730,157704],{"id":157703},[651,168732,166267,168733,166271],{},[812,168734,41499],{"href":44086,"rel":168735},[816],[651,168737,41105,168738,69920,168740,168742,168744,168746],{},[41107,168739],{},[41107,168741],{},[812,168743,161560],{"href":161111},[41107,168745],{},[812,168747,53869],{"href":82688,"rel":168748},[816],{"title":674,"searchDepth":790,"depth":790,"links":168750},[168751,168752,168753,168754,168755,168756],{"id":168566,"depth":790,"text":168567},{"id":168623,"depth":790,"text":168624},{"id":168648,"depth":790,"text":168649},{"id":168668,"depth":790,"text":168669},{"id":168685,"depth":790,"text":168686},{"id":157703,"depth":790,"text":157704},{"slug":168758,"date":168759},"my-first-week-vmware","2022-01-31T08:00:00.000Z","/newsletter/2022/01/31/my-first-week-vmware",{"title":168558,"description":168563},"newsletter/2022/01/31/my-first-week-vmware","TRsIENoOyBboo4l_GQ8FNtW9rJBKrgNEHV5O2P4Dv88",{"id":168765,"title":168766,"body":168767,"description":168771,"extension":793,"meta":169367,"navigation":797,"path":169370,"seo":169371,"stem":169372,"__hash__":169373},"content/newsletter/2022/02/07/testing-with-spring.md","Testing with Spring, Netlify Builds, Groovy 4 and Spring Boot Magic",{"type":648,"value":168768,"toc":169350},[168769,168772,168775,168779,168802,168805,168808,168811,168816,168819,168823,168830,168833,168844,168847,169133,169136,169145,169148,169153,169156,169159,169163,169177,169185,169192,169198,169202,169205,169208,169211,169214,169218,169229,169231,169233,169249,169251,169267,169269,169285,169287,169296,169298,169307,169310,169319,169321,169327,169329,169334,169347],[651,168770,168771],{},"Good morning, Happy Monday, and welcome to another edition of the newsletter friends. If you’re anywhere near me (Cleveland, OH) I hope you’re staying warm as we experienced quite the snowstorm last week. I’m encouraged that we have crossed over into February and that the worst of winter is almost behind us.",[651,168773,168774],{},"The one day I have marked on my calendar every year is the day we move the clocks forward. I’m encouraged that it's staying light out longer but on that glorious day when we move the clocks forward, I know Spring and Summer are right around the corner. If you take part in daylight savings time that day is March 13th this year, so put it on your calendars! With that let’s take a look at what happened last week.",[4542,168776,168778],{"id":168777},"testing-with-spring-and-junit-5","Testing with Spring and JUnit 5",[651,168780,168781,168782,168786,168787,168792,168793,23212,168797,168801],{},"I had the privilege of co-hosting ",[812,168783,168785],{"href":168607,"rel":168784},[816],"Tanzu Tuesdays"," where our guest was the great ",[812,168788,168791],{"href":168789,"rel":168790},"https://twitter.com/sam_brannen",[816],"Sam Brannen",". Sam was kind enough to present to us on Testing with ",[812,168794,23816],{"href":168795,"rel":168796},"https://spring.io/projects/spring-boot",[816],[812,168798,18276],{"href":168799,"rel":168800},"https://junit.org/junit5/docs/current/user-guide",[816],". It was a great talk and I will leave a link to the talk below.",[5988,168803],{"id":168804},"krA-A2HqWFQ",[651,168806,168807],{},"I write tests, probably not enough of them but I do write them. I write tests in Spring and JUnit but what is becoming apparently clear to me is that I am not caught up on all the cool things that JUnit 5 has to offer.",[651,168809,168810],{},"I think one that really stuck with me was the fact that we need to make sure we are testing our Spring Native applications using:",[651,168812,168813],{},[676,168814,168815],{},"mvn -Pnative test",[651,168817,168818],{},"You’re no longer on the JVM so your normal test suite isn’t going to cut it. Most of the time you aren’t going to run into any issues but you want to make sure you’re testing for your target platform. Thanks for the tip, Sam!",[4542,168820,168822],{"id":168821},"netlify-gridsome-builds","Netlify & Gridsome Builds",[651,168824,168825,168826,168829],{},"My website is written in a static site generator for Vue in a framework called ",[812,168827,43848],{"href":43854,"rel":168828},[816],". Whenever I write a new blog post or a new newsletter like the one you’re reading now I follow a similar process.",[651,168831,168832],{},"All of my writing starts in Notion because that is the single source of truth for all my content creation. When I have completed an article or newsletter I will create that content over on my website. This consists of creating a markdown file, adding some front matter (metadata about the article), and exporting the markdown from Notion into my website.",[651,168834,168835,168836,168840,168841,664],{},"Two weeks ago I had the article about me ",[812,168837,168839],{"href":168256,"rel":168838},[816],"joining VMware"," ready to go. I also had a newsletter edition ready to go that pointed to that article. My process for getting these pieces of content live is simple, ",[2939,168842,168843],{},"git push",[651,168845,168846],{},"This will trigger a new build on Netlify and after a few minutes my content is live. Well this is usually how it works but when I did this on the first day of my new job I ran into an issue. I started seeing this build process time out and I would see the following in the logs",[669,168848,168850],{"className":5851,"code":168849,"language":5853,"meta":674,"style":674},"10:51:42 AM: Render HTML (258 files) - 3.72s\n10:51:42 AM: Process files (1 files) - 0.23s\n10:58:05 AM: Processing images (1047 images) -\n0%Processing images (1047 images) -\n2%Processing images (1047 images) -\n5%Processing images (1047 images) -\n7%Processing images (1047 images) -\n10%Processing images (1047 images) -\n12%Processing images (1047 images) -\n14%Processing images (1047 images) -\n17%Processing images (1047 images) -\n19%Processing images (1047 images) -\n21%Processing images (1047 images) -\n24%Processing images (1047 images) -\n26%Processing images (1047 images) -\n29%Processing images (1047 images) -\n31%Processing images (1047 images) -\n...\n(1047 images) - 86%Processing images (1047 images) - 88%Processing images (1047 images) - 90%\n",[676,168851,168852,168874,168894,168915,168928,168941,168954,168967,168980,168993,169006,169019,169032,169045,169058,169071,169084,169097,169101],{"__ignoreMap":674},[679,168853,168854,168857,168860,168863,168865,168868,168871],{"class":681,"line":682},[679,168855,168856],{"class":880},"10:51:42",[679,168858,168859],{"class":689}," AM:",[679,168861,168862],{"class":689}," Render",[679,168864,24787],{"class":689},[679,168866,168867],{"class":693}," (258 ",[679,168869,168870],{"class":689},"files",[679,168872,168873],{"class":693},") - 3.72s\n",[679,168875,168876,168878,168880,168883,168886,168889,168891],{"class":681,"line":790},[679,168877,168856],{"class":880},[679,168879,168859],{"class":689},[679,168881,168882],{"class":689}," Process",[679,168884,168885],{"class":689}," files",[679,168887,168888],{"class":693}," (1 ",[679,168890,168870],{"class":689},[679,168892,168893],{"class":693},") - 0.23s\n",[679,168895,168896,168899,168901,168904,168907,168910,168912],{"class":681,"line":892},[679,168897,168898],{"class":880},"10:58:05",[679,168900,168859],{"class":689},[679,168902,168903],{"class":689}," Processing",[679,168905,168906],{"class":689}," images",[679,168908,168909],{"class":693}," (1047 ",[679,168911,101549],{"class":689},[679,168913,168914],{"class":693},") -\n",[679,168916,168917,168920,168922,168924,168926],{"class":681,"line":901},[679,168918,168919],{"class":880},"0%Processing",[679,168921,168906],{"class":689},[679,168923,168909],{"class":693},[679,168925,101549],{"class":689},[679,168927,168914],{"class":693},[679,168929,168930,168933,168935,168937,168939],{"class":681,"line":909},[679,168931,168932],{"class":880},"2%Processing",[679,168934,168906],{"class":689},[679,168936,168909],{"class":693},[679,168938,101549],{"class":689},[679,168940,168914],{"class":693},[679,168942,168943,168946,168948,168950,168952],{"class":681,"line":918},[679,168944,168945],{"class":880},"5%Processing",[679,168947,168906],{"class":689},[679,168949,168909],{"class":693},[679,168951,101549],{"class":689},[679,168953,168914],{"class":693},[679,168955,168956,168959,168961,168963,168965],{"class":681,"line":935},[679,168957,168958],{"class":880},"7%Processing",[679,168960,168906],{"class":689},[679,168962,168909],{"class":693},[679,168964,101549],{"class":689},[679,168966,168914],{"class":693},[679,168968,168969,168972,168974,168976,168978],{"class":681,"line":944},[679,168970,168971],{"class":880},"10%Processing",[679,168973,168906],{"class":689},[679,168975,168909],{"class":693},[679,168977,101549],{"class":689},[679,168979,168914],{"class":693},[679,168981,168982,168985,168987,168989,168991],{"class":681,"line":959},[679,168983,168984],{"class":880},"12%Processing",[679,168986,168906],{"class":689},[679,168988,168909],{"class":693},[679,168990,101549],{"class":689},[679,168992,168914],{"class":693},[679,168994,168995,168998,169000,169002,169004],{"class":681,"line":964},[679,168996,168997],{"class":880},"14%Processing",[679,168999,168906],{"class":689},[679,169001,168909],{"class":693},[679,169003,101549],{"class":689},[679,169005,168914],{"class":693},[679,169007,169008,169011,169013,169015,169017],{"class":681,"line":977},[679,169009,169010],{"class":880},"17%Processing",[679,169012,168906],{"class":689},[679,169014,168909],{"class":693},[679,169016,101549],{"class":689},[679,169018,168914],{"class":693},[679,169020,169021,169024,169026,169028,169030],{"class":681,"line":982},[679,169022,169023],{"class":880},"19%Processing",[679,169025,168906],{"class":689},[679,169027,168909],{"class":693},[679,169029,101549],{"class":689},[679,169031,168914],{"class":693},[679,169033,169034,169037,169039,169041,169043],{"class":681,"line":988},[679,169035,169036],{"class":880},"21%Processing",[679,169038,168906],{"class":689},[679,169040,168909],{"class":693},[679,169042,101549],{"class":689},[679,169044,168914],{"class":693},[679,169046,169047,169050,169052,169054,169056],{"class":681,"line":993},[679,169048,169049],{"class":880},"24%Processing",[679,169051,168906],{"class":689},[679,169053,168909],{"class":693},[679,169055,101549],{"class":689},[679,169057,168914],{"class":693},[679,169059,169060,169063,169065,169067,169069],{"class":681,"line":2129},[679,169061,169062],{"class":880},"26%Processing",[679,169064,168906],{"class":689},[679,169066,168909],{"class":693},[679,169068,101549],{"class":689},[679,169070,168914],{"class":693},[679,169072,169073,169076,169078,169080,169082],{"class":681,"line":2140},[679,169074,169075],{"class":880},"29%Processing",[679,169077,168906],{"class":689},[679,169079,168909],{"class":693},[679,169081,101549],{"class":689},[679,169083,168914],{"class":693},[679,169085,169086,169089,169091,169093,169095],{"class":681,"line":2145},[679,169087,169088],{"class":880},"31%Processing",[679,169090,168906],{"class":689},[679,169092,168909],{"class":693},[679,169094,101549],{"class":689},[679,169096,168914],{"class":693},[679,169098,169099],{"class":681,"line":2154},[679,169100,5741],{"class":931},[679,169102,169103,169105,169108,169110,169112,169114,169117,169119,169121,169123,169126,169128,169130],{"class":681,"line":2159},[679,169104,745],{"class":693},[679,169106,169107],{"class":880},"1047",[679,169109,168906],{"class":689},[679,169111,2378],{"class":693},[679,169113,6453],{"class":880},[679,169115,169116],{"class":689}," 86%Processing",[679,169118,168906],{"class":689},[679,169120,168909],{"class":693},[679,169122,101549],{"class":689},[679,169124,169125],{"class":693},") - 88%Processing images (",[679,169127,169107],{"class":880},[679,169129,168906],{"class":689},[679,169131,169132],{"class":693},") - 90%\n",[651,169134,169135],{},"From what I can tell this is Gridsome creating different versions of every single image on my website. This process was taking too long and the build would simply timeout each time. So I have this article about how I am starting my dream job and I can’t get it live 🤦♂️",[651,169137,169138,169139,169144],{},"After trying to find a solution to this problem I started thinking about it differently. I ran a build locally and it worked just fine. I have this great machine, why not let it do the work. It was then I found ",[812,169140,169143],{"href":169141,"rel":169142},"https://gridsome.org/docs/deploy-to-netlify/",[816],"these instructions"," that showed me how I could deploy to Netlify directly using the Netlify CLI.",[651,169146,169147],{},"I Installed the Netlify CLI, ran the build locally, and pushed it to production using the command",[651,169149,169150],{},[676,169151,169152],{},"netlify deploy --prod",[651,169154,169155],{},"This ended up not only working great but was really fast. I think I am going to take this approach going forward even If I find a solution to my problem.",[651,169157,169158],{},"If you’re interested in doing this just make sure you go into Netlify and turn off automatic builds on commits to your repository.",[4542,169160,169162],{"id":169161},"groovy-4","Groovy 4",[651,169164,169165,169166,169170,169171,169176],{},"If you're new around here I’m a huge fan of ",[812,169167,18628],{"href":169168,"rel":169169},"https://groovy-lang.org/",[816]," and have been for a while now. I created a ",[812,169172,169175],{"href":169173,"rel":169174},"https://www.udemy.com/course/apache-groovy/?referralCode=7FB8CB67D3D3D17CF1EA",[816],"14-hour course"," that teaches you all the fundamentals of Groovy. I haven’t been using it as much lately but it will always be special to me.",[651,169178,169179,169184],{},[812,169180,169183],{"href":169181,"rel":169182},"https://groovy-lang.org/releasenotes/groovy-4.0.html",[816],"Groovy 4 was just released"," and there are some really great new features. I will need some time to go through all of them but I will talk about one of them here. If you’re on Java 16+ you have access to records but what happens if you’re still stuck in the stone age of Java 8.",[651,169186,169187,169188,169191],{},"If you’re using Groovy 4 you can use the new ",[2939,169189,169190],{},"RecordType"," and take advantage of record style classes, regardless of what version of Java you’re on. If you’re a version that supports records the generated class will be a record but if It isn’t it will be an immutable type that resembles the characteristics of a record class. Pretty cool if you ask me 🥳",[651,169193,169194],{},[660,169195],{"alt":169196,"src":169197},"groovy_record.png","/images/newsletter/2022/02/07/groovy_record.png",[4542,169199,169201],{"id":169200},"spring-boot-magic","Spring Boot Magic",[651,169203,169204],{},"I’m worked on some demo’s last week that might turn into an article, tutorial, presentation, course or all of the above. I think a lot of us build out a Spring Boot application without understanding what is happening behind the scenes. The Spring Initializr, Spring Boot Starters and Auto Configuration can make getting started seem a little bit like magic.",[651,169206,169207],{},"As I started putting these demos together I remembered that I already have a tutorial on this. In the video below I show how to start with a simple Maven project and build out a simple Spring Framework application.",[5988,169209],{"id":169210},"e8aSyQo0nHo",[651,169212,169213],{},"The next evolution of this is building out a web application with an embedded Tomcat server, logging, externalized configuration and a database connection. If you can think of anything else you would like to see in this demo please let me know.",[4542,169215,169217],{"id":169216},"live-streaming","Live Streaming",[651,169219,169220,169221,51393,169224,169228],{},"I am going to live stream this week but I just don’t have it scheduled yet. If you’re not following me ",[812,169222,60837],{"href":157768,"rel":169223},[816],[812,169225,169227],{"href":165299,"rel":169226},[816],"subscribed to me on YouTube"," please do that now to be notified when I go live. If there are any topics you would like to see me cover please let me know.",[4542,169230,157574],{"id":157573},[5909,169232,164959],{"id":69848},[5316,169234,169235,169242],{},[5332,169236,169237],{},[812,169238,169241],{"href":169239,"rel":169240},"https://devblogs.microsoft.com/java/java-on-visual-studio-code-update-january-2022/",[816],"Java on Visual Studio Code Update – January 2022",[5332,169243,169244],{},[812,169245,169248],{"href":169246,"rel":169247},"https://dashaun.hashnode.dev/spring-boot-263-arm64-image",[816],"Spring Boot 2.6.3 ARM64 Image",[5909,169250,164971],{"id":157591},[5316,169252,169253,169260],{},[5332,169254,169255],{},[812,169256,169259],{"href":169257,"rel":169258},"https://foojay.io/today/video-sdkman-explained/",[816],"SDKMAN Explained",[5332,169261,169262],{},[812,169263,169266],{"href":169264,"rel":169265},"https://www.youtube.com/watch?v=cyqfiUlx2UE",[816],"You may not need JavaScript | Simon Martinelli (EN)",[5909,169268,164983],{"id":39439},[5316,169270,169271,169278],{},[5332,169272,169273],{},[812,169274,169277],{"href":169275,"rel":169276},"https://bootifulpodcast.fm/#/episodes/1a1dc9ea-c306-46b8-a835-5e248f7d4636",[816],"A Bootiful Podcast: Spring Data lead and architect extraordinaire, Oliver Drotbohm",[5332,169279,169280],{},[812,169281,169284],{"href":169282,"rel":169283},"https://bootifulpodcast.fm/#/episodes/1c136ac9-2cc3-4b76-80fa-da458fedbafe",[816],"A Bootiful Podcast: Spring Framework co-founder and project lead, friend, and all-around amazing human being, Juergen Hoeller",[5909,169286,164995],{"id":133422},[5316,169288,169289],{},[5332,169290,169291],{},[812,169292,169295],{"href":169293,"rel":169294},"https://github.com/chucknorris-io/chuck-api",[816],"Chuck Norris API",[5909,169297,166601],{"id":21972},[5316,169299,169300],{},[5332,169301,169302],{},[812,169303,169306],{"href":169304,"rel":169305},"https://learning.oreilly.com/videos/cloud-native-java/9780137834051/",[816],"Cloud Native Java with Kubernetes, 2nd Edition by Josh Long",[5909,169308,169309],{"id":79608},"🗣 Conferences",[5316,169311,169312],{},[5332,169313,169314],{},[812,169315,169318],{"href":169316,"rel":169317},"https://blockbyblock.notion.com/",[816],"Block by Block Notion Conference",[5909,169320,165430],{"id":165429},[651,169322,169323],{},[812,169324,169325],{"href":169325,"rel":169326},"https://twitter.com/therealdanvega/status/1488265020840767489",[816],[4542,169328,157704],{"id":157703},[651,169330,166267,169331,166271],{},[812,169332,41499],{"href":44086,"rel":169333},[816],[651,169335,41105,169336,69920,169338,169340,169342,169344],{},[41107,169337],{},[41107,169339],{},[812,169341,161560],{"href":161111},[41107,169343],{},[812,169345,53869],{"href":82688,"rel":169346},[816],[786,169348,169349],{},"html pre.shiki code .sZnax, html code.shiki .sZnax{--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#6F42C1}html pre.shiki code .suV6U, html code.shiki .suV6U{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#032F62}html pre.shiki code .s-uPX, html code.shiki .s-uPX{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#24292E}html pre.shiki code .sECI1, html code.shiki .sECI1{--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":674,"searchDepth":790,"depth":790,"links":169351},[169352,169353,169354,169355,169356,169357,169366],{"id":168777,"depth":790,"text":168778},{"id":168821,"depth":790,"text":168822},{"id":169161,"depth":790,"text":169162},{"id":169200,"depth":790,"text":169201},{"id":169216,"depth":790,"text":169217},{"id":157573,"depth":790,"text":157574,"children":169358},[169359,169360,169361,169362,169363,169364,169365],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":21972,"depth":892,"text":166601},{"id":79608,"depth":892,"text":169309},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":169368,"date":169369},"testing-with-spring","2022-02-07T08:00:00.000Z","/newsletter/2022/02/07/testing-with-spring",{"title":168766,"description":168771},"newsletter/2022/02/07/testing-with-spring","K1rRIUmI1byLEfqGg4_Ga716BaSVtJPbFTr5wDDeAjo",{"id":169375,"title":169376,"body":169377,"description":169381,"extension":793,"meta":169558,"navigation":797,"path":169560,"seo":169561,"stem":169562,"__hash__":169563},"content/newsletter/2022/02/14/live-streaming.md","Live Streaming, Netlify Serverless Functions and Vue 3 Docs",{"type":648,"value":169378,"toc":169550},[169379,169382,169384,169387,169390,169404,169413,169423,169443,169446,169450,169453,169460,169463,169472,169475,169479,169487,169493,169497,169511,169514,169517,169521,169524,169530,169532,169537],[651,169380,169381],{},"It’s Monday, February 14th and I am once again petitioning for the day after the Super Bowl to be a national holiday. I’m writing this before the game takes place so I don’t know how it ends. I’m just hoping for a good game and a great halftime show. In this week's edition of the newsletter, I want to talk about Live Streaming, Netlify Serverless Functions, Vue 3, and The Dropout.",[4542,169383,169217],{"id":169216},[651,169385,169386],{},"In my new role as a Spring Developer Advocate, I am going to be presenting a lot more this year. I already have a bunch of virtual events lined up including one this week. On Wednesday, February 16th I will be speaking with Mala Gupta at JetBrains. In this session, I will be talking about building REST APIs with Spring Boot.",[651,169388,169389],{},"As much experience as I have with creating and editing videos I don’t have a ton of experience live streaming or presenting virtually. With that, I thought I would start practicing last week and I ended up live streaming 3 times which is probably more action than my Twitch channel has ever seen.",[651,169391,169392,169393,169398,169399,169403],{},"My main goal was just to get used to the software and work out any issues. I am a big fan of ",[812,169394,169397],{"href":169395,"rel":169396},"https://www.ecamm.com/",[816],"Ecamm"," and I want to use the software to stream with but I keep having issues with it. There are times when it works fine and there are other times where the audio gets out of sync and for the life of me I can’t figure it out. This even happens when I use the record-only mode. I thought after getting my ",[812,169400,169402],{"href":82623,"rel":169401},[816],"brand new MacBook"," that this would go away but unfortunately, it didn’t. If anyone knows what would cause this please let me know.",[651,169405,169406,169407,169412],{},"I also used Streamyard for the first time this week and was happy with it. We have a group account for this at work and I will also be using this on my stream this Wednesday. I wish it was as customizable as Ecamm but on the other hand, I didn’t have any audio issues. I’m still trying to find a good way to play background music on Streamyard. I think I found a solution using ",[812,169408,169411],{"href":169409,"rel":169410},"https://rogueamoeba.com/loopback/",[816],"Loopback"," but I am still testing that out.",[651,169414,169415,169416,51393,169419,169422],{},"You can find the 3 live stream recordings below. If you’re not already following me on ",[812,169417,60837],{"href":157768,"rel":169418},[816],[812,169420,15432],{"href":165299,"rel":169421},[816]," please do to be notified when I go live next.",[5316,169424,169425,169431,169437],{},[5332,169426,169427],{},[812,169428,169429],{"href":169429,"rel":169430},"https://www.twitch.tv/videos/1291608965",[816],[5332,169432,169433],{},[812,169434,169435],{"href":169435,"rel":169436},"https://www.twitch.tv/videos/1292435014",[816],[5332,169438,169439],{},[812,169440,169441],{"href":169441,"rel":169442},"https://www.twitch.tv/videos/1293492510",[816],[651,169444,169445],{},"As far as what I did in the live streams, I will cover that in the next section.",[4542,169447,169449],{"id":169448},"netlify-serverless-functions","Netlify Serverless Functions",[651,169451,169452],{},"Throughout the live streams I mentioned above I was trying to solve a real problem that I have. With all of these speaking appearances, I have scheduled this year I need a way to display them on my website.",[651,169454,169455,169456,169459],{},"If you follow anything I do you know I am a huge fan of Notion. I keep everything I do in Notion so naturally, I would like to store all of my conferences, user group meetups, and live streams in one place. I started with just a list of upcoming live streams and using the ",[812,169457,101347],{"href":101345,"rel":169458},[816]," I was able to pull all of them into a page on my website.",[651,169461,169462],{},"The next question becomes how do you do that when you’re using a frontend framework like Vue. My website is a static generated website using Gridsome + Vue. You don’t want to make API calls from a frontend because you can expose your authentication / API secrets. I could stand up a backend with something like Node or Java but that seems like a lot for such a simple task. This is a perfect use case for serverless functions.",[651,169464,169465,169466,169471],{},"I was able to do this thanks to ",[812,169467,169470],{"href":169468,"rel":169469},"https://docs.netlify.com/functions/overview/",[816],"Netlify Serverless functions",". With Netlify you can create a directory and any file that exports a handler function gets turned into a serverless function and uploaded to AWS Lambda. I decided to take what I learned from the live streams and created a simple tutorial on how I used a Netlify function to connect to the Notion API.",[5988,169473],{"id":169474},"A-b1ZdlNbww",[4542,169476,169478],{"id":169477},"vue-3-documentation","Vue 3 Documentation",[651,169480,169481,169482,169486],{},"If you haven’t been paying attention the ",[812,169483,169485],{"href":43587,"rel":169484},[816],"Vue 3 docs"," have been released. Along with this release Vue 3 has now become the default. If you look in the upper left-hand corner of the documentation you will see an API reference section. This toggle will allow you to switch the docs based on if you’re using the Options or Composition API. I thought that was a really nice touch 🎉",[651,169488,169489],{},[660,169490],{"alt":169491,"src":169492},"Vue 3 Docs","/images/newsletter/2022/02/14/vue_3_docs.png",[4542,169494,169496],{"id":169495},"the-dropout","The Dropout",[651,169498,169499,169500,169504,169505,169510],{},"I am fascinated by the story of Elizabeth Holmes and Theranos. I read ",[812,169501,51563],{"href":169502,"rel":169503},"https://amzn.to/36eN1GJ",[816]," by ",[812,169506,169509],{"href":169507,"rel":169508},"https://www.amazon.com/John-Carreyrou/e/B07CWLT57D/ref=dp_byline_cont_book_1",[816],"John Carreyrou"," a few years back and was totally sucked into everything that went on. There was also a really good documentary on HBO about this that I also enjoyed.",[651,169512,169513],{},"Elizabeth Holmes has been in the news lately as she went to trial and was found guilty of defrauding investors. There is a new show coming to Hulu called the dropout which has an incredible cast.",[5988,169515],{"id":169516},"novUDyBszA8",[4542,169518,169520],{"id":169519},"nuxt-3","Nuxt 3",[651,169522,169523],{},"This was a really great Twitter thread by Sébastien Chopin on where we are with Nuxt 3. I am obviously paying close attention to this because I’m hoping to move my website over to Nuxt 3 this year.",[651,169525,169526],{},[812,169527,169528],{"href":169528,"rel":169529},"https://twitter.com/Atinux/status/1492134648733913089",[816],[4542,169531,157704],{"id":157703},[651,169533,166267,169534,166271],{},[812,169535,41499],{"href":44086,"rel":169536},[816],[651,169538,41105,169539,69920,169541,169543,169545,169547],{},[41107,169540],{},[41107,169542],{},[812,169544,161560],{"href":161111},[41107,169546],{},[812,169548,53869],{"href":82688,"rel":169549},[816],{"title":674,"searchDepth":790,"depth":790,"links":169551},[169552,169553,169554,169555,169556,169557],{"id":169216,"depth":790,"text":169217},{"id":169448,"depth":790,"text":169449},{"id":169477,"depth":790,"text":169478},{"id":169495,"depth":790,"text":169496},{"id":169519,"depth":790,"text":169520},{"id":157703,"depth":790,"text":157704},{"slug":169216,"date":169559},"2022-02-14T08:00:00.000Z","/newsletter/2022/02/14/live-streaming",{"title":169376,"description":169381},"newsletter/2022/02/14/live-streaming","uygC2F-YCXIBKk-X6t35O3ju4GQKlgcfUJi4umXhg2A",{"id":169565,"title":169566,"body":169567,"description":169571,"extension":793,"meta":169889,"navigation":797,"path":169891,"seo":169892,"stem":169893,"__hash__":169894},"content/newsletter/2022/02/28/spring-developers-twitter-community.md.md","🇺🇦 Thinking of Ukraine, Two Presentations and Spring Developers Twitter Community",{"type":648,"value":169568,"toc":169874},[169569,169572,169575,169578,169581,169584,169591,169613,169620,169626,169629,169637,169646,169649,169654,169677,169682,169711,169717,169720,169724,169732,169736,169745,169751,169753,169761,169767,169769,169771,169800,169802,169811,169813,169835,169837,169846,169848,169854,169856,169861],[651,169570,169571],{},"With everything going on in the world it’s hard to sit here and act like everything is normal. My heart is broken on what the people of Ukraine are going through. Please keep them in your prayers and thoughts and if you can do anything to support them please do.",[651,169573,169574],{},"After missing last week I’m back with another edition of the newsletter and I have a lot to talk about. Last week marked 1 month at VMware for me and It still doesn’t feel real to me. I get to wake up every single day and do what I love.",[651,169576,169577],{},"In the past 2 weeks, I gave 2 presentations and I will tell you all about those in case you missed them. I also want to talk about the Twitter Community I started and a major milestone that I crossed on YouTube.",[4542,169579,97086],{"id":169580},"building-rest-apis-in-spring-boot",[651,169582,169583],{},"I was honored to be asked to speak on the IntelliJ IDEA YouTube live stream with Mala Gupta. In this presentation, you will learn how to build a CRUD REST API using Spring Boot. I spent most of this presentation live coding and teaching the mechanics of building a REST API without going to much into REST principles.",[651,169585,169586,169587,169590],{},"I was asked a lot of questions about which plugins I use for IntelliJ. A lot of people were amazed at some of the things ",[812,169588,97502],{"href":82643,"rel":169589},[816]," can do and I would have to agree with them. I am using 2021.3 Ultimate Edition at the time of this recording. The plugins I was asked about the most are:",[5316,169592,169593,169600,169607],{},[5332,169594,169595,169596],{},"Live Templates: ",[812,169597,169598],{"href":169598,"rel":169599},"https://www.jetbrains.com/help/idea/using-live-templates.html",[816],[5332,169601,169602,169603],{},"HTTP Client: ",[812,169604,169605],{"href":169605,"rel":169606},"https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html",[816],[5332,169608,169609,169610],{},"Github Copilot: ",[812,169611,82213],{"href":82213,"rel":169612},[816],[651,169614,169615,169616,664],{},"If you want to watch the recording of this presentation you can check it out below. The source code and slides can be found ",[812,169617,18263],{"href":169618,"rel":169619},"https://github.com/danvega/building-rest-apis-spring-boot",[816],[651,169621,169622],{},[812,169623,169624],{"href":169624,"rel":169625},"https://www.youtube.com/watch?v=q_RLfOB7axQ&t=2320s",[816],[4542,169627,97079],{"id":169628},"whats-new-in-spring-boot-26",[651,169630,169631,169632,169636],{},"I was asked to speak at the San Francisco Java User Group and jumped on the opportunity. In this presentation, I gave an overview of what’s new in Spring Boot 2.6. I spent a lot of time going through the ",[812,169633,95704],{"href":169634,"rel":169635},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes",[816]," for Spring Boot 2.6.",[651,169638,169639,169640,169645],{},"In the past, I have gone through the release notes but it was more of a let me scan through here and see if anything is interesting to me. There were times I didn’t understand some of the updates until I saw one of those great ",[812,169641,169644],{"href":169642,"rel":169643},"https://www.youtube.com/watch?v=lgyO9C9zdrg",[816],"overview videos"," from Phil Webb. That’s why I was really excited to put this presentation together and show some code examples of what was new in Spring Boot 2.6.",[651,169647,169648],{},"The release notes for each major upgrade are broken down into 2 sections, Upgrading from the previous version and New & Noteworthy. I was prepared to talk about the following and we ended up getting to most of them:",[651,169650,169651],{},[2939,169652,169653],{},"Upgrading from Spring Boot 2.5",[5316,169655,169656,169659,169662,169665,169668,169671,169674],{},[5332,169657,169658],{},"Release Versioning",[5332,169660,169661],{},"Deprecations",[5332,169663,169664],{},"Circular References",[5332,169666,169667],{},"Path Matching Strategy",[5332,169669,169670],{},"Actuator Env",[5332,169672,169673],{},"Application Startup (2.4)",[5332,169675,169676],{},"Records & Configuration Properties",[651,169678,169679],{},[2939,169680,169681],{},"New & Noteworthy",[5316,169683,169684,169687,169690,169693,169696,169699,169702,169705,169708],{},[5332,169685,169686],{},"Dependency Upgrades",[5332,169688,169689],{},"Reactive Server Session Properties",[5332,169691,169692],{},"Build Info Property Exclusion",[5332,169694,169695],{},"SameSite Cookie Attribute",[5332,169697,169698],{},"Pluggable Sanitization Rules",[5332,169700,169701],{},"Java Runtime Information",[5332,169703,169704],{},"WebTestClient for Spring MVC",[5332,169706,169707],{},"Metrics Support",[5332,169709,169710],{},"Docker Image Building Support",[651,169712,169713],{},[812,169714,169715],{"href":169715,"rel":169716},"https://www.youtube.com/watch?v=oNs8sLbbAT4",[816],[651,169718,169719],{},"I’m going to come back and create videos for specific items on this list for YouTube. If you would like a detailed dive into anything new in Spring Boot 2.6 please reach out and let me know.",[4542,169721,169723],{"id":169722},"spring-developers-twitter-community","Spring Developers Twitter Community",[651,169725,169726,169727,664],{},"I was finally able to get access to create a new Community on Twitter. You can think of Twitter Communities as a combination of Facebook Groups and Reddit. I created a community for Spring Developers to connect, share what they are working on, and ask questions. In just a few days we climbed up to 800+ members. I’m excited to see where this goes so if you’re not a member please ",[812,169728,169731],{"href":169729,"rel":169730},"https://twitter.com/i/communities/1496544801533091844",[816],"join us",[4542,169733,169735],{"id":169734},"awesome-spring","Awesome Spring",[651,169737,169738,169739,169744],{},"A huge shoutout to Thomas Vitale for adding me to his ",[812,169740,169743],{"href":169741,"rel":169742},"https://github.com/ThomasVitale/awesome-spring",[816],"Awesome Spring Resource",". This is a really good collection of resources from the Spring Community. What I love about this is that it is a carefully hand-crafted list and not just a huge dump of resources. Thomas is big in the Spring community and this is a great resource if you enjoy please show your support by giving it a star.",[651,169746,169747],{},[812,169748,169749],{"href":169749,"rel":169750},"https://twitter.com/vitalethomas/status/1495883469498507267",[816],[4542,169752,15432],{"id":61224},[651,169754,169755,169756,169760],{},"I recently passed 100,000 hours of watch time on YouTube. To date I have only uploaded 232 videos most of which I would say aren’t great so to hit this milestone is pretty amazing. I want to thank all of you who support the channel and if ",[812,169757,169759],{"href":165299,"rel":169758},[816],"you’re not subscribed yet",", what are you waiting for? I have a bunch of content in the backlog for the next month so stay tuned!",[651,169762,169763],{},[660,169764],{"alt":169765,"src":169766},"YouTube 100k","/images/newsletter/2022/02/28/youtube_100k.png",[4542,169768,157574],{"id":157573},[5909,169770,164959],{"id":69848},[5316,169772,169773,169780,169787,169793],{},[5332,169774,169775],{},[812,169776,169779],{"href":169777,"rel":169778},"https://mashable.com/article/tumblr-girl-learn-code",[816],"From Tumblr girl to engineer",[5332,169781,169782],{},[812,169783,169786],{"href":169784,"rel":169785},"https://www.netlify.com/blog/announcing-netlify-graph-a-faster-way-for-teams-to-develop-web-apps-with-apis",[816],"Announcing Netlify Graph: A faster way for teams to develop web apps with APIs",[5332,169788,169789],{},[812,169790,97100],{"href":169791,"rel":169792},"https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter",[816],[5332,169794,169795],{},[812,169796,169799],{"href":169797,"rel":169798},"https://blog.trifork.com/2022/02/25/getting-out-of-a-codependent-relationship-or-how-i-moved-to-a-healthy-component-based-spring-security-configuration/",[816],"How I moved to a healthy component-based Spring Security configuration",[5909,169801,164983],{"id":39439},[5316,169803,169804],{},[5332,169805,169806],{},[812,169807,169810],{"href":169808,"rel":169809},"https://bootifulpodcast.fm/#/episodes/af8b106e-273f-4fc9-ad19-97f3da52906f",[816],"A Bootitful Podcast: Java team luminary Sharat Chander",[5909,169812,164995],{"id":133422},[5316,169814,169815,169822,169828],{},[5332,169816,169817],{},[812,169818,169821],{"href":169819,"rel":169820},"https://jdk.java.net/18/",[816],"Java 18",[5332,169823,169824],{},[812,169825,169827],{"href":122169,"rel":169826},[816],"Hilla - A modern web framework for Java",[5332,169829,169830],{},[812,169831,169834],{"href":169832,"rel":169833},"https://2021.stateofjs.com/en-US/",[816],"State of JS 2021",[5909,169836,165005],{"id":39340},[5316,169838,169839],{},[5332,169840,169841],{},[812,169842,169845],{"href":169843,"rel":169844},"https://amzn.to/3KdfsUb",[816],"Head first Git",[5909,169847,165430],{"id":165429},[651,169849,169850],{},[812,169851,169852],{"href":169852,"rel":169853},"https://twitter.com/jetbrains/status/1496786254494670851",[816],[4542,169855,157704],{"id":157703},[651,169857,166267,169858,166271],{},[812,169859,41499],{"href":44086,"rel":169860},[816],[651,169862,41105,169863,69920,169865,169867,169869,169871],{},[41107,169864],{},[41107,169866],{},[812,169868,161560],{"href":161111},[41107,169870],{},[812,169872,53869],{"href":82688,"rel":169873},[816],{"title":674,"searchDepth":790,"depth":790,"links":169875},[169876,169877,169878,169879,169880,169881,169888],{"id":169580,"depth":790,"text":97086},{"id":169628,"depth":790,"text":97079},{"id":169722,"depth":790,"text":169723},{"id":169734,"depth":790,"text":169735},{"id":61224,"depth":790,"text":15432},{"id":157573,"depth":790,"text":157574,"children":169882},[169883,169884,169885,169886,169887],{"id":69848,"depth":892,"text":164959},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":169722,"date":169890},"2022-02-28T08:00:00.000Z","/newsletter/2022/02/28/spring-developers-twitter-community.md",{"title":169566,"description":169571},"newsletter/2022/02/28/spring-developers-twitter-community.md","iyxz-lHmXLHlfx7Btf7yMMPsznqaMB_WpvDXOcUYMtg",{"id":169896,"title":169897,"body":169898,"description":169902,"extension":793,"meta":170188,"navigation":797,"path":170191,"seo":170192,"stem":170193,"__hash__":170194},"content/newsletter/2022/03/07/youtube-tips.md","My YouTube tips and I’m reviewing a book!",{"type":648,"value":169899,"toc":170165},[169900,169903,169906,169909,169912,169914,169917,169920,169923,169926,169929,169932,169936,169939,169942,169945,169949,169952,169955,169959,169962,169966,169969,169972,169974,169977,169993,169997,170000,170003,170006,170010,170013,170016,170020,170023,170028,170031,170036,170041,170046,170051,170056,170060,170063,170066,170068,170070,170079,170081,170090,170092,170115,170117,170126,170128,170137,170139,170145,170147,170152],[651,169901,169902],{},"I hope you had an amazing weekend and you’re ready to take on a new week. In today’s edition of the newsletter, I want to talk about YouTube and being asked to review a book.",[651,169904,169905],{},"Before we jump in I want to make sure everyone is prepared for one of my favorite days of the year. If you have to suffer at the hands (on the clock) of daylight savings, I’m sorry. If you vote for me for President my first task will be to abolish this ridiculous tradition.",[651,169907,169908],{},"Until it goes away I need to remind you that on Sunday, March 13, 2022, we spring our clocks forward. I mark this day on my calendar every single year because it means that the worst of winter is behind us and Spring is near.",[651,169910,169911],{},"It’s now staying light out longer and the sun is rising earlier. If you’re in San Diego or Miami where it’s probably 75 and sunny all year long you probably don’t feel my pain but I can assure you it's real. It certainly does give me a bigger appreciation for the seasons. I am excited about being to run outside, take my girls to the playground and baseball. That is if they actually play baseball this year 🤦♂️",[4542,169913,15432],{"id":61224},[651,169915,169916],{},"I had a conversation with someone last week about YouTube and I want to share some of that with you because it might help someone out who is in the same situation. She wants to start creating YouTube videos and wanted to get some advice from me on the subject.",[651,169918,169919],{},"I usually start this conversation with “What is your why”. What is your motivation behind the desire to create videos for YouTube? She responded with exactly what I thought she would and that was to bring her unique perspective and help people.",[651,169921,169922],{},"I usually ask this question because if your only motiving factor is money, it’s not going to work out. Can you make money on YouTube, absolutely? Can it be a motiving factor, for sure but don’t let it be the primary reason for getting into it.",[651,169924,169925],{},"There are a lot of people who have left their 9-5 because of their YouTube success but this doesn’t happen over night. YouTube is a long game and if you’re in it for the money you aren’t going to get that instant gratification you’re looking for.",[651,169927,169928],{},"The next set of questions usually revolves around hardware and how to set up an office for content creation. This was something we didn’t really need to go into, she live streams a lot and has this down. If this is something you have questions about please feel free to reach out to me.",[651,169930,169931],{},"With the rest of our conversation, I thought I would just share some tips that I have learned from others or through personal experience. Here are a few of the highlights:",[5909,169933,169935],{"id":169934},"just-upload","Just upload",[651,169937,169938],{},"When you’re starting something new you want to do it right. This is great but too much thinking about making that first video can lead to paralysis by analysis. I’m going to tell you something that you don’t want to hear but it’s the harsh truth. Your first video and maybe even up to 100 after that aren’t going to be very good. Sure there are exceptions to the rule but the majority of YouTubers go through this phase.",[651,169940,169941],{},"Now that we got that out of the way I want to let you know that it’s ok. Your mission is to learn about YouTube and find out of creating videos on the platform is something you enjoy doing. It’s about the process of sharing your passion with the world and you need to remember this in the beginning.",[651,169943,169944],{},"Your job is to figure out what you want to make a video on and just make it. As you begin to make more videos if you can find 1 small thing to improve on in each video you will be on your way to achieving your goals.",[5909,169946,169948],{"id":169947},"deliver-on-your-promise","Deliver on your promise",[651,169950,169951],{},"Don’t use clickbait. It doesn’t help you, your channel, or the viewer. Instead, make sure that your title and description inform the viewer of what they can expect from your video. When there is a clear outcome defined everyone wins.",[651,169953,169954],{},"You can reiterate the purpose and the outcome of this video in your outcome as long as you keep it short. As a viewer, I don’t want a 10-minute intro after clicking on your video. Explain the purpose and get right into the meat of the video if possible.",[5909,169956,169958],{"id":169957},"follow-your-passion","Follow your passion",[651,169960,169961],{},"This might seem like common sense to most people but let me explain. Remember when I said that YouTube was a long game? If you’re going to be in this for the long haul you need to be making videos on topics you’re passionate about. If you’re simply making videos because you think they are going to make the most money it is not going to last.",[5909,169963,169965],{"id":169964},"consistency-is-key","Consistency is key",[651,169967,169968],{},"Again this might seem like common sense but it’s not. It’s easy to get caught up in all the vanity metrics like subscribers and number of views, especially on the road to making the YouTube partner program but they shouldn’t be your initial goal.",[651,169970,169971],{},"If you can set a goal of producing a video on a consistent schedule great things will happen. This doesn’t need to be a goal of 5 videos a week or anything crazy like that. This needs to be a schedule that you can consistently stick to without burning out. You might be able to create 4 videos one week but if you go another 4 weeks without making one it isn’t going to help. If you think you can deliver 1 video per month and stick to that schedule, do that.",[5909,169973,6016],{"id":39340},[651,169975,169976],{},"I have a ton of resources on YouTube and people you should follow but to keep this short and sweet I will leave you with 2 books that I absolutely love.",[5316,169978,169979,169986],{},[5332,169980,169981],{},[812,169982,169985],{"href":169983,"rel":169984},"https://amzn.to/3sMhqEX",[816],"The YouTube Formula - Derral Eves",[5332,169987,169988],{},[812,169989,169992],{"href":169990,"rel":169991},"https://amzn.to/3vLcSkg",[816],"YouTube Secrets - Sean Cannell & Benji Travis",[4542,169994,169996],{"id":169995},"java-records-in-spring-boot","Java Records in Spring Boot",[651,169998,169999],{},"We have all heard the complaints that \"java is too verbose\". Some of the worst offenders of this are classes that are nothing more than data classes. To write a data class properly, you have to write a lot of low-value, repetitive, error-prone code: constructors, accessors, equals(), hashCode(), toString(), etc. IDEs will help with a lot of this but it doesn't correct the problem of readability or verbosity in these classes.",[651,170001,170002],{},"Record classes were finalized and released in Java 16. In today's tutorial, you will learn how to use record classes in your next Spring Boot project.",[5988,170004],{"id":170005},"3NshiQIy7p4",[4542,170007,170009],{"id":170008},"spring-constructor-injection","Spring Constructor Injection",[651,170011,170012],{},"I had a lot of fun putting this video together. In this tutorial, we try and answer the question that I hear a lot from Spring developers and that is “Why is constructor injection the recommended approach to dependency injection. I took a look at how we declare and manage dependencies outside of spring and then how we can tell Spring to manage our objects for us.",[5988,170014],{"id":170015},"aX-bgylmprA",[5909,170017,170019],{"id":170018},"youtube-reviews","YouTube Reviews",[651,170021,170022],{},"In just a few days I have had a lot of feedback on this video and it’s been humbling. One person in particular who I look up to and is on the Spring Team had this to say:",[1004,170024,170025],{},[651,170026,170027],{},"Hey, Dan. I just wanted to let you know that I absolutely love your videos on YouTube. I really mean it. Those 15 to 20-minute bites on particular topics have really ramped up our game on this. So well thought out and produced. Keep `em coming!",[651,170029,170030],{},"This comment really made my day. It’s really easy to reach out to someone and say something nice and I really appreciate this person for doing so. Comments like this keep me pushing forward!",[651,170032,170033],{},[2939,170034,170035],{},"YouTube comments",[1004,170037,170038],{},[651,170039,170040],{},"Finally a high-quality Spring tutorial!\ncan't wait to see more 🔥 this was so clear glad I found you",[1004,170042,170043],{},[651,170044,170045],{},"Really good content! i always made constructor injection cause my lead told me to, but i never knew why. Thank you!",[1004,170047,170048],{},[651,170049,170050],{},"Hi Dan, this is a great video, I have been a Spring dev for years, but this is such a nice clear explanation. Really enjoy these videos that take small aspects of spring and are explained so well. Thank you",[1004,170052,170053],{},[651,170054,170055],{},"Great content, as always, so clear and well structured, Dan is the best instructor ever! Deep dive please 😊",[4542,170057,170059],{"id":170058},"book-review","Book Review",[651,170061,170062],{},"I was asked by a friend to review a technical book. I was honored and I’m really looking forward to going through this process. It should give me some perspective for when I write my first book 🤩 This is the first time I will be a reviewer for a book but please don’t tell him that or the publisher for that matter. Actually, I know for a fact he reads this so hopefully, he took the week off.",[651,170064,170065],{},"If you have any suggestions or tips for me I’m never above listening to those who have walked the path before me. When it’s all said and done I hope to share some more details with you. What I can tell you is that the author is amazing and I am sure the book will be too, even without having read a single word!",[4542,170067,157574],{"id":157573},[5909,170069,164959],{"id":69848},[5316,170071,170072],{},[5332,170073,170074],{},[812,170075,170078],{"href":170076,"rel":170077},"https://spring.io/blog/2022/02/22/announcing-listcrudrepository-friends-for-spring-data-3-0",[816],"Announcing ListCrudRepository & Friends for Spring Data 3.0",[5909,170080,164971],{"id":157591},[5316,170082,170083],{},[5332,170084,170085],{},[812,170086,170089],{"href":170087,"rel":170088},"https://www.youtube.com/watch?v=bbcm9OLTnsE&t=977s",[816],"Notion - Block by Block 2022: Keynote",[5909,170091,164983],{"id":39439},[5316,170093,170094,170101,170108],{},[5332,170095,170096],{},[812,170097,170100],{"href":170098,"rel":170099},"https://inside.java/2022/03/04/podcast-022/",[816],"Inside Java - Episode 22 “JEP 408 - Simple Web Server”",[5332,170102,170103],{},[812,170104,170107],{"href":170105,"rel":170106},"https://youtu.be/Irb01MOBoYo",[816],"The Artifact.io - Episode 2: Learning Cloud Technologies with Ranga Karanam",[5332,170109,170110],{},[812,170111,170114],{"href":170112,"rel":170113},"https://youtu.be/LPv1IETzOyI",[816],"The Artifact.io - Episode 3: Multicloud vs polycloud, Jakarta EE, and other news",[5909,170116,165005],{"id":39340},[5316,170118,170119],{},[5332,170120,170121],{},[812,170122,170125],{"href":170123,"rel":170124},"https://learning.oreilly.com/library/view/devops-tools-for/9781492084013/",[816],"DevOps Tools for Java Developers",[5909,170127,167862],{"id":160019},[5316,170129,170130],{},[5332,170131,170132],{},[812,170133,170136],{"href":170134,"rel":170135},"https://spring.io/blog/2022/02/22/this-week-in-spring-22-2-22",[816],"This Week in Spring - 22/2/22",[5909,170138,165430],{"id":165429},[651,170140,170141],{},[812,170142,170143],{"href":170143,"rel":170144},"https://twitter.com/therealdanvega/status/1499861007568625669",[816],[4542,170146,157704],{"id":157703},[651,170148,166267,170149,166271],{},[812,170150,41499],{"href":44086,"rel":170151},[816],[651,170153,41105,170154,69920,170156,170158,170160,170162],{},[41107,170155],{},[41107,170157],{},[812,170159,161560],{"href":161111},[41107,170161],{},[812,170163,53869],{"href":82688,"rel":170164},[816],{"title":674,"searchDepth":790,"depth":790,"links":170166},[170167,170174,170175,170178,170179,170187],{"id":61224,"depth":790,"text":15432,"children":170168},[170169,170170,170171,170172,170173],{"id":169934,"depth":892,"text":169935},{"id":169947,"depth":892,"text":169948},{"id":169957,"depth":892,"text":169958},{"id":169964,"depth":892,"text":169965},{"id":39340,"depth":892,"text":6016},{"id":169995,"depth":790,"text":169996},{"id":170008,"depth":790,"text":170009,"children":170176},[170177],{"id":170018,"depth":892,"text":170019},{"id":170058,"depth":790,"text":170059},{"id":157573,"depth":790,"text":157574,"children":170180},[170181,170182,170183,170184,170185,170186],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":39340,"depth":892,"text":165005},{"id":160019,"depth":892,"text":167862},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":170189,"date":170190},"youtube-tips","2022-03-07T08:00:00.000Z","/newsletter/2022/03/07/youtube-tips",{"title":169897,"description":169902},"newsletter/2022/03/07/youtube-tips","9VHOx56ftTgZ9qrfL6zgvso6ssDIzrWwvYYhXG6ice8",{"id":170196,"title":170197,"body":170198,"description":170202,"extension":793,"meta":170285,"navigation":797,"path":170288,"seo":170289,"stem":170290,"__hash__":170291},"content/newsletter/2022/03/14/spring-forward.md","Spring Forward, What's new in Spring Boot 2.6 & Office Hours",{"type":648,"value":170199,"toc":170280},[170200,170203,170206,170208,170211,170214,170217,170220,170223,170226,170230,170233,170236,170247,170260,170262,170267],[651,170201,170202],{},"Happy Spring Forward! Yesterday those of us who have to live under daylight savings moved our clocks forward 1 hour. I look forward to this day as soon as it starts getting cold outside. This day signals to me that winter is almost over and days get longer with the sun setting later.",[651,170204,170205],{},"This is going to be a short newsletter because last week was busy for me personally. My wife was on vacation so I was on solo dad duty Thursday morning to Sunday night. I survived but I will say I don’t know how you single parents do it, especially those with younger kids. You deserve an award on a weekly basis.",[4542,170207,97079],{"id":169628},[651,170209,170210],{},"I put together my first video for the Spring Developer YouTube channel last week. This was exciting for me because it was the first video I created for something outside of my personal channel. Not to mention, the Spring Developer channel had 168k subscribers at the time I posted the video.",[651,170212,170213],{},"In this tutorial, you will learn about what has changed and what’s new in Spring Boot 2.6. With each release, we publish a set of release notes. You could spend the time to go through these but I often find it easier to absorb when the features are associated with a demo and that’s exactly what I did with this video.",[651,170215,170216],{},"I wasn’t able to go through every single feature because there were so many so I picked out what I thought were the major or interesting changes. The video ended up being a little over an hour but don’t let that scare you as I have included timestamps so you can jump to a particular feature.",[5988,170218],{"id":170219},"4L4LEnawcO8",[651,170221,170222],{},"I really hope you enjoy this video and if a particular feature I didn’t go through and you want to see a demo on it please reach out. I learned that keeping up with each of the major releases and really understanding what changed and why and what is new is a great way to stay up to date with Spring Boot.",[651,170224,170225],{},"I also want to thank everyone who watched this video and helped me spread the word on social media. This video ended up with 9,000 views in 1 day and 20,000 views in a 3-day span. Easily my most popular video ever out of the gate. This just gives me more of a desire to grow my own YouTube channel so that I can help more people out.",[4542,170227,170229],{"id":170228},"office-hours","Office Hours",[651,170231,170232],{},"Last week my coworker DaShaun and I ran the first episode of what we are calling Office Hours. We both noticed a need for developers in the Spring Community to have a place where they could ask questions and learn something new.",[651,170234,170235],{},"We are still working on the cadence and what the format of the show looks like but for now, we will cover:",[27665,170237,170238,170241,170244],{},[5332,170239,170240],{},"Announcements",[5332,170242,170243],{},"Demos",[5332,170245,170246],{},"Questions",[651,170248,170249,170250,170255,170256,170259],{},"If you want you can check out the ",[812,170251,170254],{"href":170252,"rel":170253},"https://www.twitch.tv/videos/1418342200",[816],"recording of episode #1",". If you want to catch our next episode that will happen Tuesday, March 15 at 10:30 AM EDT on ",[812,170257,60837],{"href":157768,"rel":170258},[816],". We are both really excited about this and we would love your feedback on it.",[4542,170261,157704],{"id":157703},[651,170263,166267,170264,166271],{},[812,170265,41499],{"href":44086,"rel":170266},[816],[651,170268,41105,170269,69920,170271,170273,170275,170277],{},[41107,170270],{},[41107,170272],{},[812,170274,161560],{"href":161111},[41107,170276],{},[812,170278,53869],{"href":82688,"rel":170279},[816],{"title":674,"searchDepth":790,"depth":790,"links":170281},[170282,170283,170284],{"id":169628,"depth":790,"text":97079},{"id":170228,"depth":790,"text":170229},{"id":157703,"depth":790,"text":157704},{"slug":170286,"date":170287},"spring-forward","2022-03-14T09:45:00.000Z","/newsletter/2022/03/14/spring-forward",{"title":170197,"description":170202},"newsletter/2022/03/14/spring-forward","XDo6EnA8Yfz9XomLY6DfByQBeZJ3ndGizIu2115eF50",{"id":170293,"title":170294,"body":170295,"description":170477,"extension":793,"meta":170478,"navigation":797,"path":170481,"seo":170482,"stem":170483,"__hash__":170484},"content/newsletter/2022/03/21/spring-one-tour-is-back.md","Spring Boot Secrets, @JsonTest & SpringOne Tour is back!",{"type":648,"value":170296,"toc":170464},[170297,170306,170309,170313,170316,170319,170322,170326,170329,170332,170335,170338,170345,170352,170358,170360,170362,170385,170387,170403,170405,170421,170423,170432,170435,170444,170446,170451],[651,170298,170299,170300,170305],{},"Happy Monday friends! In last week's newsletter, I told you how excited I was to “Spring Forward” and move our clocks 1 hr ahead. It’s getting warmer here in Ohio and staying light out longer which I absolutely love. I was excited to read last week that the ",[812,170301,170304],{"href":170302,"rel":170303},"https://www.reuters.com/world/us/us-senate-approves-bill-that-would-make-daylight-savings-time-permanent-2023-2022-03-15/",[816],"US Senate approved a bill"," that would make daylight savings permanent. It is now headed to the desk of the President where It should be signed and we can all be done with this nonsense.",[651,170307,170308],{},"In today’s edition of the newsletter, I want to tell you about 2 YouTube videos I released last week and the upcoming SpringOne Tour.",[4542,170310,170312],{"id":170311},"spring-boot-secret-properties","Spring Boot Secret Properties",[651,170314,170315],{},"In this tutorial, you will learn how to use Spring Boot secret properties in your next application. When you define your own configuration properties you need to find a way to set secret properties. You can do this through command-line arguments or environment variables, but I often forget that they are there.",[651,170317,170318],{},"Spring Boot 2.4 gave us the ability to import additional configuration files. In this demo, you will set the secret values in a file called secret.properties and ignore it from Git, so it doesn't get checked in. To wrap this up I will show you how to deploy this project to Heroku and override those secret properties using config vars.",[5988,170320],{"id":170321},"PmGLn3ua_lU",[4542,170323,170325],{"id":170324},"spring-boot-jsontest-annotation","Spring Boot @JsonTest Annotation",[651,170327,170328],{},"I think if we are being honest most of us don’t write enough tests. We can all agree that they are useful but can’t seem to find the time for them. In Spring we have slice tests which speed up our tests by only loading a “slice” of the application context.",[651,170330,170331],{},"In this video, you will learn all about the Spring Boot @JsonTest Annotation. This annotation allows you to write a slice test for testing your object's serialization and deserialization. In the past, I might have just tested this through the web layer manually but this gives you a test that can be verified through CI/CD that everything is working as expected.",[5988,170333],{"id":170334},"AiiprfLqriY",[4542,170336,89065],{"id":170337},"springone-tour",[651,170339,170340,170341,170344],{},"We are so happy to announce that the ",[812,170342,89065],{"href":89063,"rel":170343},[816]," is back, in person, and coming to a city near you. Our first stop of the tour will happen on April 26-27 in Chicago. I just finished booking my first trip through VMware and I couldn’t be more excited to connect with the Java & Spring community.",[651,170346,170347,170348,170351],{},"I am presenting on Getting Started with ",[812,170349,86347],{"href":86345,"rel":170350},[816],". If you want to learn the basics of GraphQL and how (and more importantly when) to use it in your next Spring application you should check out my session.",[651,170353,170354],{},[660,170355],{"alt":170356,"src":170357},"spring-one-tour.png","/images/newsletter/2022/03/21/spring-one-tour.png",[4542,170359,157574],{"id":157573},[5909,170361,164959],{"id":69848},[5316,170363,170364,170371,170378],{},[5332,170365,170366],{},[812,170367,170370],{"href":170368,"rel":170369},"https://blogs.oracle.com/developers/post/brain-to-the-cloud-part-i-project-intro-and-architectural-overview",[816],"Brain to the Cloud - Part I - Project Intro and Architectural Overview",[5332,170372,170373],{},[812,170374,170377],{"href":170375,"rel":170376},"https://blogs.oracle.com/developers/post/brain-to-the-cloud-part-ii-how-i-uploaded-my-brain-to-the-cloud",[816],"Brain to the Cloud - Part II - How I Uploaded My Brain to the Cloud",[5332,170379,170380],{},[812,170381,170384],{"href":170382,"rel":170383},"https://blogs.oracle.com/developers/post/brain-to-the-cloud-part-iii-examining-the-relationship-between-brain-activity-and-video-game-performance",[816],"Brain to the Cloud - Part III - Examining the Relationship Between Brain Activity and Video Game Performance",[5909,170386,164971],{"id":157591},[5316,170388,170389,170396],{},[5332,170390,170391],{},[812,170392,170395],{"href":170393,"rel":170394},"https://www.youtube.com/watch?v=mukr2Q_zBm4",[816],"Migrating from javax to Jakarta namespace",[5332,170397,170398],{},[812,170399,170402],{"href":170400,"rel":170401},"https://www.youtube.com/watch?v=q9pYsss8huk",[816],"ϟ Enlightning: How Do You Add Persistent Storage to Your Kubernetes Application?",[5909,170404,164983],{"id":39439},[5316,170406,170407,170414],{},[5332,170408,170409],{},[812,170410,170413],{"href":170411,"rel":170412},"https://bootifulpodcast.fm/#/episodes/515b6730-e7b2-42aa-a569-45d1b4cf2cb7",[816],"Microsoft Azure Developer Advocate Mark Heckler",[5332,170415,170416],{},[812,170417,170420],{"href":170418,"rel":170419},"https://bootifulpodcast.fm/#/episodes/3d24a6ae-2971-45e5-acd7-cbcc789e3587",[816],"Elastic's Felix Barnsteiner on APM for Spring Developers",[5909,170422,164995],{"id":133422},[5316,170424,170425],{},[5332,170426,170427],{},[812,170428,170431],{"href":170429,"rel":170430},"https://go.dev/doc/go1.18",[816],"Go 1.18 Release Notes",[5909,170433,170434],{"id":79608},"👨🏼💻 Conferences",[5316,170436,170437],{},[5332,170438,170439],{},[812,170440,170443],{"href":170441,"rel":170442},"https://developer.oracle.com/developer-live/java-innovations-mar-2022",[816],"Oracle Developer Live",[4542,170445,157704],{"id":157703},[651,170447,166267,170448,166271],{},[812,170449,41499],{"href":44086,"rel":170450},[816],[651,170452,41105,170453,69920,170455,170457,170459,170461],{},[41107,170454],{},[41107,170456],{},[812,170458,161560],{"href":161111},[41107,170460],{},[812,170462,53869],{"href":82688,"rel":170463},[816],{"title":674,"searchDepth":790,"depth":790,"links":170465},[170466,170467,170468,170469,170476],{"id":170311,"depth":790,"text":170312},{"id":170324,"depth":790,"text":170325},{"id":170337,"depth":790,"text":89065},{"id":157573,"depth":790,"text":157574,"children":170470},[170471,170472,170473,170474,170475],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":79608,"depth":892,"text":170434},{"id":157703,"depth":790,"text":157704},"Happy Monday friends! In last week's newsletter, I told you how excited I was to “Spring Forward” and move our clocks 1 hr ahead. It’s getting warmer here in Ohio and staying light out longer which I absolutely love. I was excited to read last week that the US Senate approved a bill that would make daylight savings permanent. It is now headed to the desk of the President where It should be signed and we can all be done with this nonsense.",{"slug":170479,"date":170480},"spring-one-tour-is-back","2022-03-21T07:45:00.000Z","/newsletter/2022/03/21/spring-one-tour-is-back",{"title":170294,"description":170477},"newsletter/2022/03/21/spring-one-tour-is-back","ysZXyMf6D8S4lTlnWAI9e7lcgUVD1A3PrvnBnZs6jT0",{"id":170486,"title":170487,"body":170488,"description":170701,"extension":793,"meta":170702,"navigation":797,"path":170705,"seo":170706,"stem":170707,"__hash__":170708},"content/newsletter/2022/03/28/spring-data-notion-udemy.md","Spring Data, Notion & Udemy Course Updates",{"type":648,"value":170489,"toc":170686},[170490,170496,170499,170502,170510,170514,170520,170523,170526,170530,170533,170536,170540,170543,170546,170548,170551,170554,170557,170560,170563,170566,170568,170570,170595,170597,170619,170621,170637,170639,170647,170649,170658,170660,170666,170668,170673],[651,170491,170492,170493,664],{},"Happy Monday, friends! I hope you all had a wonderful weekend and you’re ready to take on a new week. I had a productive week last week where I hosted Tanzu Tuesday, released 2 videos on YouTube, and started working on my presentation for the upcoming ",[812,170494,89065],{"href":89063,"rel":170495},[816],[651,170497,170498],{},"With my new position, I was finally able to create videos on a consistent basis. With consistency, I am seeing an increase in the number of subscribers and watch hours. This is great because that is why I started my YouTube channel, to help people.",[651,170500,170501],{},"With this consistency, I am beginning to be a little bit more critical of my own work. I look around my office and I have all of this amazing equipment and all I can think of is everything that I want to upgrade.",[651,170503,170504,170505,664],{},"I think this is a natural progression for any content creator. We always look to the creators we aspire to be like and ask “What can I do to produce a video as they do?”. I say this and realize there are probably 1 or 2 YouTubers out there that look up to me. I’m saying this as a reminder to myself and everyone out there... ",[2939,170506,170507],{},[7300,170508,170509],{},"Enjoy the journey and don’t get caught up on the destination",[4542,170511,170513],{"id":170512},"tanzu-tuesday-persistence-with-spring-data","Tanzu Tuesday: Persistence with Spring Data",[651,170515,170516,170517,664],{},"I had the opportunity to host Tanzu Tuesday this week. If you’re aware we have a ton of free content including live stream shows and you can find all of this over at the ",[812,170518,97002],{"href":168725,"rel":170519},[816],[651,170521,170522],{},"In this episode, I sat down with my cohost DaShaun and our guest Christoph Strobl. Christoph is on the Spring Data team and talked to us about what Spring Data is and what’s coming next. I selfishly love being a host on these shows because I always learn something and this week was no different. I learned that you can create Data projections using DTOs & Interfaces and the resulting SQL will only select the properties you define which can lead to increased performance.",[5988,170524],{"id":170525},"gEYdSUa4fX8",[4542,170527,170529],{"id":170528},"spring-data-list-crud-repository","Spring Data List Crud Repository",[651,170531,170532],{},"Before we interviewed Christoph I had a chance to put together a video on a new feature coming in Spring Data 3.0. In the current version of Spring Data when you want to retrieve a collection of things the Crud Repository will return an Iterable. In most cases, we are used to working with a List and the List Crud Repository addresses that.",[5988,170534],{"id":170535},"lDbE0uYlYgk",[4542,170537,170539],{"id":170538},"spring-boot-notion-api","Spring Boot + Notion API",[651,170541,170542],{},"If you aren’t new around here you know that I am a huge fan of Notion. In fact, I’m writing this very newsletter in Notion and I use it for so many things in my personal and professional life. In this video, I will show you a Notion database that I created to manage my upcoming talks. We will then use Spring Boot to talk to the Notion API to retrieve those rows. If you’re a fan of Spring and Notion you’re going to enjoy this one!",[5988,170544],{"id":170545},"4yHYrQ7_gKM",[4542,170547,21973],{"id":21972},[651,170549,170550],{},"I spent some time coming up with a plan for my current courses and any new courses I want to create going forward. First off if you didn’t know I used to self host my courses on Teachable and you could purchase them through my website.",[651,170552,170553],{},"I turned that off last year because I didn’t want to have to maintain courses in 2 different places and honestly I wasn’t selling anything there. You need to have a large audience or a good marketing strategy to sell on your own platform and I don’t have either.",[651,170555,170556],{},"All of my courses on Udemy and I went through and cleaned some things up there. First off I unpublished a course I did with my friend John Thompson titled “Angular for Java Developers”. This course has fallen way behind and I just don’t have the time or the knowledge to bring it up to date.",[651,170558,170559],{},"I also sent out some emails to anyone in my Spring or Groovy courses. If you are in any of my courses, check your email. I was basically asking for suggestions on how I could update current courses without completing overhauling them.",[651,170561,170562],{},"I'm throwing out the idea of releasing a bunch of shorter (1 hr), more focused courses instead of one new mega course. The analytics show that students rarely go through a 14-hour course. Just like you, I purchase a course to learn about one or two things.",[651,170564,170565],{},"I think this will help with a couple of things. First, these shorter more focused courses will be easier to update in the future. It's very hard to keep a course that spans 14 hours in length up to date with the latest and greatest features. Second, this will give students a targeted curriculum to learn those one or two concepts in detail. I think this is a win-win for everyone but I would like to hear what you think.",[4542,170567,157574],{"id":157573},[5909,170569,164959],{"id":69848},[5316,170571,170572,170579],{},[5332,170573,170574],{},[812,170575,170578],{"href":170576,"rel":170577},"https://rieckpil.de/java-development-on-an-apple-m1-a-one-year-review/",[816],"Java Development on an Apple M1 – A One Year Review",[5332,170580,170581,170586],{},[812,170582,170585],{"href":170583,"rel":170584},"https://leaddev.com/team/three-things-leaders-owe-their-teams",[816],"The 3 things leaders owe their teams",[5316,170587,170588],{},[5332,170589,170590],{},[812,170591,170594],{"href":170592,"rel":170593},"https://hacks.mozilla.org/2022/03/a-new-year-a-new-mdn/",[816],"A new year, a new MDN",[5909,170596,164971],{"id":157591},[5316,170598,170599,170605,170612],{},[5332,170600,170601],{},[812,170602,170443],{"href":170603,"rel":170604},"https://developer.oracle.com/developer-live/java-innovations-mar-2022/",[816],[5332,170606,170607],{},[812,170608,170611],{"href":170609,"rel":170610},"https://www.youtube.com/watch?v=ThBw3WBTw9Q",[816],"Tomcat in IntelliJ IDEA",[5332,170613,170614],{},[812,170615,170618],{"href":170616,"rel":170617},"https://www.youtube.com/watch?v=kfLgJ61qg6Y",[816],"Getting started with Java Project Management for Visual Studio Code",[5909,170620,164983],{"id":39439},[5316,170622,170623,170630],{},[5332,170624,170625],{},[812,170626,170629],{"href":170627,"rel":170628},"https://www.youtube.com/watch?v=SFcAdJNGDOw",[816],"How Spring Changed Java Application Development | Josh Long",[5332,170631,170632],{},[812,170633,170636],{"href":170634,"rel":170635},"https://inside.java/2022/03/22/podcast-023/",[816],"Inside Java: Episode 23 “Java 18 is Here!”",[5909,170638,164995],{"id":133422},[5316,170640,170641],{},[5332,170642,170643],{},[812,170644,170646],{"href":169819,"rel":170645},[816],"OpenJDK JDK 18 General-Availability Release",[5909,170648,170434],{"id":79608},[5316,170650,170651],{},[5332,170652,170653],{},[812,170654,170657],{"href":170655,"rel":170656},"https://www.oracle.com/cloudworld/javaone/",[816],"JavaOne is back",[5909,170659,165430],{"id":165429},[651,170661,170662],{},[812,170663,170664],{"href":170664,"rel":170665},"https://twitter.com/therealdanvega/status/1507397722361536516",[816],[4542,170667,157704],{"id":157703},[651,170669,166267,170670,166271],{},[812,170671,41499],{"href":44086,"rel":170672},[816],[651,170674,41105,170675,69920,170677,170679,170681,170683],{},[41107,170676],{},[41107,170678],{},[812,170680,161560],{"href":161111},[41107,170682],{},[812,170684,53869],{"href":82688,"rel":170685},[816],{"title":674,"searchDepth":790,"depth":790,"links":170687},[170688,170689,170690,170691,170692,170700],{"id":170512,"depth":790,"text":170513},{"id":170528,"depth":790,"text":170529},{"id":170538,"depth":790,"text":170539},{"id":21972,"depth":790,"text":21973},{"id":157573,"depth":790,"text":157574,"children":170693},[170694,170695,170696,170697,170698,170699],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":79608,"depth":892,"text":170434},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},"Happy Monday, friends! I hope you all had a wonderful weekend and you’re ready to take on a new week. I had a productive week last week where I hosted Tanzu Tuesday, released 2 videos on YouTube, and started working on my presentation for the upcoming SpringOne Tour.",{"slug":170703,"date":170704},"spring-data-notion-udemy","2022-03-28T09:00:00.000Z","/newsletter/2022/03/28/spring-data-notion-udemy",{"title":170487,"description":170701},"newsletter/2022/03/28/spring-data-notion-udemy","8kmKIHis50ACmUwoXiRaiMvwjsBF0lYFBbyagV_ekV0",{"id":170710,"title":170711,"body":170712,"description":170716,"extension":793,"meta":170935,"navigation":797,"path":170938,"seo":170939,"stem":170940,"__hash__":170941},"content/newsletter/2022/04/11/office-hours-code.md","Office Hours, Code & My Discord Server",{"type":648,"value":170713,"toc":170919},[170714,170717,170724,170726,170735,170738,170742,170745,170748,170752,170755,170758,170761,170764,170769,170777,170782,170784,170786,170809,170811,170827,170829,170852,170854,170863,170865,170868,170877,170879,170888,170890,170893,170899,170901,170906],[651,170715,170716],{},"Happy Monday, friends! It’s crazy to think that we are heading into the middle of April already. Here in the Vega household it’s been a rough couple of weeks. We have all been sick with a number of bugs going around but we are hoping this is the end of it. This is the main reason you haven’t seen any videos from me lately and I’m hoping to get back to that in a few weeks.",[651,170718,170719,170720,664],{},"This will be my last newsletter for a few weeks. Next week the family and I are heading to Hilton Head for some R&R and the following week I will be in Chicago for the ",[812,170721,89065],{"href":170722,"rel":170723},"https://tanzu.vmware.com/developer/springone-tour/2022/chicago/",[816],[4542,170725,170229],{"id":170228},[651,170727,170728,170729,170734],{},"Below is a recording from our latest office hours live stream. This is a chance for you to find out what is the latest news in the world of Spring, learn something new and ask any questions you might have. Up until this point DaShaun and I have just been testing this format out. We are happy to announce that this show is going pro and it will be added to the ",[812,170730,170733],{"href":170731,"rel":170732},"https://tanzu.vmware.com/developer/tv/",[816],"Tanzu TV portfolio"," soon.",[5988,170736],{"id":170737},"thqCEMyIBCU",[4542,170739,170741],{"id":170740},"tanzu-tv-code-spring-for-graphql","Tanzu TV - Code: Spring for GraphQL",[651,170743,170744],{},"In this episode of Code I’m talking all about Spring for GraphQL. I am working on my presentation for the SpringOne Tour and I began working on some of my demo’s in this live stream.",[5988,170746],{"id":170747},"6WfxWfIwoAU",[4542,170749,170751],{"id":170750},"discord-server","Discord Server",[651,170753,170754],{},"If you didn’t know I started a community a discord a couple years ago. I honestly haven’t been giving it the attention it needs. A week or two ago I decided to shut down the server without much warning. I made this decision because I need to make sure I am spending my time wisely. This was another medium that wasn’t providing the value I envisioned when I started it.",[651,170756,170757],{},"If I am being completely honest, I’m just not a big fan of Discord. This is both from a user and server admin point of view. There are a lot of things I just don’t like about it but I don’t want to get into here.",[4542,170759,170760],{"id":83146},"Thank You 🙏",[651,170762,170763],{},"A friend sent me the message below out of nowhere one day. Just another reminder that it is really easy to say something positive to someone. It takes only a couple of minutes but it could really make their day and put them in a better mood. Thank you friend for the kind words 🤩",[1004,170765,170766],{},[651,170767,170768],{},"The past couple days, I have been fixed on the concept of people wanting to be the noun without doing the verb. I thought this blog post was awesome and communicates it very well.",[1004,170770,170771],{},[651,170772,170773],{},[812,170774,170775],{"href":170775,"rel":170776},"https://isaacjeffries.com/blog/2020/4/16/the-noun-and-the-verb",[816],[1004,170778,170779],{},[651,170780,170781],{},"I want to know, I think you are doing an amazing job of being the verb by producing awesome content. Keep up the great work.",[4542,170783,157574],{"id":157573},[5909,170785,164959],{"id":69848},[5316,170787,170788,170795,170802],{},[5332,170789,170790],{},[812,170791,170794],{"href":170792,"rel":170793},"https://www.infoq.com/articles/native-java-graalvm/",[816],"Revolutionizing Java with GraalVM Native ImageLIKE1PRINT",[5332,170796,170797],{},[812,170798,170801],{"href":170799,"rel":170800},"https://www.infoq.com/articles/architecture-trends-2022/",[816],"Software Architecture and Design InfoQ Trends Report—April 2022",[5332,170803,170804],{},[812,170805,170808],{"href":170806,"rel":170807},"http://www.mastertheboss.com/java-ee/jakarta-ee/whats-new-in-jakarta-ee-10",[816],"What’s new in Jakarta EE 10",[5909,170810,164971],{"id":157591},[5316,170812,170813,170820],{},[5332,170814,170815],{},[812,170816,170819],{"href":170817,"rel":170818},"https://www.youtube.com/watch?v=lrtnMQuxUvc&t=3245s",[816],"Must know tools in the Java testing ecosystem with Ixchel Ruiz",[5332,170821,170822],{},[812,170823,170826],{"href":170824,"rel":170825},"https://www.youtube.com/watch?v=PzFp7_6heaY&t=2654s",[816],"Cloud Native Crew - Pilot",[5909,170828,164983],{"id":39439},[5316,170830,170831,170838,170845],{},[5332,170832,170833],{},[812,170834,170837],{"href":170835,"rel":170836},"https://www.youtube.com/watch?v=lRzKuDVJZ_Y",[816],"Between Chair and Keyboard with Caitlin Mahoney",[5332,170839,170840],{},[812,170841,170844],{"href":170842,"rel":170843},"https://bootifulpodcast.fm/#/episodes/eefa8329-35b2-44e7-b7f4-2044565f20d2",[816],"Craig McLuckie, Kubernetes cofounder and vice president of R&D at VMware",[5332,170846,170847],{},[812,170848,170851],{"href":170849,"rel":170850},"https://bootifulpodcast.fm/#/episodes/082ae3f2-aa90-4cc0-bc3e-fe95f49c95de",[816],"GraphQL Java founder Andi Marek",[5909,170853,164995],{"id":133422},[5316,170855,170856],{},[5332,170857,170858],{},[812,170859,170862],{"href":170860,"rel":170861},"https://openjdk.java.net/jeps/425",[816],"JEP 425: Virtual Threads (Preview)",[5909,170864,165005],{"id":39340},[651,170866,170867],{},"We are having a tough time at home with our youngest daughter. On top of everyone being sick lately with whatever bug is going around she will just not sleep through though the night. A friend recommended this book and this point I am willing to try anything so send your suggestions in please.",[5316,170869,170870],{},[5332,170871,170872],{},[812,170873,170876],{"href":170874,"rel":170875},"https://amzn.to/3NUrxQN",[816],"Touchpoints - Birth to Three",[5909,170878,167862],{"id":160019},[5316,170880,170881],{},[5332,170882,170883],{},[812,170884,170887],{"href":170885,"rel":170886},"https://www.thesevletter.com/p/announcing-the-sev-letter",[816],"Announcing The Sev Letter",[5909,170889,165430],{"id":165429},[651,170891,170892],{},"Twitter is working on an edit button!",[651,170894,170895],{},[812,170896,170897],{"href":170897,"rel":170898},"https://twitter.com/TwitterComms/status/1511456466233815041",[816],[4542,170900,157704],{"id":157703},[651,170902,166267,170903,166271],{},[812,170904,41499],{"href":44086,"rel":170905},[816],[651,170907,41105,170908,69920,170910,170912,170914,170916],{},[41107,170909],{},[41107,170911],{},[812,170913,161560],{"href":161111},[41107,170915],{},[812,170917,53869],{"href":82688,"rel":170918},[816],{"title":674,"searchDepth":790,"depth":790,"links":170920},[170921,170922,170923,170924,170925,170934],{"id":170228,"depth":790,"text":170229},{"id":170740,"depth":790,"text":170741},{"id":170750,"depth":790,"text":170751},{"id":83146,"depth":790,"text":170760},{"id":157573,"depth":790,"text":157574,"children":170926},[170927,170928,170929,170930,170931,170932,170933],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":160019,"depth":892,"text":167862},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":170936,"date":170937},"office-hours-code","2022-04-11T09:30:00.000Z","/newsletter/2022/04/11/office-hours-code",{"title":170711,"description":170716},"newsletter/2022/04/11/office-hours-code","Wdiymp2CTkFGxZd88FiGdhHg5LXyJ8M8l6tb33QcDCI",{"id":170943,"title":170944,"body":170945,"description":170949,"extension":793,"meta":171175,"navigation":797,"path":171178,"seo":171179,"stem":171180,"__hash__":171181},"content/newsletter/2022/05/02/vacation-conferences.md","Vacation, Conference Season and a YouTube Milestone",{"type":648,"value":170946,"toc":171156},[170947,170950,170956,170962,170968,170971,170974,170977,170981,170984,170986,170993,170996,171000,171007,171010,171013,171015,171018,171021,171030,171034,171041,171047,171051,171054,171056,171058,171081,171083,171092,171094,171114,171116,171125,171127,171129,171135,171137,171143],[651,170948,170949],{},"Happy Monday friends! It’s been a while since we last talked and I have a lot to share with you. A couple of weeks ago my family and I took a much-needed vacation to Hilton Head, SC. We had such a great time at the beach, riding bikes and eating so much good food.",[651,170951,170952],{},[660,170953],{"alt":170954,"src":170955},"Hilton Head Sunrise","/images/newsletter/2022/05/02/hilton_head_01.png",[651,170957,170958],{},[660,170959],{"alt":170960,"src":170961},"Hilton Head Family Water","/images/newsletter/2022/05/02/hilton_head_02.png",[651,170963,170964],{},[660,170965],{"alt":170966,"src":170967},"Hilton Head Dockside","/images/newsletter/2022/05/02/hilton_head_03.png",[651,170969,170970],{},"We returned on a Saturday and on Sunday I began packing for SpringOne Tour in Chicago. That night I decided to take a Covid test because I had a bit of a cough. I tested positive and didn’t believe it so I tested again with the same result. I didn’t believe it because even as of recent I have been hearing of others getting pretty sick and I wasn’t sick.",[651,170972,170973],{},"I was really bummed out because this meant that I had to cancel my trip to Chicago. This was the first time I was going to get to hang out with coworkers in person. This also meant that I wouldn’t be giving my talk on Spring for GraphQL which I worked really hard on.",[651,170975,170976],{},"At the end of the day, it’s just the first conference of many this year. I spent last week catching up on some things at work and after that and a vacation, I feel ready to go for conference season!",[4542,170978,170980],{"id":170979},"conference-talks","Conference Talks",[651,170982,170983],{},"Speaking of conferences I received notice that I would be speaking at 2 more conferences this summer while I was on vacation. When you receive as many declines as I do you tend to celebrate the wins and I am so pumped for both of these. 🥳",[5909,170985,97022],{"id":160364},[651,170987,170988,170989,170992],{},"If you know me you know that I am a huge fan of Vue and the community. I have submitted a number of talks over the years to Vue-related conferences with no luck. I was so excited to find out that I will be speaking at ",[812,170990,97022],{"href":97020,"rel":170991},[816]," which is located in Ft. Lauderdale FL! I will be speaking on Nuxt 3 and more specifically what’s coming in Nuxt 3!",[651,170994,170995],{},"I think what I am looking forward to most is meeting so many people from the Vue community that I have talked to over the years and never met. If you’re heading to this conference please let me know so we can catch up.",[5909,170997,170999],{"id":170998},"code-on-the-beach","Code on the Beach",[651,171001,171002,171003,171006],{},"A few days later I got notice that I had been accepted to ",[812,171004,170999],{"href":97041,"rel":171005},[816],". This conference also takes place in FL but this time we move up the coast to Atlantic Beach. I have heard some great things about this conference and I was excited to hear I will be joined by a couple of coworkers. In this talk, you will learn how to build Full Stack Java applications with Spring Boot.",[5909,171008,97036],{"id":171009},"kansas-city-developer-conference-kcdc",[651,171011,171012],{},"Late on Friday I found out that I was accepted to KCDC. This will be my first time speaking at this conference and I am really looking forward to it. I will be presenting with my friend and coworker Nate Schutta. In this talk we will be presenting on Spring Recipes: A Collection of Common Sense Solutions.",[4542,171014,83109],{"id":83108},[651,171016,171017],{},"I celebrated 3 months last week at VMware! I can’t remember the last time I was this happy at work. The company I work for is amazing on so many levels. I get to go to work every single day and do what I love with a really great team.",[651,171019,171020],{},"VMware attracts some of the most talented people in our industry and you can see it with every interaction company wide. The benefits are amazing and I appreciate the freedom to maintains work/life balance. A lot of companies will preach this but it’s often just talk, not here.",[651,171022,171023,171024,171029],{},"To top it all off VMware was ranked ",[812,171025,171028],{"href":171026,"rel":171027},"https://www.forbes.com/lists/best-employers-diversity",[816],"#2 by Forbes"," on America’s best employers for diversity last week.",[4542,171031,171033],{"id":171032},"youtube-15000-subscriber-milestone","YouTube 15,000 Subscriber Milestone",[651,171035,171036,171037,171040],{},"While I was on vacation I crossed 15,000 subscribers on ",[812,171038,15432],{"href":165299,"rel":171039},[816],"! This is amazing and If I was home I probably would have created a video to talk through this amazing milestone. Next up is 20k and I will try and put something together for that one.",[651,171042,171043],{},[660,171044],{"alt":171045,"src":171046},"YouTube 15k","/images/newsletter/2022/05/02/youtube_15k.png",[4542,171048,171050],{"id":171049},"this-newsletter","This newsletter",[651,171052,171053],{},"I have been trying to figure out what to do with this newsletter. It might not seem like it but it takes a lot of effort and I’m not sure anyone would miss it. I pay attention to the analytics and I can tell that some of you are reading this but I just feel like my efforts can be better spent elsewhere. For now I will keep this around but I am going to move it to an every other week cadence. If you have any feedback for me on this newsletter please feel free to reach out.",[4542,171055,157574],{"id":157573},[5909,171057,164959],{"id":69848},[5316,171059,171060,171067,171074],{},[5332,171061,171062],{},[812,171063,171066],{"href":171064,"rel":171065},"https://blog.jetbrains.com/blog/2022/04/28/jetbrains_partners_with_gitpod/",[816],"Remote Development With JetBrains Gateway and Gitpod",[5332,171068,171069],{},[812,171070,171073],{"href":171071,"rel":171072},"https://www.infoq.com/news/2022/04/red-hat-releases-hibernate-6/",[816],"Hibernate ORM 6.0 Delivers Improved Performance",[5332,171075,171076],{},[812,171077,171080],{"href":171078,"rel":171079},"https://medium.com/graalvm/graalvm-22-1-developer-experience-improvements-apple-silicon-builds-and-more-b7ac9a0f6066",[816],"GraalVM 22.1: Developer experience improvements, Apple Silicon builds, and more",[5909,171082,164983],{"id":39439},[5316,171084,171085],{},[5332,171086,171087],{},[812,171088,171091],{"href":171089,"rel":171090},"https://bootifulpodcast.fm/#/episodes/28280dc4-c9ca-4229-8075-b4f63a4cb6c4",[816],"Cloud guru Tiffany Jernigan",[5909,171093,164995],{"id":133422},[5316,171095,171096,171102,171108],{},[5332,171097,171098],{},[812,171099,171100],{"href":171100,"rel":171101},"https://openjdk.java.net/jeps/8273943",[816],[5332,171103,171104],{},[812,171105,171106],{"href":171106,"rel":171107},"https://nuxtjs.org/announcements/nuxt3-rc/",[816],[5332,171109,171110],{},[812,171111,171113],{"href":170860,"rel":171112},[816],"Project Loom preview set for Java 19",[5909,171115,167862],{"id":160019},[5316,171117,171118],{},[5332,171119,171120],{},[812,171121,171124],{"href":171122,"rel":171123},"https://tanzu.vmware.com/content/josh-blog/this-month-in-spring-april-2022",[816],"This month in Spring - April 2022",[5909,171126,165017],{"id":165016},[5909,171128,165430],{"id":165429},[651,171130,171131],{},[812,171132,171133],{"href":171133,"rel":171134},"https://twitter.com/juliendubois/status/1518671023415177217",[816],[4542,171136,157704],{"id":157703},[651,171138,171139,171140,166271],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of Coffee & Code and I will see you in 2 weeks. If you have any links you would like me to include please ",[812,171141,41499],{"href":44086,"rel":171142},[816],[651,171144,41105,171145,69920,171147,171149,171151,171153],{},[41107,171146],{},[41107,171148],{},[812,171150,161560],{"href":161111},[41107,171152],{},[812,171154,53869],{"href":82688,"rel":171155},[816],{"title":674,"searchDepth":790,"depth":790,"links":171157},[171158,171163,171164,171165,171166,171174],{"id":170979,"depth":790,"text":170980,"children":171159},[171160,171161,171162],{"id":160364,"depth":892,"text":97022},{"id":170998,"depth":892,"text":170999},{"id":171009,"depth":892,"text":97036},{"id":83108,"depth":790,"text":83109},{"id":171032,"depth":790,"text":171033},{"id":171049,"depth":790,"text":171050},{"id":157573,"depth":790,"text":157574,"children":171167},[171168,171169,171170,171171,171172,171173],{"id":69848,"depth":892,"text":164959},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":160019,"depth":892,"text":167862},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":171176,"date":171177},"vacation-conferences","2022-05-02T07:30:00.000Z","/newsletter/2022/05/02/vacation-conferences",{"title":170944,"description":170949},"newsletter/2022/05/02/vacation-conferences","hiEExTJbuo2KMYyQy-MtZmpQp2yTMciMxYDTx8WWDbY",{"id":171183,"title":171184,"body":171185,"description":171189,"extension":793,"meta":171479,"navigation":797,"path":171481,"seo":171482,"stem":171483,"__hash__":171484},"content/newsletter/2022/05/16/website-updates.md","Website Updates, New Content, Live Coding & SpringOne",{"type":648,"value":171186,"toc":171457},[171187,171190,171193,171195,171198,171201,171204,171207,171210,171230,171233,171237,171241,171248,171251,171253,171258,171261,171263,171266,171270,171273,171276,171279,171281,171284,171295,171298,171306,171308,171311,171315,171324,171326,171328,171358,171360,171390,171392,171408,171410,171426,171429,171437,171439,171444],[651,171188,171189],{},"Happy Monday and welcome to the new bi-weekly newsletter. I decided to move this newsletter to every other Monday for a couple of reasons. First, it’s a lot of work and stress to produce this newsletter each week on top of everything else I am doing. Second, I just don’t think I have enough going on to warrant a newsletter every single week. When I get on the level of Ken Kousen (Hi, Ken 👋🏻) I will consider moving back to weekly. I’m going to try this for now but I would love your feedback.",[651,171191,171192],{},"This week I want to tell you about some long-overdue changes I made to the website, I did some live coding, wrote some blog posts, and released a few videos.",[4542,171194,161336],{"id":161335},[651,171196,171197],{},"Over the past couple of weeks, I had a chance to make some long-overdue updates to the website. I’m not sure when this happened but all of my inline code fences were displaying as blocks. I’m a little bit worried that nobody told me about this. I know the analytics tell me that I am getting a little under 50k visitors a month and not one of you noticed this 😳",[651,171199,171200],{},"This was an issue with the Gridsome plugin I was using and found out that I could turn of syntax highlighting for inline code fences. I did that and updated the styles to help it stand out for both light and dark modes. This should now be corrected for both my blog posts and newsletter archives on the web.",[651,171202,171203],{},"For as long as I can remember I have wanted to start updating some of my more popular older posts. Before I could do this I had to update the blog header. I was displaying a date there but that was the published date.",[651,171205,171206],{},"I now display the words “Published On” before this date so that viewers know when the article was published. If I go back and update the article you will now see “Updated On” and a 2nd date displayed. I like this information as a consumer of other blogs so I hope you will appreciate this.",[651,171208,171209],{},"With this change in place I went back and updated a few blog posts:",[5316,171211,171212,171218,171225],{},[5332,171213,171214],{},[812,171215,213],{"href":171216,"rel":171217},"https://www.danvega.dev/blog/2017/04/07/spring-boot-command-line-runner/",[816],[5332,171219,171220],{},[812,171221,171224],{"href":171222,"rel":171223},"https://www.danvega.dev/blog/2017/07/05/read-json-data-spring-boot-write-database/",[816],"How to read JSON Data in Spring Boot and Write to a database",[5332,171226,171227],{},[812,171228,82645],{"href":165881,"rel":171229},[816],[651,171231,171232],{},"I will continue to do this for older blog posts when I can. It’s nice to bring them up to current versions of Spring Boot as well as add what I have learned over the years.",[4542,171234,171236],{"id":171235},"new-content","New Content",[5909,171238,171240],{"id":171239},"value-annotation-in-spring-boot","@Value Annotation in Spring Boot",[651,171242,171243,171244],{},"Blog Post: ",[812,171245,171246],{"href":171246,"rel":171247},"https://www.danvega.dev/blog/2022/05/11/spring-boot-value-annotation/",[816],[5988,171249],{"id":171250},"vLSyFktOm4g",[5909,171252,390],{"id":85122},[651,171254,171243,171255],{},[812,171256,87164],{"href":87164,"rel":171257},[816],[5988,171259],{"id":171260},"oq-c3D67WqM",[4542,171262,60814],{"id":60813},[651,171264,171265],{},"This past week I was able to get some more live coding under my belt. I think like most people there is a bit of anxiety when it comes to coding in public. With experience I am getting much more comfortable with it and even enjoying it at times!",[5909,171267,171269],{"id":171268},"tanzu-tv-code","Tanzu TV - Code",[651,171271,171272],{},"This week I hoped on one of our tv shows called Code. This is a chance for us advocates to jump on and build something in public. We don’t have a script, guest or slides. Just me, you and some code. In this weeks session I built out some code I would use for some blog post and YouTube videos.",[651,171274,171275],{},"The first part was building out Spring Data JPA repositories. Once I had that in place I walked through how to do pagination in GraphQL.",[5988,171277],{"id":171278},"67GtZRgE0h0",[5909,171280,97345],{"id":97344},[651,171282,171283],{},"If you have been following along I have been running a little experiment called “Spring Office Hours” with my friend and coworker DaShaun. We were doing this on our own Twitch & YouTube channels and it was a chance to talk about what’s new in the world of Spring, demo off some code and answer your questions.",[651,171285,171286,171287,171290,171291,171294],{},"I’m happy to announce that ",[812,171288,97345],{"href":97000,"rel":171289},[816]," is out of beta and we have been added to the Tanzu TV lineup on the ",[812,171292,97002],{"href":168725,"rel":171293},[816],". This past week was episode 01 and while I started off with some audio issues, we ended strong.",[5988,171296],{"id":171297},"qOlgTi6bC5U",[651,171299,171300,171301,664],{},"If you want to join us live for Episode 2 you can view the ",[812,171302,171305],{"href":171303,"rel":171304},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0002/",[816],"episode page here",[4542,171307,82113],{"id":82579},[651,171309,171310],{},"This week I found out some amazing news about the upcoming SpringOne conference this December in San Francisco. I will be there in person and I will be doing something really cool that I can’t tell you about yet. I’m so excited about this and as soon as I can say something I will. With that said, I hope to see you all there!",[5909,171312,171314],{"id":171313},"call-for-papers","Call for Papers",[651,171316,171317,171318,171323],{},"Speaking of SpringOne our ",[812,171319,171322],{"href":171320,"rel":171321},"https://springone.io/2022/cfp",[816],"Call for Paper","s is open now and closes on June 20, 2022. If you’re interested in speaking please submit a talk.",[4542,171325,157574],{"id":157573},[5909,171327,164959],{"id":69848},[5316,171329,171330,171337,171344,171351],{},[5332,171331,171332],{},[812,171333,171336],{"href":171334,"rel":171335},"https://spring.io/blog/2022/05/02/ever-wanted-to-rewrite-a-query-in-spring-data-jpa",[816],"Ever wanted to rewrite a query in Spring Data JPA?",[5332,171338,171339],{},[812,171340,171343],{"href":171341,"rel":171342},"https://apurvsheth.medium.com/serverless-with-spring-boot-aws-lambda-bc76c1de2b12",[816],"Serverless With Spring Boot & AWS Lambda",[5332,171345,171346],{},[812,171347,171350],{"href":171348,"rel":171349},"https://blog.cloudflare.com/introducing-d1/",[816],"Cloudflare: Announcing D1: our first SQL database",[5332,171352,171353],{},[812,171354,171357],{"href":171355,"rel":171356},"https://www.docker.com/blog/the-magic-of-docker-desktop-is-now-available-on-linux",[816],"The Magic of Docker Desktop is Now Available on Linux",[5909,171359,164971],{"id":157591},[5316,171361,171362,171369,171376,171383],{},[5332,171363,171364],{},[812,171365,171368],{"href":171366,"rel":171367},"https://youtu.be/T9KItuTJe-8",[816],"Cloud Native Crew: Episode 3, with Ben Hale",[5332,171370,171371],{},[812,171372,171375],{"href":171373,"rel":171374},"https://www.youtube.com/watch?v=A1V71peRNn0",[816],"New in Spring Framework 6: Http interfaces & Declaritive Htttp Clients",[5332,171377,171378],{},[812,171379,171382],{"href":171380,"rel":171381},"https://www.youtube.com/watch?v=QuvS_VLbGko",[816],"Spring Boot Tutorial - Nice & Easy",[5332,171384,171385],{},[812,171386,171389],{"href":171387,"rel":171388},"https://www.youtube.com/watch?v=T9KItuTJe-8",[816],"Cloud Native Crew - with Ben Hale",[5909,171391,164983],{"id":39439},[5316,171393,171394,171401],{},[5332,171395,171396],{},[812,171397,171400],{"href":171398,"rel":171399},"https://bootifulpodcast.fm/#/episodes/c82fb7d3-2854-4b31-8d5e-f183e7b87643",[816],"Java Champion and Spring Katas legend Chandra Guntur",[5332,171402,171403],{},[812,171404,171407],{"href":171405,"rel":171406},"https://bootifulpodcast.fm/#/episodes/aa1abf06-ca95-426c-b200-f3669ab3839d",[816],"EasyMock contributor, Java Champion, and Java luminary Henri Tremblay",[5909,171409,164995],{"id":133422},[5316,171411,171412,171419],{},[5332,171413,171414],{},[812,171415,171418],{"href":171416,"rel":171417},"https://github.com/micrometer-metrics/micrometer/releases/tag/v1.9.0",[816],"Micrometer 1.9.0 Released",[5332,171420,171421],{},[812,171422,171425],{"href":171423,"rel":171424},"https://openjdk.java.net/jeps/427",[816],"JEP 427: Pattern Matching for switch (Third Preview)",[5909,171427,171428],{"id":79608},"👩💻 Conferences",[5316,171430,171431],{},[5332,171432,171433,1223],{},[812,171434,171436],{"href":157685,"rel":171435},[816],"JavaScript and Friends Conference Registration is now open",[4542,171438,157704],{"id":157703},[651,171440,166267,171441,166271],{},[812,171442,41499],{"href":44086,"rel":171443},[816],[651,171445,41105,171446,69920,171448,171450,171452,171454],{},[41107,171447],{},[41107,171449],{},[812,171451,161560],{"href":161111},[41107,171453],{},[812,171455,53869],{"href":82688,"rel":171456},[816],{"title":674,"searchDepth":790,"depth":790,"links":171458},[171459,171460,171464,171468,171471,171478],{"id":161335,"depth":790,"text":161336},{"id":171235,"depth":790,"text":171236,"children":171461},[171462,171463],{"id":171239,"depth":892,"text":171240},{"id":85122,"depth":892,"text":390},{"id":60813,"depth":790,"text":60814,"children":171465},[171466,171467],{"id":171268,"depth":892,"text":171269},{"id":97344,"depth":892,"text":97345},{"id":82579,"depth":790,"text":82113,"children":171469},[171470],{"id":171313,"depth":892,"text":171314},{"id":157573,"depth":790,"text":157574,"children":171472},[171473,171474,171475,171476,171477],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":79608,"depth":892,"text":171428},{"id":157703,"depth":790,"text":157704},{"slug":161335,"date":171480},"2022-05-16T07:30:00.000Z","/newsletter/2022/05/16/website-updates",{"title":171184,"description":171189},"newsletter/2022/05/16/website-updates","zCfgSecVsojLd3UENcHGPo7hDynpNX67QKb2hAzpji4",{"id":171486,"title":171487,"body":171488,"description":171492,"extension":793,"meta":171691,"navigation":797,"path":171694,"seo":171695,"stem":171696,"__hash__":171697},"content/newsletter/2022/05/31/happy-memorial-day.md","Happy Memorial Day, Spring for GraphQL and Office Hours",{"type":648,"value":171489,"toc":171676},[171490,171493,171496,171506,171508,171511,171517,171520,171522,171525,171528,171531,171534,171536,171538,171568,171570,171593,171595,171611,171613,171629,171631,171640,171642,171644,171653,171655,171657,171663],[651,171491,171492],{},"Good morning friends. I hope everyone in the US had a happy Memorial day weekend. I want to thank every one of you who has served or is currently serving. Your dedication and bravery to this country are inspiring and we are lucky to have you protecting us.",[651,171494,171495],{},"I only have a couple of things to share with you this week but the next edition of the newsletter should be a little bit more exciting.",[651,171497,171498,171499,23212,171502,171505],{},"I am hitting the road next week for ",[812,171500,97015],{"href":97013,"rel":171501},[816],[812,171503,97022],{"href":97020,"rel":171504},[816],". If you’re at either of those conferences please come say hi. If you want to see a list of events that I will be at this year you can check out the speaking page on my website.",[4542,171507,86347],{"id":86782},[651,171509,171510],{},"Spring for GraphQL 1.0 has been released! This is a huge milestone and the culmination of a really great collaboration between the Spring team and the GraphQL Java team. I put together an article on the release and a video tutorial walking you through the basics of how to get started with Spring for GraphQL.",[651,171512,171243,171513],{},[812,171514,171515],{"href":171515,"rel":171516},"https://www.danvega.dev/blog/2022/05/17/spring-for-graphql/",[816],[5988,171518],{"id":171519},"3PCNqXrU-2g",[4542,171521,97345],{"id":97344},[651,171523,171524],{},"DaShaun and I have been busy with our new live stream called Spring Office Hours. This is a chance to tell you what’s new in the Spring Community, show off something we are working on, and answer your questions. I think we have gotten into a good grove and I am excited about the future of this show. If you want you can check out some of our recent episodes below.",[5988,171526],{"id":171527},"HYszA0Za704",[5988,171529],{"id":171530},"rMu-QBEFG7U",[5988,171532],{"id":171533},"5_DE6r4oxEw",[4542,171535,157574],{"id":157573},[5909,171537,164959],{"id":69848},[5316,171539,171540,171547,171554,171561],{},[5332,171541,171542],{},[812,171543,171546],{"href":171544,"rel":171545},"https://medium.com/qe-unit/airbnbs-microservices-architecture-journey-to-quality-engineering-d5a490e6ba4f",[816],"Airbnb’s Microservices Architecture Journey To Quality Engineering",[5332,171548,171549],{},[812,171550,171553],{"href":171551,"rel":171552},"https://www.docker.com/blog/welcome-tilt-fixing-the-pains-of-microservice-development-for-kubernetes/",[816],"Tilt joins Docker",[5332,171555,171556],{},[812,171557,171560],{"href":171558,"rel":171559},"https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0",[816],"Preparing for Spring Boot 3.0",[5332,171562,171563],{},[812,171564,171567],{"href":171565,"rel":171566},"https://spring.io/blog/2022/05/19/spring-for-graphql-1-0-release",[816],"Spring for GraphQL 1.0 Released",[5909,171569,164971],{"id":157591},[5316,171571,171572,171579,171586],{},[5332,171573,171574],{},[812,171575,171578],{"href":171576,"rel":171577},"https://www.youtube.com/watch?v=DkZr7_c9ry8",[816],"Annotations In Java Tutorial - How To Create And Use Your Own Custom Annotations",[5332,171580,171581],{},[812,171582,171585],{"href":171583,"rel":171584},"https://www.youtube.com/watch?v=lCzcQ0Q5cSQ",[816],"Azure is the home for your Java applications, Part 1 | Azure Friday",[5332,171587,171588],{},[812,171589,171592],{"href":171590,"rel":171591},"https://www.youtube.com/watch?v=u0kWB9S_np0&t=1s",[816],"Getting Started with Java: The JBang way",[5909,171594,164983],{"id":39439},[5316,171596,171597,171604],{},[5332,171598,171599],{},[812,171600,171603],{"href":171601,"rel":171602},"https://podcasts.apple.com/us/podcast/podrocket-a-web-development-podcast-from-logrocket/id1539945251?i=1000561826077",[816],"Nuxt 3 with Daniel Roe",[5332,171605,171606],{},[812,171607,171610],{"href":171608,"rel":171609},"https://www.youtube.com/watch?v=jAjFBwCzgz0",[816],"Between Chair and Keyboard - Raju Gandhi",[5909,171612,164995],{"id":133422},[5316,171614,171615,171622],{},[5332,171616,171617],{},[812,171618,171621],{"href":171619,"rel":171620},"https://drawsql.app/",[816],"DrawSQL",[5332,171623,171624],{},[812,171625,171628],{"href":171626,"rel":171627},"https://github.com/Diizzayy/nuxt-graphql-client",[816],"Nuxt GraphQL Client",[5909,171630,165005],{"id":39340},[5316,171632,171633],{},[5332,171634,171635],{},[812,171636,171639],{"href":171637,"rel":171638},"https://leanpub.com/graphql-java/",[816],"GraphQL with Java and Spring",[5909,171641,165017],{"id":165016},[5909,171643,167862],{"id":160019},[5316,171645,171646],{},[5332,171647,171648],{},[812,171649,171652],{"href":171650,"rel":171651},"https://blog.jetbrains.com/idea/2022/05/java-annotated-monthly-may-2022/",[816],"Java Annotated Monthly - May 2022",[5909,171654,165430],{"id":165429},[4542,171656,157704],{"id":157703},[651,171658,171659,171660,166271],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of Coffee & Code and I will see you in the next one. If you have any links you would like me to include please ",[812,171661,41499],{"href":44086,"rel":171662},[816],[651,171664,41105,171665,69920,171667,171669,171671,171673],{},[41107,171666],{},[41107,171668],{},[812,171670,161560],{"href":161111},[41107,171672],{},[812,171674,53869],{"href":82688,"rel":171675},[816],{"title":674,"searchDepth":790,"depth":790,"links":171677},[171678,171679,171680,171690],{"id":86782,"depth":790,"text":86347},{"id":97344,"depth":790,"text":97345},{"id":157573,"depth":790,"text":157574,"children":171681},[171682,171683,171684,171685,171686,171687,171688,171689],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":160019,"depth":892,"text":167862},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":171692,"date":171693},"happy-memorial-day","2022-05-31T07:30:00.000Z","/newsletter/2022/05/31/happy-memorial-day",{"title":171487,"description":171492},"newsletter/2022/05/31/happy-memorial-day","MqNH8s5VMuVKsCWyqYO3Bcm7vLyw72BllXbujDkEy8I",{"id":171699,"title":171700,"body":171701,"description":171705,"extension":793,"meta":171972,"navigation":797,"path":171975,"seo":171976,"stem":171977,"__hash__":171978},"content/newsletter/2022/06/13/the-road-show-begins.md","The Road Show Begins",{"type":648,"value":171702,"toc":171957},[171703,171706,171710,171713,171727,171733,171736,171742,171745,171752,171760,171766,171769,171772,171778,171781,171787,171795,171799,171802,171805,171811,171817,171823,171826,171829,171832,171835,171837,171839,171862,171864,171893,171895,171904,171906,171915,171917,171926,171928,171937,171939,171944],[651,171704,171705],{},"It was a busy week on the road last week and I’m happy to be back home and publishing the newest edition of this newsletter. Last week I had the honor of giving 3 brand new talks in 4 days in 2 different countries.",[4542,171707,171709],{"id":171708},"toronto-spring-meetup-group","Toronto Spring Meetup Group",[651,171711,171712],{},"I started out by flying into Toronto on Monday morning. I was supposed to leave at 7 AM and get in by 8:30 and my flight time was pushed back twice to 10:45 AM. This put a little stress on me because I had to give a meetup talk at 12:30 so it was going to be tight. I ended up getting there right on time and it was a really relaxed meetup group.",[651,171714,171715,171720,171721,171726],{},[812,171716,171719],{"href":171717,"rel":171718},"https://tanzu.vmware.com/developer/team/cora-iberkleid/",[816],"Cora"," and I did a talk on ",[812,171722,171725],{"href":171723,"rel":171724},"https://www.testcontainers.org/",[816],"Testcontainers"," and this was the first time we gave that talk. I was interested in finding out how our timing was going to work. We ended up having some problems with jerks on zoom and also deep dove into a couple of things that we weren't going to cover so we ended up spending a lot more time than the hour that we planned. All in all the talk went great and I can’t wait to share more about Testcontainers in the future!",[651,171728,171729],{},[660,171730],{"alt":171731,"src":171732},"8A3239C7-7CBD-4BB3-8534-CB9C282565CA_1_105_c.jpeg","/images/newsletter/2022/06/13/8A3239C7-7CBD-4BB3-8534-CB9C282565CA_1_105_c.jpeg",[651,171734,171735],{},"I want to thank everyone at the VMware offices for welcoming us with open arms and we even got a tour of the office!",[651,171737,171738],{},[660,171739],{"alt":171740,"src":171741},"6FCC74D8-CBDD-4E24-A466-DC2F8F6B1E34_1_105_c.jpeg","/images/newsletter/2022/06/13/6FCC74D8-CBDD-4E24-A466-DC2F8F6B1E34_1_105_c.jpeg",[4542,171743,97015],{"id":171744},"springone-tour-toronto",[651,171746,171747,171748,171751],{},"Next up was our ",[812,171749,89065],{"href":89063,"rel":171750},[816]," that took place on Tuesday & Wednesday in Toronto. This was our 2-day conference where you can meet the experts (and me) in person. This was also the first time I was able to meet a lot of my coworkers so that was really exciting.",[651,171753,171754,171755,171759],{},"The conference kicked off with ",[812,171756,83167],{"href":171757,"rel":171758},"https://tanzu.vmware.com/developer/team/nate-schutta/",[816]," talking about microservices. I was sitting in the front row listening to Nate speak and all I could think of was “I have to follow this guy”. Nate is a great storyteller and presenter and I learned a lot from him.",[651,171761,171762],{},[660,171763],{"alt":171764,"src":171765},"16407CF1-4346-4503-B87E-66BA7F362864_1_105_c.jpeg","/images/newsletter/2022/06/13/16407CF1-4346-4503-B87E-66BA7F362864_1_105_c.jpeg",[651,171767,171768],{},"I gave my talk on Spring for GraphQL and I thought that it went pretty well. There were some great questions afterward that I jotted down that I hope to explore further in the near future. The rest of the conference was amazing with so many great talks by Josh Long, Spencer, Glenn, DaShaun and so many more.",[651,171770,171771],{},"I ended up staying at the Marriott that is attached to the Rogers Centre where the Toronto Blue Jays play. They have a restaurant there that overlooks the field. They weren’t playing while I was there but I want to go back one day when they are. Look at this view 🤩",[651,171773,171774],{},[660,171775],{"alt":171776,"src":171777},"0E8A9984-77D6-4540-8DC7-6545145C92BB_1_102_o.jpeg","/images/newsletter/2022/06/13/0E8A9984-77D6-4540-8DC7-6545145C92BB_1_102_o.jpeg",[651,171779,171780],{},"A bunch of us went out for dinner one night at the CN Tower and wow, what a great experience!",[651,171782,171783],{},[660,171784],{"alt":171785,"src":171786},"EF17787C-A300-47CB-B53C-3CB9D2A57B38_1_105_c.jpeg","/images/newsletter/2022/06/13/EF17787C-A300-47CB-B53C-3CB9D2A57B38_1_105_c.jpeg",[651,171788,171789,171790,171794],{},"I’m really excited about our next tour stop in ",[812,171791,171793],{"href":97048,"rel":171792},[816],"New York City"," where most of my team should be in the same place at the same time. If you’re in a city where we are stopping make sure you sign up and come say hi 👋🏻",[4542,171796,171798],{"id":171797},"vueconfus","VueConf.US",[651,171800,171801],{},"I have been a part of the Vue community for almost 4 years now and this was my first Vue Conference. Needless to say, I was really excited when I got accepted to speak. I said this on stage but I want to say it again, thank you to everyone who put on this conference, and thank you for allowing me to ramble for 30 minutes.",[651,171803,171804],{},"My talk was on what’s new in Nuxt 3. I have been looking into moving my website over to Nuxt 3 for a while now and as we get closer and closer to a release I’m getting really excited about the move.",[651,171806,171807],{},[660,171808],{"alt":171809,"src":171810},"CDB602A4-B009-4444-961A-FA5AFFCB74E4_1_105_c.jpeg","/images/newsletter/2022/06/13/CDB602A4-B009-4444-961A-FA5AFFCB74E4_1_105_c.jpeg",[651,171812,171813],{},[660,171814],{"alt":171815,"src":171816},"58305412-43C9-43EA-8088-FB3A44A207BD_1_105_c.jpeg","/images/newsletter/2022/06/13/58305412-43C9-43EA-8088-FB3A44A207BD_1_105_c.jpeg",[651,171818,171819],{},[660,171820],{"alt":171821,"src":171822},"Pratik","/images/newsletter/2022/06/13/pratik.jpg",[651,171824,171825],{},"What I loved most about this conference was what I love about all conferences, the people. I got to meet some people that I have talked to over the years like Adam Jahr and Erik Hanchett and caught up with an old friend Cameron Childress. I really appreciate everyone who came up to me and provided some feedback.",[651,171827,171828],{},"One person asked me how I am so confident on stage and that was a bit of a shock to me. I have some confidence because I knew the material well and it’s something I like talking about but I wouldn’t ever think of myself as overconfident, so thank you for that!",[4542,171830,171831],{"id":89038},"What’s next",[651,171833,171834],{},"I am home for the next two weeks so I hope to pump out a bunch of content before heading back on the road for SpringOne Tour NYC. If there is anything you would like to see please reach out to me on Twitter.",[4542,171836,157574],{"id":157573},[5909,171838,164959],{"id":69848},[5316,171840,171841,171848,171855],{},[5332,171842,171843],{},[812,171844,171847],{"href":171845,"rel":171846},"https://github.blog/2020-04-14-github-is-now-free-for-teams/",[816],"GitHub is free for teams",[5332,171849,171850],{},[812,171851,171854],{"href":171852,"rel":171853},"https://tailwindcss.com/blog/tailwindcss-v3-1",[816],"Tailwind CSS v3.1",[5332,171856,171857],{},[812,171858,171861],{"href":171859,"rel":171860},"https://www.jobrunr.io/en/blog/2022-06-10-my-own-product-part-2/",[816],"Launching a developer product - part 2",[5909,171863,164971],{"id":157591},[5316,171865,171866,171873,171879,171886],{},[5332,171867,171868],{},[812,171869,171872],{"href":171870,"rel":171871},"https://www.youtube.com/watch?v=5IROOj7sLKg",[816],"Bootiful Kubernetes Operators by Cora Iberkleid and Josh Long @ Spring I/O 2022",[5332,171874,171875],{},[812,171876,171878],{"href":171870,"rel":171877},[816],"Declarative Clients in Spring by Rossen Stoyanchev and Olga Maciaszek-Sharma @ Spring I/O 2022",[5332,171880,171881],{},[812,171882,171885],{"href":171883,"rel":171884},"https://www.youtube.com/watch?v=c-GV2PxymoY",[816],"How fixing a broken window cut down our build time by 50% by Philip Riecks @ Spring I/O 2022",[5332,171887,171888],{},[812,171889,171892],{"href":171890,"rel":171891},"https://www.youtube.com/watch?v=oTn8SHz5Ux8",[816],"Ahead Of Time and Native in Spring Boot 3.0 by Brian Clozel and Stéphane Nicoll @ Spring I/O 2022",[5909,171894,164983],{"id":39439},[5316,171896,171897],{},[5332,171898,171899],{},[812,171900,171903],{"href":171901,"rel":171902},"https://bootifulpodcast.fm/#/episodes/564cc3c0-378f-45cb-86c3-e652da8067b0",[816],"Kubernetes legend and friend Cora Iberkleid on Tanzu, Cartographer, and more",[5909,171905,164995],{"id":133422},[5316,171907,171908],{},[5332,171909,171910],{},[812,171911,171914],{"href":171912,"rel":171913},"https://histoire.dev/",[816],"Histoire",[5909,171916,165005],{"id":39340},[5316,171918,171919],{},[5332,171920,171921],{},[812,171922,171925],{"href":171923,"rel":171924},"https://learning.oreilly.com/library/view/learning-microsoft-azure/9781098113315/",[816],"Learning Microsoft Azure (Early Edition)",[5909,171927,166601],{"id":21972},[5316,171929,171930],{},[5332,171931,171932],{},[812,171933,171936],{"href":171934,"rel":171935},"https://www.masterclass.com/sessions/classes/make-compelling-videos-that-go-viral",[816],"MKBHD - Masterclass",[4542,171938,157704],{"id":157703},[651,171940,166267,171941,166271],{},[812,171942,41499],{"href":44086,"rel":171943},[816],[651,171945,41105,171946,69920,171948,171950,171952,171954],{},[41107,171947],{},[41107,171949],{},[812,171951,161560],{"href":161111},[41107,171953],{},[812,171955,53869],{"href":82688,"rel":171956},[816],{"title":674,"searchDepth":790,"depth":790,"links":171958},[171959,171960,171961,171962,171963,171971],{"id":171708,"depth":790,"text":171709},{"id":171744,"depth":790,"text":97015},{"id":171797,"depth":790,"text":171798},{"id":89038,"depth":790,"text":171831},{"id":157573,"depth":790,"text":157574,"children":171964},[171965,171966,171967,171968,171969,171970],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":21972,"depth":892,"text":166601},{"id":157703,"depth":790,"text":157704},{"slug":171973,"date":171974},"the-road-show-begins","2022-06-13T14:00:00.000Z","/newsletter/2022/06/13/the-road-show-begins",{"title":171700,"description":171705},"newsletter/2022/06/13/the-road-show-begins","5Jz__QsoAPHEm7pCsnMYizZLaBflEou717DFTS7EWHc",{"id":171980,"title":171981,"body":171982,"description":172264,"extension":793,"meta":172265,"navigation":797,"path":172268,"seo":172269,"stem":172270,"__hash__":172271},"content/newsletter/2022/06/27/internet-explorer-retires.md","Internet Explorer Retires, Java Frameworks and a YouTube Milestone",{"type":648,"value":171983,"toc":172250},[171984,171991,171993,171996,172033,172036,172042,172046,172049,172052,172055,172061,172065,172074,172080,172083,172089,172091,172094,172097,172100,172105,172111,172113,172115,172138,172140,172177,172179,172194,172196,172219,172221,172230,172232,172237],[651,171985,171986,171987,171990],{},"Happy Monday and welcome to another edition of the newsletter. I am either on my way or in NYC depending on when you are reading this. I’m visiting for the next stop on our ",[812,171988,89065],{"href":97048,"rel":171989},[816],". If you happen to be attending the conference please say hi 👋🏻 It’s been a busy couple of weeks for me which means I have plenty to share so let’s get into it.",[4542,171992,159606],{"id":159605},[651,171994,171995],{},"I was able to create and publish 5 videos over the last 2 weeks which is a lot. I’m trying to cut down on production value and focus on the content so that I can publish videos faster.",[5316,171997,171998,172005,172012,172019,172026],{},[5332,171999,172000],{},[812,172001,172004],{"href":172002,"rel":172003},"https://youtu.be/PN2K1_jMl5A",[816],"Github Codespaces",[5332,172006,172007],{},[812,172008,172011],{"href":172009,"rel":172010},"https://youtu.be/1lEeEvdcZT0",[816],"Create a new annotation in Spring Boot",[5332,172013,172014],{},[812,172015,172018],{"href":172016,"rel":172017},"https://youtu.be/dciF5Pc3G1s",[816],"Vue 3 Options API: You don’t have to use the Composition API",[5332,172020,172021],{},[812,172022,172025],{"href":172023,"rel":172024},"https://youtu.be/5KjCHz9-nJo",[816],"Spring Boot Multi-document properties",[5332,172027,172028],{},[812,172029,172032],{"href":172030,"rel":172031},"https://youtu.be/FZFzKCSq8n8",[816],"Java Problem Solving: Left to Right Eval",[651,172034,172035],{},"I also got the notification this week that my YouTube channel crossed 2,000,000 views. That is so freaking cool and I can’t thank all of you enough for watching.",[651,172037,172038],{},[660,172039],{"alt":172040,"src":172041},"YouTube Milestone","/images/newsletter/2022/06/27/youtube-2-million.png",[4542,172043,172045],{"id":172044},"java-frameworks-panel-discussion","Java Frameworks Panel Discussion",[651,172047,172048],{},"It’s so great to be back in person for meetups. This was just our 2nd meeting after bringing back the Cleveland Java User Group. This was a Java Frameworks Panel discussion where we had representatives of Spring Framework, Quarkus, and Micronaut.",[651,172050,172051],{},"It was a great turnout and we had the opportunity to answer questions and then go into some demos. I was the Spring representative and I went through and explained What Spring Framework is and where Spring Boot comes into the picture. Another question that always comes up with developers new to Spring is what types of applications can I build?",[651,172053,172054],{},"I wrapped up my portion of the presentation by building a simple application with Spring Boot. This gave me a chance to show off the Spring Initalizr, talk about dependency injection and demo how easy it is to connect to a database.",[651,172056,172057],{},[660,172058],{"alt":172059,"src":172060},"Cleveland Java Meetup","/images/newsletter/2022/06/27/cjug.jpeg",[4542,172062,172064],{"id":172063},"internet-explorer","Internet Explorer",[651,172066,172067,172068,172073],{},"On June 15, 2022, ",[812,172069,172072],{"href":172070,"rel":172071},"https://blogs.windows.com/windowsexperience/2022/06/15/internet-explorer-11-has-retired-and-is-officially-out-of-support-what-you-need-to-know/",[816],"Internet Explorer officially retired",". On one hand, it’s a little bit sad because for a long time this is how many users explored the internet. On the other hand, I don’t have a lot of great memories of developing for the web with IE standing in my way. I remember fondly IE 6 being the biggest pain to target. When we no longer had to support IE 6 we could stop using the numerous amounts of hacks that existed in CSS & JavaScript.",[651,172075,172076],{},[660,172077],{"alt":172078,"src":172079},"Internet Explorer Retires","/images/newsletter/2022/06/27/internet-explorer.png",[651,172081,172082],{},"This is how I was feeling that day 🤣",[651,172084,172085],{},[812,172086,172087],{"href":172087,"rel":172088},"https://twitter.com/therealdanvega/status/1537068063601958912",[816],[4542,172090,97502],{"id":165875},[651,172092,172093],{},"If you aren’t aware yet there is a project by Github called Copilot which is an AI trained on billions of lines of code that will help you write code. It does this through comments and context of an application. It also does a really good job of writing documentation and I use it often in Markdown files.",[651,172095,172096],{},"I have been lucky enough to have been invited to the Github Copilot program early on. I have used it for building frontend applications with JavaScript, TypeScript and Vue within Visual Studio Code. I have also used it for building Java & Spring applications within IntelliJ IDEA. I really enjoy the product and I believe it makes me more productive.",[651,172098,172099],{},"This past week Github Copilot was now available to anyone who wanted to try it out. The catch was that they will be charging for it soon. I can tell you that I have no problem paying for it and have already signed up.",[651,172101,172102],{},[660,172103],{"alt":97502,"src":172104},"/images/newsletter/2022/06/27/github-copilot.png",[651,172106,172107,172108,172110],{},"I heard some comments on Twitter that people were upset that Github was taking their code for free and now charging for Github Copilot. I can certainly understand that point of view but It doesn’t bother me. Github is one of the most important tools in life as a developer and it is given to us for ",[2939,172109,43837],{}," by Microsoft. If you",[4542,172112,157574],{"id":157573},[5909,172114,164959],{"id":69848},[5316,172116,172117,172124,172131],{},[5332,172118,172119],{},[812,172120,172123],{"href":172121,"rel":172122},"https://medium.com/javarevisited/spring-for-graphql-with-querydsl-9c4964a225d9",[816],"Spring for GraphQL with Querydsl",[5332,172125,172126],{},[812,172127,172130],{"href":172128,"rel":172129},"https://blogs.oracle.com/javamagazine/post/java-garbage-collectors-evolution",[816],"Java garbage collection: The 10-release evolution from JDK 8 to JDK 18",[5332,172132,172133],{},[812,172134,172137],{"href":172135,"rel":172136},"https://survey.stackoverflow.co/2022/",[816],"StackOverflow 2022 Developer Survey",[5909,172139,164971],{"id":157591},[5316,172141,172142,172149,172156,172163,172170],{},[5332,172143,172144],{},[812,172145,172148],{"href":172146,"rel":172147},"https://www.youtube.com/watch?v=lKSSBvRDmTg",[816],"Java 19 Virtual Threads - JEP Café #11",[5332,172150,172151],{},[812,172152,172155],{"href":172153,"rel":172154},"https://www.youtube.com/watch?v=A7LMrVz8QhM",[816],"How to Inspect and Troubleshoot Spring Microservices on Kubernetes",[5332,172157,172158],{},[812,172159,172162],{"href":172160,"rel":172161},"https://www.youtube.com/watch?v=WTZmTcvDAB8",[816],"ϟ Enlightning: Event-Driven Architecture and Real-Time Analytics",[5332,172164,172165],{},[812,172166,172169],{"href":172167,"rel":172168},"https://www.youtube.com/watch?v=lLTdGmGesIs",[816],"[VDTRIESTE22] Spring For Architects - Conference by Nate Schutta and Jakub Pilimon",[5332,172171,172172],{},[812,172173,172176],{"href":172174,"rel":172175},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0007/",[816],"Spring Office Hours: SpringOne Tour Toronto Recap",[5909,172178,164983],{"id":39439},[5316,172180,172181,172188],{},[5332,172182,172183],{},[812,172184,172187],{"href":172185,"rel":172186},"https://bootifulpodcast.fm/#/episodes/0083a911-e923-4982-8c22-d024a1eee2c7",[816],"Spring Framework contributor Sébastien Deleuze on GraalVM, AOT, project Leyden, and WebAssembly",[5332,172189,172190],{},[812,172191,172193],{"href":120613,"rel":172192},[816],"The Changelog Podcats: What is DevRel?",[5909,172195,166601],{"id":21972},[5316,172197,172198,172205,172212],{},[5332,172199,172200],{},[812,172201,172204],{"href":172202,"rel":172203},"https://www.youtube.com/watch?v=EExlnnq5Grs",[816],"JavaBrains - 01 Course Introduction (Reactive programming with Java - full course)",[5332,172206,172207],{},[812,172208,172211],{"href":172209,"rel":172210},"https://aws-for-web.dev/",[816],"AWS for Web Developers",[5332,172213,172214],{},[812,172215,172218],{"href":172216,"rel":172217},"https://www.adobe.com/max/2021/sessions.html",[816],"Adobe Max 2021 Session Recordings",[5909,172220,167862],{"id":160019},[5316,172222,172223],{},[5332,172224,172225],{},[812,172226,172229],{"href":172227,"rel":172228},"https://spring.io/blog/2022/06/21/this-week-in-spring-june-21st-2022",[816],"This Week in Spring - June 21st, 2022",[4542,172231,157704],{"id":157703},[651,172233,166267,172234,166271],{},[812,172235,41499],{"href":44086,"rel":172236},[816],[651,172238,41105,172239,69920,172241,172243,172245,172247],{},[41107,172240],{},[41107,172242],{},[812,172244,161560],{"href":161111},[41107,172246],{},[812,172248,53869],{"href":82688,"rel":172249},[816],{"title":674,"searchDepth":790,"depth":790,"links":172251},[172252,172253,172254,172255,172256,172263],{"id":159605,"depth":790,"text":159606},{"id":172044,"depth":790,"text":172045},{"id":172063,"depth":790,"text":172064},{"id":165875,"depth":790,"text":97502},{"id":157573,"depth":790,"text":157574,"children":172257},[172258,172259,172260,172261,172262],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":21972,"depth":892,"text":166601},{"id":160019,"depth":892,"text":167862},{"id":157703,"depth":790,"text":157704},"Happy Monday and welcome to another edition of the newsletter. I am either on my way or in NYC depending on when you are reading this. I’m visiting for the next stop on our SpringOne Tour. If you happen to be attending the conference please say hi 👋🏻 It’s been a busy couple of weeks for me which means I have plenty to share so let’s get into it.",{"slug":172266,"date":172267},"internet-explorer-retires","2022-06-27T07:00:00.000Z","/newsletter/2022/06/27/internet-explorer-retires",{"title":171981,"description":172264},"newsletter/2022/06/27/internet-explorer-retires","_wCCUScTPky58iiQdxPiSQE-E85Ocr6jj1X2oN2xyYc",{"id":172273,"title":172274,"body":172275,"description":172536,"extension":793,"meta":172537,"navigation":797,"path":172540,"seo":172541,"stem":172542,"__hash__":172543},"content/newsletter/2022/07/12/bootiful-podcast.md","SpringOne NYC, A Bootiful Podcast and new video content",{"type":648,"value":172276,"toc":172516},[172277,172284,172287,172291,172294,172300,172308,172311,172314,172318,172321,172327,172329,172332,172336,172339,172342,172346,172353,172356,172360,172363,172369,172373,172376,172382,172384,172386,172395,172397,172420,172422,172437,172439,172469,172471,172474,172476,172488,172490,172496,172498,172503],[651,172278,172279,172280,172283],{},"Hello friends and welcome to another edition of the newsletter. This edition is coming to you a day late and as I write this I am sitting in Seattle for another ",[812,172281,89065],{"href":97027,"rel":172282},[816],". I’m having a great time in Seattle and I will tell you more about this stop in the next version of the newsletter.",[651,172285,172286],{},"In this edition of the newsletter, I want to talk about SpringOne Tour NYC, A Bootiful Podcast, 2 new videos for YouTube, and a conference recording.",[4542,172288,172290],{"id":172289},"springone-tour-new-york-city","SpringOne Tour New York City",[651,172292,172293],{},"I had the opportunity to spend a week in NYC with my entire team for the 1st time since joining VMware. It was so great to have everyone in one place and I learned so much on this trip. I gave my talk on Getting Started with GraphQL and I thought it went really well I received some great questions from the audience.",[651,172295,172296],{},[660,172297],{"alt":172298,"src":172299},"Dan Speaking in NYC","/images/newsletter/2022/07/12/dan-nyc-talk.jpeg",[651,172301,172302,172303,172307],{},"After my talk, I was able to attend a bunch of sessions and I learned so much. One session that I was excited to see was on the new ",[812,172304,36524],{"href":172305,"rel":172306},"https://github.com/spring-projects-experimental/spring-cli",[816]," from Mark Pollack. This is an experimental project at the moment but it solves a real problem that other communities have solutions for.",[651,172309,172310],{},"The Spring CLI will help increase productivity when creating new projects or adding functionality to existing projects. You can read through the documentation to learn more but I plan on creating some content in the new future about this exciting project.",[651,172312,172313],{},"What was really exciting for me is that I had a chance to sit down with Mark and do some pair programming. After about an hour I was able to create a new GraphQL project that you can use with the Spring CLI. Again if that doesn’t make sense, I’ll have more on that soon!",[4542,172315,172317],{"id":172316},"a-bootiful-podcast-with-josh-long","A Bootiful Podcast with Josh Long",[651,172319,172320],{},"I had the opportunity to sit down with my friend and coworker Josh Long for an episode of his podcast. We had a chance to talk about my career and how I ended up where I am today. It’s always fun reflecting on my career and I can’t wait for you to hear our conversation.",[651,172322,172323],{},[812,172324,172325],{"href":172325,"rel":172326},"https://bootifulpodcast.fm/#/episodes/1396a2af-8cf3-42e1-8083-71aa8c18eeb8",[816],[4542,172328,15432],{"id":61224},[651,172330,172331],{},"I was able to create 2 videos over the last 2 weeks. It’s crazy that in one week I am able to create 3 videos and some weeks I don’t create any. I guess that is the life of being a YouTuber though, consistency is the key to the whole game.",[5909,172333,172335],{"id":172334},"testing-your-graphql-apis","Testing your GraphQL APIs",[651,172337,172338],{},"I have been creating a lot of content around Spring for GraphQL lately and one of the questions that keep coming up is how can I test my APIs. The testing support in Spring for GraphQL out of the box is really good and easy to get started with. In this tutorial, you will learn how to write a slice test and the GraphQL Tester that includes a fluent API for testing your APIs independent of the server transport.",[5988,172340],{"id":172341},"0b0x3C_BTT8",[5909,172343,172345],{"id":172344},"intellij-live-templates-for-spring-boot","IntelliJ Live Templates for Spring Boot",[651,172347,172348,172349,172352],{},"If you aren’t aware the Spring Developer Advocacy team started a ",[812,172350,169723],{"href":169729,"rel":172351},[816],". There are so many great questions in that community and my plan is to start answering questions there. In this video, I answer my friend DaShaun’s question on how to quickly automate some one-liners in Spring Boot.",[5988,172354],{"id":172355},"38VYJjjKqh0",[4542,172357,172359],{"id":172358},"whats-new-in-nuxt-3","What’s new in Nuxt 3",[651,172361,172362],{},"I gave a talk at VueConf US in June on What is new in Nuxt 3. That conference was such a great time and I thought my talk went well. Now you can be the judge of that now that the recording is online. Thanks to the fine folks over at Vue Mastery for making it available. If you don’t have a subscription to them you should consider signing up.",[651,172364,172365],{},[812,172366,172367],{"href":172367,"rel":172368},"https://www.vuemastery.com/conferences/vueconf-us-2022/whats-coming-in-nuxt3",[816],[4542,172370,172372],{"id":172371},"happy-anniversary","Happy Anniversary",[651,172374,172375],{},"On July 8th my wife Jen and I celebrated our 5th Anniversary. I’m not sure how I got so lucky but If I can offer you some unsolicited advice… Marry your best friend 🤩🥳",[651,172377,172378],{},[660,172379],{"alt":172380,"src":172381},"Wedding Picture","/images/newsletter/2022/07/12/wedding.png",[4542,172383,157574],{"id":157573},[5909,172385,164959],{"id":69848},[5316,172387,172388],{},[5332,172389,172390],{},[812,172391,172394],{"href":172392,"rel":172393},"https://www.infoq.com/articles/data-oriented-programming-java/",[816],"Data-Oriented Programming",[5909,172396,164971],{"id":157591},[5316,172398,172399,172406,172413],{},[5332,172400,172401],{},[812,172402,172405],{"href":172403,"rel":172404},"https://www.youtube.com/watch?v=AE_Srj6r4Rc&list=WL&index=2",[816],"Securing SPAs with Spring by Marcus Hert Da Coregio @ Spring I/O 2022",[5332,172407,172408],{},[812,172409,172412],{"href":172410,"rel":172411},"https://www.youtube.com/watch?v=gKPMKRnnbXU",[816],"Gradle Tutorial - Crash Course",[5332,172414,172415],{},[812,172416,172419],{"href":172417,"rel":172418},"https://www.youtube.com/watch?v=jkP199zzknw",[816],"Spring Cloud Gateway: Resilience, Security, and Observability by Thomas Vitale @ Spring I/O 2022",[5909,172421,164983],{"id":39439},[5316,172423,172424,172430],{},[5332,172425,172426],{},[812,172427,172429],{"href":172325,"rel":172428},[816],"Bootiful Podcast featuring Dan Vega",[5332,172431,172432],{},[812,172433,172436],{"href":172434,"rel":172435},"https://syntax.fm/show/478/supper-club-developer-experience-with-shawn-wang",[816],"Syntax.fm - Supper Club × Developer Experience with Shawn Wang",[5909,172438,164995],{"id":133422},[5316,172440,172441,172448,172455,172462],{},[5332,172442,172443],{},[812,172444,172447],{"href":172445,"rel":172446},"https://aws.amazon.com/fr/codewhisperer/",[816],"Amazon CodeWhisperer",[5332,172449,172450],{},[812,172451,172454],{"href":172452,"rel":172453},"https://github.com/sdras/awesome-actions",[816],"Awesome Actions",[5332,172456,172457],{},[812,172458,172461],{"href":172459,"rel":172460},"https://bun.sh/",[816],"Bun - JavaScript Runtime",[5332,172463,172464],{},[812,172465,172468],{"href":172466,"rel":172467},"https://twitter.com/testcontainers/status/1542115229605269505",[816],"TestContainers 1.17.3",[5909,172470,165017],{"id":165016},[651,172472,172473],{},"“If I had an hour to solve a problem I'd spend 55 minutes thinking about the problem and five minutes thinking about solutions.” - Albert Einstein",[5909,172475,167862],{"id":160019},[5316,172477,172478],{},[5332,172479,172480,13517,172485],{},[812,172481,172484],{"href":172482,"rel":172483},"https://blog.jetbrains.com/idea/2022/07/java-annotated-monthly-july-2022/",[816],"Java Annotated Monthly - July 2022",[7300,172486,172487],{},"(Thanks for the mention 🤩)",[5909,172489,165430],{"id":165429},[651,172491,172492],{},[812,172493,172494],{"href":172494,"rel":172495},"https://twitter.com/therealdanvega/status/1544782934477078539",[816],[4542,172497,157704],{"id":157703},[651,172499,166267,172500,166271],{},[812,172501,41499],{"href":44086,"rel":172502},[816],[651,172504,41105,172505,69920,172507,172509,172511,172513],{},[41107,172506],{},[41107,172508],{},[812,172510,161560],{"href":161111},[41107,172512],{},[812,172514,53869],{"href":82688,"rel":172515},[816],{"title":674,"searchDepth":790,"depth":790,"links":172517},[172518,172519,172520,172524,172525,172526,172535],{"id":172289,"depth":790,"text":172290},{"id":172316,"depth":790,"text":172317},{"id":61224,"depth":790,"text":15432,"children":172521},[172522,172523],{"id":172334,"depth":892,"text":172335},{"id":172344,"depth":892,"text":172345},{"id":172358,"depth":790,"text":172359},{"id":172371,"depth":790,"text":172372},{"id":157573,"depth":790,"text":157574,"children":172527},[172528,172529,172530,172531,172532,172533,172534],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":165016,"depth":892,"text":165017},{"id":160019,"depth":892,"text":167862},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},"Hello friends and welcome to another edition of the newsletter. This edition is coming to you a day late and as I write this I am sitting in Seattle for another SpringOne Tour. I’m having a great time in Seattle and I will tell you more about this stop in the next version of the newsletter.",{"slug":172538,"date":172539},"bootiful-podcast","2022-07-12T17:00:00.000Z","/newsletter/2022/07/12/bootiful-podcast",{"title":172274,"description":172536},"newsletter/2022/07/12/bootiful-podcast","waQc6TEB1Y85nziWt4Cw81ql6snjkRiSGldflZhR3J8",{"id":172545,"title":172546,"body":172547,"description":172851,"extension":793,"meta":172852,"navigation":797,"path":172855,"seo":172856,"stem":172857,"__hash__":172858},"content/newsletter/2022/07/18/spring-one-tour-seattle.md","SpringOne Tour Seattle, Code on the Beach, and Office Hours",{"type":648,"value":172548,"toc":172832},[172549,172556,172559,172562,172568,172571,172575,172578,172581,172584,172590,172593,172597,172600,172603,172609,172612,172616,172619,172622,172627,172629,172632,172639,172642,172645,172650,172653,172656,172659,172662,172668,172670,172679,172685,172691,172693,172695,172725,172727,172750,172752,172768,172770,172779,172781,172783,172799,172801,172804,172810,172812,172819],[651,172550,172551,172552,172555],{},"I hope all of you had a wonderful weekend full of friends, family, and fun and are ready to tackle another week. I’m writing this newsletter back in my home office after a fantastic trip to Seattle for a ",[812,172553,83142],{"href":97027,"rel":172554},[816],". I’ll tell you more about that as well as the one I have coming up next week in Florida.",[4542,172557,97029],{"id":172558},"springone-tour-seattle",[651,172560,172561],{},"I spent most of last week in the great city of Seattle and I was lucky to be able to take my family with me on this trip. Before I get into the SpringOne Tour stop I just have to say what a beautiful city Seattle is. We had a chance to visit the pier, Space Needle, and more, and the views of the water, mountains, and city are just breathtaking.",[651,172563,172564],{},[660,172565],{"alt":172566,"src":172567},"Family at the Space Needle in Seattle","/images/newsletter/2022/07/18/family-pic.jpeg",[651,172569,172570],{},"As for the conference, I knew that I would be giving my Introduction to GraphQL talk and half of a new talk on Testcontainers. I found out while I was in Seattle that my coworker Cora wasn’t going to be able to make it so I knew I would need to step up and give the presentation by myself.",[5909,172572,172574],{"id":172573},"introduction-to-spring-for-graphql","Introduction to Spring for GraphQL",[651,172576,172577],{},"This was my 3rd time giving this presentation and at this point, I am pretty comfortable with the material. In this talk, we talk about why you should learn GraphQL and that’s usually the first question developers have.",[651,172579,172580],{},"I found that after the talk the main question I kept getting asked was “When do we know it’s time to move from REST to GraphQL”. I will try and create some content around that question in the near future but here is my short answer. If you find yourself creating many endpoints to support the number of clients calling your application it might be time to take a look at GraphQL.",[651,172582,172583],{},"The more custom endpoints and code you are writing means the more code you’re going to have to support and maintain. With GraphQL you can have a single endpoint and let your clients tell you what information they need instead of you telling the clients what information is available to them.",[651,172585,172586],{},[660,172587],{"alt":172588,"src":172589},"Dan Presenting on GraphQL","/images/newsletter/2022/07/18/graphql-in-action.jpeg",[651,172591,172592],{},"I don’t think I will be giving this talk at any other SpringOne Tour stops this year so if you’re interested in me giving this presentation at your conference or meetup please reach out and let me know.",[5909,172594,172596],{"id":172595},"level-up-your-integration-testing-with-testcontainers","Level up your integration testing with Testcontainers",[651,172598,172599],{},"As I mentioned earlier I was prepared to give half of this 40-minute talk but not the whole thing. Lucky for me my coworker put together a bunch of great slides and I was able to give an introduction to Testcontainers using them.",[651,172601,172602],{},"With this essentially being the first time I gave this talk I thought it went really well. I started out by talking about the importance of testing and that if tests are slow and hard to write I won't write them. Cora says this often and I thought I would share it with the audience because it is so true!",[651,172604,172605],{},[660,172606],{"alt":172607,"src":172608},"Testing Slide - Quote from Cora","/images/newsletter/2022/07/18/testing-slide.png",[651,172610,172611],{},"To me this means that if we can make testing easy, fast and concise I will be more likely to write tests, which is what we all should be doing. In the last 25 minutes, I went through a live Spring Boot demo where I created a new integration test and used Testcontainers. We talked through the progression of running a docker command, docker compose, a generic Testcontainer and finally a specific Testcontainer Module.",[4542,172613,172615],{"id":172614},"vueconf-us-recordings","VueConf US Recordings",[651,172617,172618],{},"At the beginning of June, I had the honor of speaking at VueConf US in Ft. Lauderdale FL. It was an amazing conference with so many great speakers and it was my first time attending and speaking at a Vue conference.",[651,172620,172621],{},"My session was 30 minutes and it was on “What’s new in Nuxt 3” and as the title suggests we take a tour of the new features in Nuxt 3. All of the sessions were recorded and you can find mine using the link below.",[651,172623,172624],{},[812,172625,172367],{"href":172367,"rel":172626},[816],[4542,172628,170999],{"id":170998},[651,172630,172631],{},"Next week I will be speaking at Code on the Beach in Atlantic Beach FL. I’ll be honest, I can get used to trips to Florida to talk about code and spend time on the beach 😉",[651,172633,172634,172635,172638],{},"My talk is on Full-Stack Java Development with Spring Boot. The first topic I want to discuss in this presentation is the term ",[2939,172636,172637],{},"Full-Stack"," because depending on who you ask you might get a different definition of what the role involves. The question is what is a Full-Stack Developer?",[651,172640,172641],{},"To me, it starts with the word developer. When we qualify this person as a developer we are saying they write code. I know as well as anyone that there is much more to a developer than writing code but for this conversation, I will keep it to that. With that comes some understanding of how the internet works so I will also omit that for this conversation.",[651,172643,172644],{},"When it comes to writing code I believe that this individual works on both the front-end and back-end of the stack. This means that they have front-end skills like HTML, CSS & JavaScript. I realize those are broad buckets and we can spend an entire article getting specific about those but I won’t at this time. In JavaScript I believe you should also have some experience with frameworks like React, Angular or Vue. If you have worked with those you might also have experience with bundlers and build tools.",[651,172646,172647],{},[660,172648],{"alt":52272,"src":172649},"/images/newsletter/2022/07/18/joshua-aragon-EaB4Ml7C7fE-unsplash.jpg",[651,172651,172652],{},"When it comes to the back-end they should have experience in a programming language like Java, build tools like Maven or Gradle, a framework like Spring, and working with databases. Again I have simplified those requirements a bit but those are the types of skills one would need to build back-end web applications.",[651,172654,172655],{},"This is just one stack and would be different if you were working on something like mobile applications but the important part is that you are writing code on both the front-end (client) and back-end (server) of the application.",[651,172657,172658],{},"I think where some of the argument comes in is how much involvement would this person have in the deployment process. Should they understand Containers, Docker, scripting, CI/CD, and everything else that goes into DevOps? Personally, I think it’s nice to understand some of that but as a requirement that would probably depend on the company and position.",[651,172660,172661],{},"I’m curious what your thoughts are on what the responsibilities are of a Full-Stack Developer. If you want I have a tweet that you can reply to or you can reply directly to this email.",[651,172663,172664],{},[812,172665,172666],{"href":172666,"rel":172667},"https://twitter.com/therealdanvega/status/1548355158332952577",[816],[4542,172669,97345],{"id":97344},[651,172671,172672,172673,172678],{},"I have talked about this before but my friend ",[812,172674,172677],{"href":172675,"rel":172676},"https://github.com/dashaun",[816],"DaShaun"," and I run a show called Spring Office Hours. This is your chance to find out what’s new in the Spring Community, check out some demos, and ask any questions you might have. This week our show returns live on Tuesday at 11 AM EDT and you can find out more using the link below.",[651,172680,172681],{},[660,172682],{"alt":172683,"src":172684},"Spring Office Hours - Episode 08 Cover","/images/newsletter/2022/07/18/spring-office-hours.png",[651,172686,172687],{},[812,172688,97000],{"href":172689,"rel":172690},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0008/",[816],[4542,172692,157574],{"id":157573},[5909,172694,164959],{"id":69848},[5316,172696,172697,172704,172711,172718],{},[5332,172698,172699],{},[812,172700,172703],{"href":172701,"rel":172702},"https://vitejs.dev/blog/announcing-vite3.html",[816],"Vite 3.0 is out!",[5332,172705,172706],{},[812,172707,172710],{"href":172708,"rel":172709},"https://joshlong.com/jl/blogpost/pagination_in_a_reactive_application.html",[816],"Pagination in a Reactive Application",[5332,172712,172713],{},[812,172714,172717],{"href":172715,"rel":172716},"https://reflectoring.io/spring-shell/",[816],"Create Command-line Applications with Spring Shell",[5332,172719,172720],{},[812,172721,172724],{"href":172722,"rel":172723},"https://medium.com/97-things/full-stack-developer-is-a-mindset-207632bcf34c",[816],"“Full-Stack Developer” Is a Mindset",[5909,172726,164971],{"id":157591},[5316,172728,172729,172736,172743],{},[5332,172730,172731],{},[812,172732,172735],{"href":172733,"rel":172734},"https://www.youtube.com/watch?v=3QPp_DlcZpM",[816],"What’s next for Vue - Evan You",[5332,172737,172738],{},[812,172739,172742],{"href":172740,"rel":172741},"https://www.vuemastery.com/conferences/vueconf-us-2022/nuxtjs-and-chrome",[816],"Nuxt.js and Chrome - Kara Erickson",[5332,172744,172745],{},[812,172746,172749],{"href":172747,"rel":172748},"https://www.vuemastery.com/conferences/vueconf-us-2022/building-accessible-components",[816],"Building Accessible Components - Homer Gains",[5909,172751,164983],{"id":39439},[5316,172753,172754,172761],{},[5332,172755,172756],{},[812,172757,172760],{"href":172758,"rel":172759},"https://spring.io/blog/2022/07/14/a-bootiful-podcast-nate-schutta-the-thinking-person-s-architect-my-friend-and-teammate",[816],"A Bootiful Podcast with Nate Schutta",[5332,172762,172763],{},[812,172764,172767],{"href":172765,"rel":172766},"https://www.infoq.com/podcasts/engineering-empathy/",[816],"Engineering with Empathy",[5909,172769,164995],{"id":133422},[5316,172771,172772],{},[5332,172773,172774],{},[812,172775,172778],{"href":172776,"rel":172777},"https://jdk.java.net/19/release-notes",[816],"JDK 19 - Early-Access Release Notes",[5909,172780,165005],{"id":39340},[5909,172782,167862],{"id":160019},[5316,172784,172785,172792],{},[5332,172786,172787],{},[812,172788,172791],{"href":172789,"rel":172790},"https://spring.io/blog/2022/07/12/this-week-in-spring-july-12th-2022",[816],"This week in Spring - July 12th",[5332,172793,172794],{},[812,172795,172798],{"href":172796,"rel":172797},"https://devrelweekly.com/",[816],"DevRel Weekly",[5909,172800,165430],{"id":165429},[651,172802,172803],{},"I’m signed up for ViteConf which is happening in October. If you’re not signed up for what are you waiting for, it’s FREE.",[651,172805,172806],{},[812,172807,172808],{"href":172808,"rel":172809},"https://twitter.com/ViteConf/status/1548362941321187330",[816],[4542,172811,157704],{"id":157703},[651,172813,172814,172815,166271],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of the newsletter and I will see you next Monday morning. If you have any links you would like me to include please get i",[812,172816,172818],{"href":44086,"rel":172817},[816],"n touch with me",[651,172820,41105,172821,69920,172823,172825,172827,172829],{},[41107,172822],{},[41107,172824],{},[812,172826,161560],{"href":161111},[41107,172828],{},[812,172830,53869],{"href":82688,"rel":172831},[816],{"title":674,"searchDepth":790,"depth":790,"links":172833},[172834,172838,172839,172840,172841,172850],{"id":172558,"depth":790,"text":97029,"children":172835},[172836,172837],{"id":172573,"depth":892,"text":172574},{"id":172595,"depth":892,"text":172596},{"id":172614,"depth":790,"text":172615},{"id":170998,"depth":790,"text":170999},{"id":97344,"depth":790,"text":97345},{"id":157573,"depth":790,"text":157574,"children":172842},[172843,172844,172845,172846,172847,172848,172849],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":160019,"depth":892,"text":167862},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},"I hope all of you had a wonderful weekend full of friends, family, and fun and are ready to tackle another week. I’m writing this newsletter back in my home office after a fantastic trip to Seattle for a SpringOne Tour stop. I’ll tell you more about that as well as the one I have coming up next week in Florida.",{"slug":172853,"date":172854},"spring-one-tour-seattle","2022-07-18T10:00:00.000Z","/newsletter/2022/07/18/spring-one-tour-seattle",{"title":172546,"description":172851},"newsletter/2022/07/18/spring-one-tour-seattle","Z-GzBVWZVMD2dQ7v5mjpCs2nSAQhd3e0MWRUGVtXnuA",{"id":172860,"title":172861,"body":172862,"description":173239,"extension":793,"meta":173240,"navigation":797,"path":173242,"seo":173243,"stem":173244,"__hash__":173245},"content/newsletter/2022/08/01/code-on-the-beach.md","Florida is HOT, SpringOne Workshop and Spring Office Hours",{"type":648,"value":172863,"toc":173219},[172864,172871,172873,172876,172879,172888,172891,172897,172903,172907,172914,172917,172924,172928,172931,172936,172944,172947,172952,172961,172964,172969,172972,172978,172982,172985,172988,172991,172994,172999,173004,173009,173014,173019,173024,173029,173034,173039,173043,173051,173057,173059,173062,173075,173078,173082,173085,173087,173089,173119,173121,173143,173145,173161,173163,173172,173174,173183,173185,173188,173190,173193,173199,173201,173206],[651,172865,172866,172867,172870],{},"Happy Monday and welcome to another edition of the newsletter. I am back from a week in sunny ",[2939,172868,172869],{},"HOT"," Florida. I spent a few days in the Atlantic Beach area for Code on the Beach and then my family and I drove down to Orlando to spend a few days at Disney World.",[4542,172872,170999],{"id":170998},[651,172874,172875],{},"I’ll start with Code on the Beach where this was my first time attending and speaking. I said this in a previous newsletter but I could really get used to conferences on the beach. I was able to speak at VueConf US earlier this summer in Ft Lauderdale and now this one in Atlantic Beach.",[651,172877,172878],{},"I want to start by thanking the conference organizers for not only selecting me to speak but they did an amazing job of putting on a really great conference. The keynotes were amazing, top-notch speakers, the food was great and a built-in beach break was the cherry on top.",[651,172880,172881,172882,172887],{},"The conference was held at the ",[812,172883,172886],{"href":172884,"rel":172885},"https://www.oneoceanresort.com/",[816],"One Ocean Resort and Spa"," in Atlantic Beach and it was gorgeous. We had an ocean view room with a balcony and the crew there were all very helpful. There was a little town with shops, restaurants, and bars there and it was the perfect place to spend a few days.",[651,172889,172890],{},"This is a place that I don’t know I would have ever gone to if I didn’t have a reason to go there and this is one thing I am truly thankful for when it comes to my job. If you have a chance to spend a few days there I would highly recommend it.",[651,172892,172893],{},[660,172894],{"alt":172895,"src":172896},"My girls at Atlantic Beach","/images/newsletter/2022/08/01/0DCB1D05-F68D-410C-97FA-0BB981BD7A13.jpeg",[651,172898,172899],{},[660,172900],{"alt":172901,"src":172902},"Dinner in Jacksonville","/images/newsletter/2022/08/01/A71FE848-6A02-43F9-B85B-6C5AE1EC76C9.jpeg",[5909,172904,172906],{"id":172905},"full-stack-java-development-with-spring-boot","Full Stack Java Development with Spring Boot",[651,172908,172909,172910,172913],{},"I will start with my talk on Full-Stack Java Development with Spring Boot. I gave a version of this talk at ",[812,172911,82113],{"href":82590,"rel":172912},[816]," last year. When I got accepted to give this talk I knew it wasn’t going to be the exact same talk but I envisioned a version of it.",[651,172915,172916],{},"When I finally sat down and started to think about what I wanted to cover I quickly realized that this was going to be a whole new talk. I ended up putting this presentation together in about 4 days which is an improvement over the months it used to take me. I guess it does help when you get to devote your entire day to it and you don’t have to constantly context switch.",[651,172918,172919,172920,664],{},"What I really wanted to cover in this talk was the responsibilities of a full-stack developer and then talk about the particular stack that I am working with on the front and backend. This talk was recorded but I am not sure if it will be available to the general public or not. Either way, I will probably create another version of this for YouTube so stay on the lookout for that. If you want to check out the code and slides from my talk you can get them ",[812,172921,18263],{"href":172922,"rel":172923},"https://github.com/danvega/full-stack-java-spring",[816],[5909,172925,172927],{"id":172926},"code-on-the-beach-sessions","Code on the Beach Sessions",[651,172929,172930],{},"I was able to catch a few sessions while I was there and here are my notes from a few of them.",[651,172932,172933],{},[2939,172934,172935],{},"The Pursuit of Knowledge",[651,172937,172938,172943],{},[812,172939,172942],{"href":172940,"rel":172941},"https://twitter.com/cecilphillip",[816],"Cecil Phillip"," kicked off the conference with a keynote on the Pursuit of Knowledge. As someone who is constantly trying to learn, I really appreciated his talk. One thing he said a few times was that Information is not knowledge. You can watch as many YouTube tutorials or read as many books as you want but that isn’t going to give you what you need to really understand something.",[651,172945,172946],{},"As developers, we need to be taking what we learn and applying it. I think these are all things we have heard before but Cecil's talk was great and it inspired me to work on removing distractions from my day-to-day work so I can focus on the things I want to learn.",[651,172948,172949],{},[2939,172950,172951],{},"Rebuilding Together",[651,172953,172954,172955,172960],{},"The day 2 keynote was given by ",[812,172956,172959],{"href":172957,"rel":172958},"https://twitter.com/therealdanvega/status/1551917107436290049",[816],"Valarie Regas"," and it was about rebuilding together. Valarie shared her personal story about switching careers and how the pandemic affected her. You never want to hear about someone struggling but it’s a reminder that you aren’t the only one. Her story was inspiring and a reminder that our health, both physical and mental are the most important things you can focus on in your life.",[651,172962,172963],{},"The one thing I took away from her talk is that the people who don’t seem like they need help are probably the ones that we should be checking in on and that’s so true. Be a good person and reach out to your friends and coworkers and just ask them how they are doing and if there is anything you can do to help them out.",[651,172965,172966],{},[2939,172967,172968],{},"Cloud Native Architecture",[651,172970,172971],{},"The last session I want to highlight was by my friend Nate Schutta. He gave an hour talk on Cloud Native Architecture and it was packed full of knowledge. Nate is such a great presenter and I learn something from him every single time I watch him talk. His ability to keep a presentation moving and interesting is something you just don’t see from everyone. If this recording gets posted I would highly recommend watching it.",[651,172973,172974],{},[812,172975,172976],{"href":172976,"rel":172977},"https://twitter.com/therealdanvega/status/1551991104744181760",[816],[4542,172979,172981],{"id":172980},"disney-world","Disney World",[651,172983,172984],{},"My family and I decided to make the 2.5-hour drive from Jacksonville to Orlando and stay at Disney’s Polynesian Resort for a few days. First off this was the first time staying at a resort that is on Disney and wow what a difference that makes. I don’t think I can ever go back to staying off the resort again.",[651,172986,172987],{},"The ability to hop on and off a monorail, boat, or bus to go wherever you want whenever you want is very convenient and something you need when traveling with small kids. We spent our time at Disney Springs, Magic Kingdom, Hollywood Studios, and Epcot.",[651,172989,172990],{},"I’m still not sure how we survived walking around in 100-degree weather for 3 days but we did. Did I mention that Florida is HOT 🥵 I already knew this though because we went with my daughter when she was 1 in the middle of July and for some reason, I didn’t learn my lesson. I think the next time we go we will shoot for March.",[651,172992,172993],{},"My girls had so much fun and it really is the most magical place on earth. Just seeing how excited my girls were will be a memory I carry with me for the rest of my life. I feel very fortunate that I was able to be there with them ❤️",[651,172995,172996],{},[660,172997],{"alt":172981,"src":172998},"/images/newsletter/2022/08/01/D6A9CD9C-228C-4E4F-A15E-3BB77429EA2F.jpeg",[651,173000,173001],{},[660,173002],{"alt":172981,"src":173003},"/images/newsletter/2022/08/01/C4CB8AEB-4306-4B60-BC2A-218D8CE13D13.jpeg",[651,173005,173006],{},[660,173007],{"alt":172981,"src":173008},"/images/newsletter/2022/08/01/DF728E8B-BB3F-4E39-9FDF-4DF456991791.jpeg",[651,173010,173011],{},[660,173012],{"alt":172981,"src":173013},"/images/newsletter/2022/08/01/F8C2F2EF-D149-41EA-A1F2-74B82851F9AF.jpeg",[651,173015,173016],{},[660,173017],{"alt":172981,"src":173018},"/images/newsletter/2022/08/01/E1007E35-7BC7-4546-A88D-B34A046702ED.jpeg",[651,173020,173021],{},[660,173022],{"alt":172981,"src":173023},"/images/newsletter/2022/08/01/647E73F8-F954-4D2C-A6C6-DF74DF94B5DD.jpeg",[651,173025,173026],{},[660,173027],{"alt":172981,"src":173028},"/images/newsletter/2022/08/01/66382F0C-ED2D-445C-9DAA-0455A0CFF4F3.jpeg",[651,173030,173031],{},[660,173032],{"alt":172981,"src":173033},"/images/newsletter/2022/08/01/518292D0-4241-4F16-A235-68A673B40395.jpeg",[651,173035,173036],{},[660,173037],{"alt":172981,"src":173038},"/images/newsletter/2022/08/01/023A3AAF-80F7-4258-8D52-CB0785A8AF5A.jpeg",[4542,173040,173042],{"id":173041},"springone-workshop","SpringOne Workshop",[651,173044,173045,173046,173050],{},"I have known about this for a while now and I might have even teased it out a couple of times. I’m so excited to announce that I will be giving an Introduction to Spring Boot workshop at SpringOne this year. Join me and my friend ",[812,173047,172677],{"href":173048,"rel":173049},"https://twitter.com/dashaun",[816]," as we help you get up and running with Spring Boot by learning the fundamentals of the Spring Framework.",[651,173052,173053],{},[812,173054,173055],{"href":173055,"rel":173056},"https://springone.io/2022/workshops/spring-boot",[816],[4542,173058,97345],{"id":97344},[651,173060,173061],{},"There are 2 new recordings up of Spring Office Hours. This is our chance to talk about what’s new in the Spring Community and answer any questions you have. I really enjoyed episode 09 where we had our friend Greg Turnquist join us to talk about Spring Data.",[5316,173063,173064,173069],{},[5332,173065,173066],{},[812,173067,172689],{"href":172689,"rel":173068},[816],[5332,173070,173071],{},[812,173072,173073],{"href":173073,"rel":173074},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0009/",[816],[5988,173076],{"id":173077},"p4gC7M6fjMU",[4542,173079,173081],{"id":173080},"kcdc","KCDC",[651,173083,173084],{},"I will be presenting at KCDC next week. If you're there please make sure to find me!",[4542,173086,157574],{"id":157573},[5909,173088,164959],{"id":69848},[5316,173090,173091,173098,173105,173112],{},[5332,173092,173093],{},[812,173094,173097],{"href":173095,"rel":173096},"https://devblogs.microsoft.com/java/java-on-visual-studio-code-update-july-2022/",[816],"Java on Visual Studio Code Update – July 2022",[5332,173099,173100],{},[812,173101,173104],{"href":173102,"rel":173103},"https://thenewstack.io/google-launches-carbon-an-experimental-replacement-for-c/",[816],"Google Launches Carbon, an Experimental Replacement for C++",[5332,173106,173107],{},[812,173108,173111],{"href":173109,"rel":173110},"https://openai.com/blog/dall-e-now-available-in-beta/",[816],"DALL E Now Available in Beta",[5332,173113,173114],{},[812,173115,173118],{"href":173116,"rel":173117},"https://maciejwalkowiak.com/blog/maven-dependencies-spring-boot-actuator-info/",[816],"Listing Maven dependencies in Spring Boot Actuator Info endpoint",[5909,173120,164971],{"id":157591},[5316,173122,173123,173130,173136],{},[5332,173124,173125],{},[812,173126,173129],{"href":173127,"rel":173128},"https://www.youtube.com/watch?v=xGBWppaDzjk",[816],"How to make an impact at a conference or event (if you are not a sponsor)",[5332,173131,173132],{},[812,173133,173134],{"href":173134,"rel":173135},"https://www.youtube.com/c/devnexus-conference/videos",[816],[5332,173137,173138],{},[812,173139,173142],{"href":173140,"rel":173141},"https://www.youtube.com/watch?v=eAQBWamiaVY",[816],"Ex-YouTube Employee Reveals How To Grow Your YouTube Channel",[5909,173144,164983],{"id":39439},[5316,173146,173147,173154],{},[5332,173148,173149],{},[812,173150,173153],{"href":173151,"rel":173152},"https://changelog.com/podcast/499",[816],"The Changelog - Love live RSS!",[5332,173155,173156],{},[812,173157,173160],{"href":173158,"rel":173159},"https://bootifulpodcast.fm/#/episodes/6fa8d806-2e62-4782-bd15-e232b1e22eee",[816],"Spring Cloud Kubernetes contributor Ryan Baxter",[5909,173162,164995],{"id":133422},[5316,173164,173165],{},[5332,173166,173167],{},[812,173168,173171],{"href":173169,"rel":173170},"https://github.com/carbon-language/carbon-lang",[816],"Carbon Language: An experimental successor to C++",[5909,173173,167862],{"id":160019},[5316,173175,173176],{},[5332,173177,173178],{},[812,173179,173182],{"href":173180,"rel":173181},"https://tanzu.vmware.com/developer/blog/this-week-in-spring-july-26th-2022/",[816],"This Week in Spring - July 26th, 2022",[5909,173184,165017],{"id":165016},[651,173186,173187],{},"“I've learned that people will forget what you said, people will forget what you did, but people will never forget how you made them feel.” - Maya Angelou",[5909,173189,165430],{"id":165429},[651,173191,173192],{},"Sad news from the Java world recently as we lost a special one 😢",[651,173194,173195],{},[812,173196,173197],{"href":173197,"rel":173198},"https://twitter.com/Java_Champions/status/1548737176841572352",[816],[4542,173200,157704],{"id":157703},[651,173202,166267,173203,166271],{},[812,173204,41499],{"href":44086,"rel":173205},[816],[651,173207,41105,173208,69920,173210,173212,173214,173216],{},[41107,173209],{},[41107,173211],{},[812,173213,161560],{"href":161111},[41107,173215],{},[812,173217,53869],{"href":82688,"rel":173218},[816],{"title":674,"searchDepth":790,"depth":790,"links":173220},[173221,173225,173226,173227,173228,173229,173238],{"id":170998,"depth":790,"text":170999,"children":173222},[173223,173224],{"id":172905,"depth":892,"text":172906},{"id":172926,"depth":892,"text":172927},{"id":172980,"depth":790,"text":172981},{"id":173041,"depth":790,"text":173042},{"id":97344,"depth":790,"text":97345},{"id":173080,"depth":790,"text":173081},{"id":157573,"depth":790,"text":157574,"children":173230},[173231,173232,173233,173234,173235,173236,173237],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":160019,"depth":892,"text":167862},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},"Happy Monday and welcome to another edition of the newsletter. I am back from a week in sunny HOT Florida. I spent a few days in the Atlantic Beach area for Code on the Beach and then my family and I drove down to Orlando to spend a few days at Disney World.",{"slug":170998,"date":173241},"2022-08-01T11:00:00.000Z","/newsletter/2022/08/01/code-on-the-beach",{"title":172861,"description":173239},"newsletter/2022/08/01/code-on-the-beach","VQo6hajdnhA93mOvClqBTDDW4RadbAkD-gPMRyBnKks",{"id":173247,"title":173248,"body":173249,"description":173253,"extension":793,"meta":173566,"navigation":797,"path":173569,"seo":173570,"stem":173571,"__hash__":173572},"content/newsletter/2022/08/15/kcdc-2022.md","KCDC 2022 Recap & My Presentation Tips",{"type":648,"value":173250,"toc":173548},[173251,173254,173256,173259,173262,173265,173268,173271,173274,173277,173280,173283,173286,173289,173293,173301,173304,173322,173325,173331,173334,173338,173341,173344,173347,173353,173357,173360,173365,173368,173372,173375,173380,173383,173388,173391,173396,173399,173425,173427,173429,173452,173454,173477,173479,173495,173497,173506,173508,173517,173519,173521,173526,173528,173530,173535],[651,173252,173253],{},"Happy Monday and welcome to another edition of the newsletter. Today’s newsletter will talk a lot about KCDC including highlights from my session, my presentation tips, and the travel nightmare I had on my way there. I hope you all had a great weekend and are ready to tackle a new week.",[4542,173255,173081],{"id":173080},[651,173257,173258],{},"Last week I traveled to Kansas City for the Kansas City Developer Conference. The conference was on Monday and Tuesday with the workshops concluding the conference on Wednesday. I was all booked to fly in early on Sunday because there was a speaker dinner on Sunday evening and I wanted to get my hands on some of that amazing KC BBQ.",[651,173260,173261],{},"My United flight was originally set to depart at 10:30 AM out of Cleveland heading to Chicago and my connection would leave around 12:30 for Kansas City. I woke up around 7 AM because I wanted to have coffee with my wife and spend some time with my kids before I left for a few days.",[651,173263,173264],{},"As soon as I got up a noticed that my flight had been delayed 1 hour. This wasn’t a big deal at the time but little did I know this was going to be the start of one of the longest travel days of my life. I arrived at the airport around 10:15 for an 11:30 departure which in Cleveland is enough time for me to get through security and get to my gate.",[651,173266,173267],{},"When I got to the gate and we were supposed to start boarding the captain was standing at the desk which wasn’t a good sign. He gave the announcement that there was bad weather in Chicago and that it was not looking good but he would give us another update around 12 PM. At this point I know I am probably not going to make my connection in Chicago so I asked the desk if there were any other flights from Chicago to KC today. There were 2 of them but they were all booked up 🤦♂️",[651,173269,173270],{},"At 12 they provided us with an update that we would depart around 3:30 EDT. With no flights from Chicago to KC, I decided to get my bag pulled off the plane. I immediately got on the phone with my company's travel agency but apparently, I wasn’t the only one having travel issues that day as I waited almost 90 minutes to talk to someone.",[651,173272,173273],{},"After a long wait an agent, I will call her Angel (because she saved my day and was so nice) was able to start looking for alternative flights. She kept finding options and the options kept getting booked by others in the same situation. I was really starting to worry that I wouldn’t make it there. Finally, she was able to get me booked on a flight at 6:15 into Atlanta which got me into KC at 10:40. I would miss the speaker dinner but at this point, I was just happy to have a plan.",[651,173275,173276],{},"While I am on the phone with Angel I am at the baggage carousel downstairs waiting for my bag to be pulled off the plane. After a while, I realize that this bag is not coming down here. I talk to someone at United and they confirm my suspicion, that the bag is on its way to Chicago. They assure me that it’s going to get on a plane heading for KC and should be there before I get there tonight.",[651,173278,173279],{},"When I landed in Atlanta I received a text that my connection from ATL → KC was delayed an hour 🤦♂️ At this point all I could do was laugh. Lucky for me Atlanta has a really great delta lounge so I decided to head straight there for dinner and a drink. After spending some time in the lounge I boarded my plane and was on my way to KC.",[651,173281,173282],{},"When I arrived I made my way down to baggage and asked someone in the Delta baggage area where United baggage was and of course, it was in another terminal. It’s 11:30 PM at this point and the airport is empty. I was told there were buses running but decided to just make the walk because who knows how many buses they have running at this point.",[651,173284,173285],{},"I got to the other terminal and to my disbelief my bag was actually there. Wow, something good finally happened today! Now it was time to find a ride to my hotel which was the Marriott downtown. There were no cabs anywhere to be found so I requested an Uber and nobody was picking me up. A few minutes later I got a ride but he was 20 minutes out. After waiting for him for 20 minutes and taking the 30-minute ride downtown I got checked into my hotel and got to bed at 1:15 AM.",[651,173287,173288],{},"I set my alarm for 6:30 because I wanted to get breakfast and get checked into the conference. I was excited to see some friends and learn, who needs sleep 🤷♂️",[5909,173290,173292],{"id":173291},"spring-recipes-a-collection-of-common-sense-solutions","Spring Recipes: A collection of common-sense solutions",[651,173294,173295,173296,173300],{},"I was really excited about this talk because it was my first time speaking with my good friend ",[812,173297,83167],{"href":173298,"rel":173299},"http://ntschutta.io/",[816],". The idea was simple, we would present a problem along with the solution and then discuss it further. This was going to be around anything Spring related and we wanted to have a mix of beginner and intermediate content.",[651,173302,173303],{},"I wanted to stress that there might be some problems that seem easy for some but new to others. We broke down the recipes into the following categories:",[5316,173305,173306,173309,173312,173315,173317,173319],{},[5332,173307,173308],{},"Getting Started with Spring Boot",[5332,173310,173311],{},"Building Web Applications",[5332,173313,173314],{},"Working with Databases",[5332,173316,100910],{},[5332,173318,27574],{},[5332,173320,173321],{},"Production",[651,173323,173324],{},"In each category, we had 4-7 problems and we took some time to discuss the solution. Half the battle is knowing which project or tool you need to reach for to solve your problem. I really hope everyone in attendance enjoyed our presentation. Below you can find the code and slides for this presentation.",[651,173326,173327],{},[812,173328,173329],{"href":173329,"rel":173330},"https://github.com/danvega/spring-recipes",[816],[651,173332,173333],{},"Special shoutout to Andrew Rubalcaba who was at the conference and took the time to sit in on our session. It was really great finally meeting him in person!",[5909,173335,173337],{"id":173336},"kansas-city-developers-conference","Kansas City Developers Conference",[651,173339,173340],{},"First off I want to thank all of the organizers and speakers who helped make this conference a great one. I was really honored to be among so many great speakers and talented developers.",[651,173342,173343],{},"I stayed at the Marriott downtown which was a really great hotel right across the street from the concentration center. The food in Kansas City was amazing and everyone I came in contact with was super friendly, it’s a midwest thing.",[651,173345,173346],{},"They have already announced that KCDC will be back in 2023 on June 22 & 23 with the workshops happening prior to the conference. I hope to be back in the great city of Kansas City next year and I hope to see you all there.",[651,173348,173349],{},[812,173350,173351],{"href":173351,"rel":173352},"https://twitter.com/kc_dc/status/1558155132545761280",[816],[4542,173354,173356],{"id":173355},"presentation-tips","Presentation tips",[651,173358,173359],{},"I like to think that I have learned a lot over the years when it comes to presenting so I thought I would take a few minutes and share my notes:",[651,173361,173362],{},[2939,173363,173364],{},"Your Number One Job",[651,173366,173367],{},"When you get accepted as a speaker you owe it to the audience to be prepared. The number one job you have as a speaker is to speak for the time allotted to you. This means that if they ask you to speak for 1 hour you need to do what you can to hit that mark. If you come up a few minutes short that is ok but if you’re finishing 20-30 min early that is a problem. Expect that you might speak a little bit faster in person than you did in run-throughs and have some material you can use if you need it.",[651,173369,173370],{},[2939,173371,73587],{},[651,173373,173374],{},"I am a big fan of dark mode but when it comes to in-person presentations it’s not always the most visually appealing. This is no more prevalent than when someone opens up a text editor with dark mode in a bright room. It becomes really hard to read the code and as soon as I can’t follow along with what you’re doing, you lost me. Turn to a light editor theme or at the very least talk to the AV people and try and get the lights above turned down.",[651,173376,173377],{},[2939,173378,173379],{},"Check out your room before your talk",[651,173381,173382],{},"It might be a good idea to check out the room you’re speaking in before you talk if you can. I like to watch the first presentation in that room if possible to see the lighting, find out if they are recording these sessions and how the podium setup is. If I am going to be live coding I need to find out if I am able to type on the podium and that always isn’t the case. If that happens you can talk to someone early and see if you can get a high-top table in there which is usually better for live coding.",[651,173384,173385],{},[2939,173386,173387],{},"The Speaker Room",[651,173389,173390],{},"At most conferences, they have a speaker room where speakers can work on their presentations. Take advantage of this room for a quiet place to run through your slides one last time.",[651,173392,173393],{},[2939,173394,173395],{},"The Checklist",[651,173397,173398],{},"I have a checklist that I use before any in-person presentation, YouTube video, live stream, etc… Make your own checklist and make sure you check it twice before giving your presentation. My list includes things like:",[5316,173400,173401,173404,173407,173410,173413,173416,173419,173422],{},[5332,173402,173403],{},"Turn on Do Not Disturb",[5332,173405,173406],{},"Screen Resolution",[5332,173408,173409],{},"My phone is out of sight",[5332,173411,173412],{},"The browser windows I need are open and ready to go",[5332,173414,173415],{},"If I’m live coding that first project is open and ready to go",[5332,173417,173418],{},"Sound is off",[5332,173420,173421],{},"Clicker works",[5332,173423,173424],{},"Slides are open and presenter mode is on",[4542,173426,157574],{"id":157573},[5909,173428,164959],{"id":69848},[5316,173430,173431,173438,173445],{},[5332,173432,173433],{},[812,173434,173437],{"href":173435,"rel":173436},"https://github.com/readme/featured/java-programming-language",[816],"Don’t call it a comeback: Why Java is still champ",[5332,173439,173440],{},[812,173441,173444],{"href":173442,"rel":173443},"https://springbootlearning.medium.com/jdbc-vs-jpa-623fe8258e8",[816],"JDBC vs JPA",[5332,173446,173447],{},[812,173448,173451],{"href":173449,"rel":173450},"https://jeremydmiller.com/2022/08/10/putting-solid-into-perspective/",[816],"Putting SOLID into perspective",[5909,173453,164971],{"id":157591},[5316,173455,173456,173463,173470],{},[5332,173457,173458],{},[812,173459,173462],{"href":173460,"rel":173461},"https://www.youtube.com/watch?v=AUyMX-mbLe8",[816],"Episode 3: Getting Started with SpringBoot and Oracle Database",[5332,173464,173465],{},[812,173466,173469],{"href":173467,"rel":173468},"https://www.youtube.com/watch?v=-n9H8KnYjVI",[816],"⚡️ Enlightning - What Are Cloud Native Buildpacks and How Do They Work?",[5332,173471,173472],{},[812,173473,173476],{"href":173474,"rel":173475},"https://www.youtube.com/watch?v=rxDVTocffdU",[816],"Simple, Secure API calls using a Vue 3 Composable",[5909,173478,164983],{"id":39439},[5316,173480,173481,173488],{},[5332,173482,173483],{},[812,173484,173487],{"href":173485,"rel":173486},"https://www.youtube.com/watch?v=WSflPE_oIRI",[816],"\"I Hate Agile!\" | Allen Holub On Why He Thinks Agile And Scrum Are Broken",[5332,173489,173490],{},[812,173491,173494],{"href":173492,"rel":173493},"https://anchor.fm/happypathprogramming/episodes/65-Rods-Gambit---Spring--Scala--TypeScript--and-Chess-with-Rod-Johnson-e1m50ui",[816],"Happy Path Programming: Rod's Gambit - Spring, Scala, TypeScript, and Chess with Rod Johnson",[5909,173496,165005],{"id":39340},[5316,173498,173499],{},[5332,173500,173501],{},[812,173502,173505],{"href":173503,"rel":173504},"https://learning.oreilly.com/library/view/kubernetes-up-and/9781098110192/",[816],"Kubernetes: Up and Running, 3rd Edition",[5909,173507,166601],{"id":21972},[5316,173509,173510],{},[5332,173511,173512],{},[812,173513,173516],{"href":173514,"rel":173515},"https://ted.dev/refactoring-to-hexagonal-architecture.html",[816],"Refactoring to Hexagonal Architecture",[5909,173518,167862],{"id":160019},[5909,173520,165017],{"id":165016},[1004,173522,173523],{},[651,173524,173525],{},"To achieve great things, two things are needed; a plan, and not quite enough time. - Leonard Bernstein",[5909,173527,165430],{"id":165429},[4542,173529,157704],{"id":157703},[651,173531,166267,173532,166271],{},[812,173533,41499],{"href":44086,"rel":173534},[816],[651,173536,41105,173537,69920,173539,173541,173543,173545],{},[41107,173538],{},[41107,173540],{},[812,173542,161560],{"href":161111},[41107,173544],{},[812,173546,53869],{"href":82688,"rel":173547},[816],{"title":674,"searchDepth":790,"depth":790,"links":173549},[173550,173554,173555,173565],{"id":173080,"depth":790,"text":173081,"children":173551},[173552,173553],{"id":173291,"depth":892,"text":173292},{"id":173336,"depth":892,"text":173337},{"id":173355,"depth":790,"text":173356},{"id":157573,"depth":790,"text":157574,"children":173556},[173557,173558,173559,173560,173561,173562,173563,173564],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":39340,"depth":892,"text":165005},{"id":21972,"depth":892,"text":166601},{"id":160019,"depth":892,"text":167862},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":173567,"date":173568},"kcdc-2022","2022-08-05T11:00:00.000Z","/newsletter/2022/08/15/kcdc-2022",{"title":173248,"description":173253},"newsletter/2022/08/15/kcdc-2022","T18u7PsoYZb9xYCsJ2isSVJ1V2tanOZ511JuAeSyMbU",{"id":173574,"title":173575,"body":173576,"description":173580,"extension":793,"meta":173831,"navigation":797,"path":173834,"seo":173835,"stem":173836,"__hash__":173837},"content/newsletter/2022/08/29/spring-security-changes.md","Spring Security, Office Hours, and Submitting Conference Talks",{"type":648,"value":173577,"toc":173816},[173578,173581,173590,173593,173597,173605,173615,173618,173621,173624,173626,173629,173632,173636,173639,173642,173645,173656,173669,173672,173674,173676,173699,173701,173724,173726,173749,173751,173765,173767,173776,173778,173787,173789,173796,173798,173803],[651,173579,173580],{},"Good morning friends and welcome to another edition of the newsletter. This past week I posted a new video on Spring Security changes that will affect anyone working on a new Spring Boot 2.7 project or later. There is also a new episode of Spring Office Hours and some changes to the show that we made this week. Finally, I am submitting talks to one of my favorite conferences this week, CodeMash. I thought I would take a little time to give you some tips and tricks I have learned this year about submitting conference talks.",[651,173582,173583,173584,173589],{},"If you follow me or this newsletter you know that I work for VMware. Our big conference, ",[812,173585,173588],{"href":173586,"rel":173587},"https://www.vmware.com/explore/us.html",[816],"VMware Explore"," is this week and some members of my team will be there. There are going to be tons of exciting talks and announcements this week in San Francisco and I hope everyone working and attending has a really great time.",[651,173591,173592],{},"I want to wish all of the speakers good luck and I will say a special prayer to the demo gods for all of you. I want to give a special shoutout to my friend DaShaun who will be speaking at the general session this morning. Not that he needs it, he is going to be amazing!",[4542,173594,173596],{"id":173595},"spring-security-without-the-webconfigureradapter","Spring Security without the WebConfigurerAdapter",[651,173598,173599,173600,173604],{},"I am working on a new guide for the ",[812,173601,97002],{"href":173602,"rel":173603},"https://www.notion.so/Spring-Security-Office-Hours-and-Submitting-Conference-Talks-58f7270c8df446ee9531a48d8a45c1d4",[816]," that I will be able to tell you more about in the next edition of this newsletter. It has to do with Spring Security and to be honest I have done a whole lot of work with it over the past year or so. This meant that I needed to brush up on the project and do a little learning last week which is honestly one of my favorite things to do.",[651,173606,173607,173608,51880,173612,173614],{},"In doing so I came across this article on some ",[812,173609,173611],{"href":169791,"rel":173610},[816],"changes that happened in Spring Security 5.7.0",[676,173613,40080],{}," is a class that you would extend to configure web security and it was being deprecated. If this was going away how would we write our configuration going forward? This was the question that I had researched and that blog post I mentioned above was my answer.",[651,173616,173617],{},"The video below is a tutorial that walks you through the old way to do it and what you should do going forward.",[5988,173619],{"id":173620},"s4X4SJv2RrU",[651,173622,173623],{},"I’m also trying to experiment with YouTube and one of the things I am trying to do is cut out as much fluff as possible. I feel like I got right to the point in this video and it seems to be doing very well in its first few days. Thank you to everyone who supports my videos and provides feedback. I love doing this and want to get better!",[4542,173625,170229],{"id":170228},[651,173627,173628],{},"We made a small change a couple of weeks ago and that was to add our personal YouTube channels to the stream. I want to try and attract more viewers to the live stream because one of the main components of the show is answering your questions. If we don’t have viewers we don’t have questions to answer. We got a pretty good turnout and some really great questions in this episode. My hope is with some more consistency we can start to grow those numbers in the near future. We will have a show this week but It will most likely be later this week as DaShaun is San Francisco for VMware Explore.",[5988,173630],{"id":173631},"4iA2AMZvivo",[4542,173633,173635],{"id":173634},"conference-call-for-papers-cfp","Conference Call For Papers (CFP)",[651,173637,173638],{},"There are a couple of conferences that I will be submitting proposals to this week and I thought I would take a minute to talk about them. First off I have learned a lot this year about CFPs both as a submitter and a reviewer.",[651,173640,173641],{},"I had the opportunity to be on the selection committee for SpringOne this year and I have a whole new level of respect for everyone who has to review proposals. There were so many great talks submitted and we had so many internal discussions about different titles, abstracts and if it fit the track that they were submitting to.",[651,173643,173644],{},"Through this process and a year of submitting talks (and mostly getting denied) I have learned a lot. I will try and put this into a longer form post at some point but here are my quick tips and tricks.",[5316,173646,173647,173650,173653],{},[5332,173648,173649],{},"Your title and description need to be exciting: This is your sales pitch and it needs to get the reviewer excited about the topic. Don’t go with “introduction to GraphQL” and expect someone to be excited about that.",[5332,173651,173652],{},"Humans are reading these proposals: There is no need to stuff keywords or buzz words throughout the abstract. It is most likely being read by a human being and needs to make sense to the person reading it.",[5332,173654,173655],{},"Provide Outcomes: What are attendees going to get out of your talk? There needs to be a reason and and outcome for attendees to want to signup and attend your talk.",[651,173657,173658,173659,173662,173663,173668],{},"I am submitting talks to ",[812,173660,97312],{"href":162046,"rel":173661},[816]," this week which is one of my favorite conferences right here in Ohio. If you’re on the fence I encourage you to submit a talk and if you want some help reviewing your abstract please let me know. I also found a conference called ",[812,173664,173667],{"href":173665,"rel":173666},"https://www.javaland.eu",[816],"JavaLand"," which I have never been to but looks like a great conference so I will submit a few there as well.",[651,173670,173671],{},"There are so many great speakers out there submitting to conferences and it’s really getting competitive. If you submit to a conference and get rejected, keep your head up. The best speakers in our industry are getting rejections and it’s ok. Move on to the next one and enjoy the process.",[4542,173673,157574],{"id":157573},[5909,173675,164959],{"id":69848},[5316,173677,173678,173685,173692],{},[5332,173679,173680],{},[812,173681,173684],{"href":173682,"rel":173683},"https://cguntur.me/2020/05/20/understanding-apache-maven-the-series/",[816],"Understanding Apache Maven - The Series",[5332,173686,173687],{},[812,173688,173691],{"href":173689,"rel":173690},"https://www.infoq.com/news/2022/08/spring-authorization-server-1-0/",[816],"Spring Authorization Server 1.0 Planned for November 2022",[5332,173693,173694],{},[812,173695,173698],{"href":173696,"rel":173697},"https://blog.heroku.com/next-chapter",[816],"Heroku’s Next Chapter",[5909,173700,164971],{"id":157591},[5316,173702,173703,173710,173717],{},[5332,173704,173705],{},[812,173706,173709],{"href":173707,"rel":173708},"https://www.youtube.com/watch?v=OedzEb5HVPs",[816],"⚡️ Enlightning - Now You Git It!",[5332,173711,173712],{},[812,173713,173716],{"href":173714,"rel":173715},"https://www.youtube.com/watch?v=xHminZ9Dxm4",[816],"Hibernate & JPA Tutorial - Crash Course",[5332,173718,173719],{},[812,173720,173723],{"href":173721,"rel":173722},"https://www.youtube.com/watch?v=bzdw4rpHSh8",[816],"15-factor cloud-native apps with Grace Jansen",[5909,173725,164983],{"id":39439},[5316,173727,173728,173735,173742],{},[5332,173729,173730],{},[812,173731,173734],{"href":173732,"rel":173733},"https://www.youtube.com/watch?v=dJArTS_xgcQ",[816],"Gary Vee’s NEW Strategies for Growing on YouTube and Social Media | #ThinkMediaPodcast #141",[5332,173736,173737],{},[812,173738,173741],{"href":173739,"rel":173740},"https://www.youtube.com/watch?v=WJtFW0o4Hho",[816],"Between Chair and Keyboard with Jonatan Ivanov",[5332,173743,173744],{},[812,173745,173748],{"href":173746,"rel":173747},"https://www.youtube.com/watch?v=cwGFgYNJzno",[816],"Between Chair and Keyboard with Rustam Mehmandarov",[5909,173750,164995],{"id":133422},[5316,173752,173753,173760],{},[5332,173754,173755],{},[812,173756,173759],{"href":173757,"rel":173758},"https://github.com/GoogleCloudPlatform/native-image-support-java",[816],"Native Image Support for Google Cloud Libraries",[5332,173761,173762],{},[812,173763,169735],{"href":169741,"rel":173764},[816],[5909,173766,165005],{"id":39340},[5316,173768,173769],{},[5332,173770,173771],{},[812,173772,173775],{"href":173773,"rel":173774},"https://www.manning.com/books/spring-security-in-action",[816],"Spring Security in Action",[5909,173777,167862],{"id":160019},[5316,173779,173780],{},[5332,173781,173782],{},[812,173783,173786],{"href":173784,"rel":173785},"https://github.blog/2022-08-25-open-source-monthly-august-2022-edition/",[816],"Github: Open Source Monthly: August 2022 Edition",[5909,173788,165017],{"id":165016},[651,173790,173791,173792,173795],{},"“Judging a Person Does Not Define Who They Are, It Defines ",[2939,173793,173794],{},"Who You Are",".”",[4542,173797,157704],{"id":157703},[651,173799,166267,173800,166271],{},[812,173801,41499],{"href":44086,"rel":173802},[816],[651,173804,41105,173805,69920,173807,173809,173811,173813],{},[41107,173806],{},[41107,173808],{},[812,173810,161560],{"href":161111},[41107,173812],{},[812,173814,53869],{"href":82688,"rel":173815},[816],{"title":674,"searchDepth":790,"depth":790,"links":173817},[173818,173819,173820,173821,173830],{"id":173595,"depth":790,"text":173596},{"id":170228,"depth":790,"text":170229},{"id":173634,"depth":790,"text":173635},{"id":157573,"depth":790,"text":157574,"children":173822},[173823,173824,173825,173826,173827,173828,173829],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":160019,"depth":892,"text":167862},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":173832,"date":173833},"spring-security-changes","2022-08-29T10:00:00.000Z","/newsletter/2022/08/29/spring-security-changes",{"title":173575,"description":173580},"newsletter/2022/08/29/spring-security-changes","6eywF8wmjJluhJksK_lmuqvJ74N7sAu2dMFePQxaABo",{"id":173839,"title":173840,"body":173841,"description":173845,"extension":793,"meta":174170,"navigation":797,"path":174173,"seo":174174,"stem":174175,"__hash__":174176},"content/newsletter/2022/09/12/spring-boot-jwt.md","Securing REST APIs with JWTs and YouTube is adding paid courses!",{"type":648,"value":173842,"toc":174151},[173843,173846,173850,173853,173856,173859,173865,173869,173871,173873,173882,173885,173889,173909,173912,173915,173918,173925,173928,173932,173935,173943,173946,173952,173955,173958,173961,173963,173970,173972,173974,174004,174006,174036,174038,174054,174056,174065,174067,174083,174085,174101,174103,174119,174121,174124,174130,174132,174138],[651,173844,173845],{},"Happy Monday friends and welcome to another edition of the newsletter. This week I have a new new content on securing REST endpoints in Spring Boot with JWTs, upcoming talks, VMware Explore and YouTube is adding paid courses. As always I included some of the best things I found on the web over the last couple of weeks.",[4542,173847,173849],{"id":173848},"current-status","Current Status",[651,173851,173852],{},"If you’re new around here I am a huge fan of Cleveland sports and for the first time since 2004, the Cleveland Browns are 1-0. Hard to believe that it's been that long since they won the first game of the year but also not that hard to consider if you pay attention to football 🤦♂️",[651,173854,173855],{},"My daughter starts pre-k school and this year she has moved up to the 4-year-old classroom. We were really excited to find out that she will be in the same class as 2 of her best friends which always makes it easier. I’m excited for her because she loves to be around other kids and just like her dad she loves to learn.",[651,173857,173858],{},"I think I speak for all parents who work from home in saying I look forward to the house being a little bit calmer so I can get some work done. Next year the 2-year-old will be 3 and they will both be in school 🤩",[651,173860,173861],{},[660,173862],{"alt":173863,"src":173864},"Bella Starts PreK","/images/newsletter/2022/09/12/bella-prek.jpeg",[4542,173866,173868],{"id":173867},"spring-security-json-web-tokens-jwt","Spring Security & JSON Web Tokens (JWT)",[651,173870,89145],{},[651,173872,89148],{},[651,173874,173875,173876,173881],{},"You will find the video below but I also ",[812,173877,173880],{"href":173878,"rel":173879},"https://www.danvega.dev/blog/2022/09/06/spring-security-jwt/",[816],"created a blog post"," for this content as a place you can always reference back to. This video has done better than any other video on my channel in the first three days of its release. Thank you again to everyone who continues to support my content, I truly appreciate it.",[5988,173883],{"id":173884},"KYNR5js2cXE",[4542,173886,173888],{"id":173887},"upcoming-talks","Upcoming Talks",[5316,173890,173891,173899,173902],{},[5332,173892,173893,173898],{},[812,173894,173897],{"href":173895,"rel":173896},"https://tanzu.vmware.com/content/webinars/sep-22-spring-recipes-a-collection-of-common-sense-solutions",[816],"Sept 22nd - Spring Recipes: A collection of common sense solutions","\nIn this session, you will learn about common situations you will encounter as a developer and how Spring can make the right thing to do the easy thing to do. Cutting through the cruft, you will leave armed with practical code examples you can use on your projects.",[5332,173900,173901],{},"Oct 5th: Creating GraphQL APIs in Java\nI am giving a lunch and learn to over 100 developers for an awesome Fortune 500 company. In this talk I am going to introduce them to GraphQL and how they can start using it today in their Java / Spring applications.",[5332,173903,173904,173908],{},[812,173905,173907],{"href":173055,"rel":173906},[816],"Dec 6th - 8th: SpringOne Workshop","\nIn this workshop we are going to introduce you to Spring Boot. If you’re new to Spring and what to learn what types of applications you can build and how to get started this is the workshop for you. By the end of this session you will be able to build your first Spring Boot application.",[651,173910,173911],{},"If you would like me to speak at your conference, meetup group or company wide meeting please feel free to reach out to me.",[4542,173913,173588],{"id":173914},"vmware-explore",[651,173916,173917],{},"I work for VMware and our big conference, VMware Explore was a couple of weeks ago. First off I had some major FOMO as I saw all of the pictures and videos hitting Twitter. I hope I am able to make it next year and be a part of this amazing conference.",[651,173919,173920,173921,173924],{},"There were lots of exciting product announcements that came out of the conference but I was tuning in for one really exciting one. My good friend and coworker ",[812,173922,101262],{"href":173048,"rel":173923},[816]," was on the main stage for the general session to help announce VMware Aria. It was so exciting to see DaShuan on the big stage where he belongs and he absolutely crushed it!",[5988,173926],{"id":173927},"yDpRDh9Lt_A",[4542,173929,173931],{"id":173930},"youtube-courses","YouTube Courses?",[651,173933,173934],{},"YouTube is already the best platform for both producing and consuming educational content on the web. It currently isn’t a place where creators like myself can host paid content though. This might all be changing soon.",[651,173936,173937,173938,173942],{},"Right now I have a handful of ",[812,173939,173941],{"href":100862,"rel":173940},[816],"paid courses"," on Udemy. While I haven’t created a new one in a while they are still doing pretty well on the platform. When you don’t have a huge audience you need a platform that can give you audience and that is what Udemy has done for me.",[651,173944,173945],{},"With that said I am huge fan of YouTube and the platform it gives me. I love putting free content on there and I am going on all in on YouTube. I first heard that YouTube might be offering paid courses through a tweet from Roberto Blake who is about as plugged into the YouTube community as anyone:",[651,173947,173948],{},[812,173949,173950],{"href":173950,"rel":173951},"https://twitter.com/robertoblake/status/1568235062331670528",[816],[651,173953,173954],{},"Next I saw a similar message from Robert Kyncl who is YouTube's Chief Business Officer. Coincidently the question Robert was answering was from Roberto as well.",[5988,173956],{"id":173957},"0e2m9DOY0ec",[651,173959,173960],{},"I’m really excited about this prospect and can’t wait to see this start rolling out. Does anyone know of any creators that already have paid courses on YouTube? What are your thoughts on this feature?",[4542,173962,82113],{"id":82579},[651,173964,173965,173966,173969],{},"Our long-running ",[812,173967,18205],{"href":82111,"rel":173968},[816]," is coming up December 6th to 8th, in San Francisco. There is going to be an amazing lineup of speakers and sessions and we are back in person. I will be giving a workshop on getting started with Spring Boot and hopefully a general session. You can get $200 off when registering if you use the code S1VM22_Advocate_200.",[4542,173971,157574],{"id":157573},[5909,173973,164959],{"id":69848},[5316,173975,173976,173983,173990,173997],{},[5332,173977,173978],{},[812,173979,173982],{"href":173980,"rel":173981},"https://medium.com/javarevisited/understanding-logging-in-spring-boot-ac0fd79177b4",[816],"Understanding logging in Spring Boot",[5332,173984,173985],{},[812,173986,173989],{"href":173987,"rel":173988},"https://snyk.io/blog/password-hashing-java-applications/",[816],"How to do password hashing in Java applications the right way!",[5332,173991,173992],{},[812,173993,173996],{"href":173994,"rel":173995},"https://deno.com/blog/v1.25",[816],"Deno 1.25 Release Notes",[5332,173998,173999],{},[812,174000,174003],{"href":174001,"rel":174002},"https://dzone.com/articles/jpa-constructor",[816],"Should We Have a Constructor on JPA",[5909,174005,164971],{"id":157591},[5316,174007,174008,174015,174022,174029],{},[5332,174009,174010],{},[812,174011,174014],{"href":174012,"rel":174013},"https://www.infoq.com/articles/james-ward-java-jvm-languages/",[816],"Java Champion James Ward on the State of Java and JVM Languages",[5332,174016,174017],{},[812,174018,174021],{"href":174019,"rel":174020},"https://vimeo.com/748032161",[816],"What makes a good developer- (-Christin Gorman)",[5332,174023,174024],{},[812,174025,174028],{"href":174026,"rel":174027},"https://vimeo.com/748031614",[816],"Jakarta EE 10 - Feature by Feature-(Ivar Grimstad)",[5332,174030,174031],{},[812,174032,174035],{"href":174033,"rel":174034},"https://vimeo.com/748031405",[816],"Das Boot- Diving into Debugging Spring Boot Applications- (Mark Heckler)",[5909,174037,164983],{"id":39439},[5316,174039,174040,174047],{},[5332,174041,174042],{},[812,174043,174046],{"href":174044,"rel":174045},"https://www.youtube.com/watch?v=tM79Z08WDkM",[816],"Between Chair and Keyboard with Soumik Majumder",[5332,174048,174049],{},[812,174050,174053],{"href":174051,"rel":174052},"https://syntax.fm/show/506/big-deno-changes",[816],"Sytax.fm - Big Deno Changes",[5909,174055,164995],{"id":133422},[5316,174057,174058],{},[5332,174059,174060],{},[812,174061,174064],{"href":174062,"rel":174063},"https://bloggingfordevs.com/",[816],"Blogging for Devs",[5909,174066,165005],{"id":39340},[5316,174068,174069,174076],{},[5332,174070,174071],{},[812,174072,174075],{"href":174073,"rel":174074},"https://pragprog.com/titles/vsjava2e/functional-programming-in-java-second-edition/",[816],"Functional Programming in Java, Second Edition",[5332,174077,174078],{},[812,174079,174082],{"href":174080,"rel":174081},"https://amzn.to/3qwWBM0",[816],"Create Something Awesome: How Creators are Profiting from Their Passion in the Creator Economy",[5909,174084,167862],{"id":160019},[5316,174086,174087,174094],{},[5332,174088,174089],{},[812,174090,174093],{"href":174091,"rel":174092},"https://tanzu.vmware.com/content/josh-blog/this-month-in-spring-august-2022",[816],"This Month in Spring - August 2022",[5332,174095,174096],{},[812,174097,174100],{"href":174098,"rel":174099},"https://www.infoq.com/news/2022/08/java-news-roundup-aug22-2022/",[816],"InfoQ Java Roundup - August 2022",[5909,174102,169309],{"id":79608},[5316,174104,174105,174112],{},[5332,174106,174107],{},[812,174108,174111],{"href":174109,"rel":174110},"https://viteconf.org/",[816],"ViteConf",[5332,174113,174114],{},[812,174115,174118],{"href":174116,"rel":174117},"https://www.linkedin.com/pulse/github-universe-coming-github/",[816],"Github Universe is Coming!",[5909,174120,165430],{"id":165429},[651,174122,174123],{},"Navigating tech as a woman from the great Angie Jones",[651,174125,174126],{},[812,174127,174128],{"href":174128,"rel":174129},"https://twitter.com/techgirl1908/status/1563289706195849218",[816],[4542,174131,157704],{"id":157703},[651,174133,174134,174135,166271],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of the newsletter and I will see you in the next one. If you have any links you would like me to include please ",[812,174136,41499],{"href":44086,"rel":174137},[816],[651,174139,41105,174140,69920,174142,174144,174146,174148],{},[41107,174141],{},[41107,174143],{},[812,174145,161560],{"href":161111},[41107,174147],{},[812,174149,53869],{"href":82688,"rel":174150},[816],{"title":674,"searchDepth":790,"depth":790,"links":174152},[174153,174154,174155,174156,174157,174158,174159,174169],{"id":173848,"depth":790,"text":173849},{"id":173867,"depth":790,"text":173868},{"id":173887,"depth":790,"text":173888},{"id":173914,"depth":790,"text":173588},{"id":173930,"depth":790,"text":173931},{"id":82579,"depth":790,"text":82113},{"id":157573,"depth":790,"text":157574,"children":174160},[174161,174162,174163,174164,174165,174166,174167,174168],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":160019,"depth":892,"text":167862},{"id":79608,"depth":892,"text":169309},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":174171,"date":174172},"spring-boot-jwt","2022-09-12T12:00:00.000Z","/newsletter/2022/09/12/spring-boot-jwt",{"title":173840,"description":173845},"newsletter/2022/09/12/spring-boot-jwt","iLPvjQbN9_u-VULACpT1hCM3BFhLpznJQ1XBWXCzNo8",{"id":174178,"title":174179,"body":174180,"description":174184,"extension":793,"meta":174522,"navigation":797,"path":174524,"seo":174525,"stem":174526,"__hash__":174527},"content/newsletter/2022/09/19/youtube-shorts.md","YouTube Shorts, Office Hours and an Embarrassing Loss",{"type":648,"value":174181,"toc":174503},[174182,174185,174187,174194,174197,174200,174206,174208,174211,174213,174220,174223,174225,174228,174231,174234,174240,174243,174245,174248,174251,174269,174273,174276,174326,174328,174351,174354,174358,174367,174370,174373,174376,174380,174386,174388,174390,174411,174413,174436,174438,174468,174470,174480,174482,174488],[651,174183,174184],{},"Welcome back to another edition of the newsletter. This week I have a lot of new content to share with you, I’m working on a new Spring Boot Tutorial for YouTube and Adobe buys Figma.",[4542,174186,173849],{"id":173848},[651,174188,174189,174190,664],{},"Last week was a busy week and this week isn’t slowing down. I have a presentation coming up on Thursday with my coworker and friend Nate Schutta. We are going to present on Spring Recipes where we show a problem and then how you can go about solving it in Spring. If you’re interested in attending you can ",[812,174191,174193],{"href":173895,"rel":174192},[816],"register for free here",[651,174195,174196],{},"I’m currently facing an issue with my Sony 6100 and overheating during my live streams. I think this issue has to do with me messing with my settings and turning the quality up to 4k for my local recordings. After doing some research I found out that this could be causing my issues. I am going to try and run a test this week before my presentation to see if I can correct this. I am curious to hear from my audience though, has anyone else run into this issue?",[651,174198,174199],{},"Finally, I have to start my week off with the embarrassing end to the Browns game yesterday. They were up 13 points with less than 2 minutes to go and found a way to lose that game. It is really hard to be a Browns fan and this quote from the late great Les Levine sums it up:",[651,174201,174202],{},[812,174203,174204],{"href":174204,"rel":174205},"https://twitter.com/RuiterWrongFAN/status/1571620668797587457",[816],[4542,174207,43654],{"id":43653},[651,174209,174210],{},"I recently reorganized my office to be more functional when it comes to creating content and it’s starting to pay off. While I was able to create some pretty solid content this week I know there is plenty of room for improvement and I am looking forward to learning and getting better. If you’re interested in what I have been doing to my office let me know and I will share it one week in this newsletter.",[5909,174212,97345],{"id":97344},[651,174214,174215,174216,174219],{},"DaShaun and I got back to streaming and had a successful ",[812,174217,97345],{"href":97000,"rel":174218},[816],". What I love most about these shows is we get to hear from you and find out what problems you’re facing. We took a lot of notes from this show and have some plans to address some of the questions in the near future. The big thing we want to do with this show is getting more consistent and we have scheduled the next 3 shows. For now, we will go live on Tuesday afternoons at 3:30 EDT. Here is the replay of Episode 11:",[5988,174221],{"id":174222},"79cNoyXhHNU",[5909,174224,15432],{"id":61224},[651,174226,174227],{},"As I write this, I just realized that I posted 6 new videos to YouTube this week. While 3 of them were YouTube Shorts this type of productivity is just not sustainable but regardless it was a fun week.",[651,174229,174230],{},"In this tutorial, I answered a question I got from a subscriber. He needed to be able to consume a third-party API, save that information to a database and then send it to another service. I recreated this flow into a fun little tutorial that I hope answered the question.",[5988,174232],{"id":174233},"XEtPVm_SL2Q",[651,174235,174236,174237,664],{},"I have been publishing a lot of tutorials related to Spring Security lately. In almost all of those demos, I use an in-memory user because it’s quick and I am trying to keep these tutorials on the topic. While this is great for demos it isn’t going to be used in the real world. In this tutorial, I discuss how to authenticate against a database using the ",[676,174238,174239],{},"JdbcUserDetailsManager",[5988,174241],{"id":174242},"d7ZmZFbE_qY",[5909,174244,165117],{"id":165116},[651,174246,174247],{},"I consume content from a lot of the big YouTubers out there and all of them are telling me that I don’t want to miss the short-form content train. Knowing that I decided to get on board and at least see what kind of content I can produce and if it helps out at all.",[651,174249,174250],{},"After a few videos, the one thing I really enjoy is that it has to be less than 60 seconds which means I can shoot, edit and publish videos much faster. The more videos I work on the more editing experience I gain. The challenge I am finding right now is that it’s really hard to record my screen in 9:16 format but I managed to get a tutorial in this week and it turned out ok.",[5316,174252,174253,174262],{},[5332,174254,174255],{},[2939,174256,174257],{},[812,174258,174261],{"href":174259,"rel":174260},"https://youtube.com/shorts/Ch_Q1mdZjuQ",[816],"My #1 tip for anyone who wants to learn how to code 👨💻👩💻",[5332,174263,174264],{},[812,174265,174268],{"href":174266,"rel":174267},"https://youtube.com/shorts/JiDjWX4pzWo",[816],"Create a REST API in Java using Spring Boot in less than 60 seconds",[5909,174270,174272],{"id":174271},"spring-boot-tutorial","Spring Boot Tutorial",[651,174274,174275],{},"I created a post on YouTube asking my subscribers so I thought I would also pose this question to you. I am working on an outline for a big tutorial that I would like to release on YouTube. This is probably going to be a pretty long video but I want to target developers new to Spring. The idea is here are all the basics you need to know to create and deploy your first application. This is a rough outline of what I have so far:",[5316,174277,174278,174281,174283,174286,174289,174303,174305,174308,174311,174321,174323],{},[5332,174279,174280],{},"Why Spring?",[5332,174282,100885],{},[5332,174284,174285],{},"What is Spring used for?",[5332,174287,174288],{},"Create a new project",[5332,174290,174291,174292],{},"Spring Framework Core\n",[5316,174293,174294,174297,174300],{},[5332,174295,174296],{},"Application Context",[5332,174298,174299],{},"Beans",[5332,174301,174302],{},"IoC / Dependency Injection",[5332,174304,56408],{},[5332,174306,174307],{},"Externalized Configuration",[5332,174309,174310],{},"CRUD RestController",[5332,174312,174313,174314],{},"Connect to a database\n",[5316,174315,174316,174318],{},[5332,174317,101023],{},[5332,174319,174320],{},"PostgreSQL (Docker Compose)",[5332,174322,27574],{},[5332,174324,174325],{},"Deployment",[4542,174327,173888],{"id":173887},[5316,174329,174330,174337,174342,174345],{},[5332,174331,174332],{},[812,174333,174336],{"href":174334,"rel":174335},"https://youtu.be/mquczsmTIm8",[816],"Sept 20th - Spring Office Hours",[5332,174338,174339,173898],{},[812,174340,173897],{"href":173895,"rel":174341},[816],[5332,174343,174344],{},"Oct 5th: Creating GraphQL APIs in Java\nI am giving a lunch and learn to over 100 developers for an awesome Fortune 500 company. In this talk, I am going to introduce them to GraphQL and how they can start using it today in their Java / Spring applications.",[5332,174346,174347,174350],{},[812,174348,173907],{"href":173055,"rel":174349},[816],"\nIn this workshop, we are going to introduce you to Spring Boot. If you’re new to Spring and what to learn what types of applications you can build and how to get started this is the workshop for you. By the end of this session, you will be able to build your first Spring Boot application.",[651,174352,174353],{},"If you would like me to speak at your conference, meetup group or company-wide meeting please feel free to reach out to me.",[4542,174355,174357],{"id":174356},"adobe-acquires-figma","Adobe Acquires Figma",[651,174359,174360,174361,174366],{},"The big news that dropped last week was that ",[812,174362,174365],{"href":174363,"rel":174364},"https://news.adobe.com/news/news-details/2022/Adobe-to-Acquire-Figma/default.aspx",[816],"Adobe was purchasing Figma",". I heard a lot of fans of Figma up in arms that the big bad evil empire of Adobe purchased their favorite tool and they were going to run it into the ground. First off, I don’t think they paid 20 billion dollars to run it into the ground. Second, I saw the following excerpt in an email from Adobe:",[1004,174368,174369],{},"While we have been reducing our investment in XD, we will continue to support it. We are excited about Figma's vision for the future of product design and the potential of our teams coming together to benefit our customers.",[651,174371,174372],{},"The reason I bring this up today is because I am a huge fan of Adobe XD. I use it to create all of my YouTube thumbnails, images for my website and any promo graphics for courses or social media. I started using it because it was included in my Adobe subscription and then grew to love it. I’m sad that it might be going away but I’m also excited that a popular tool like Figma might be part of my subscription going forward.",[651,174374,174375],{},"I happen to have a lot of love for Adobe and the people that work there. I wish them nothing but continued success and congratulate them and the Figma team on this acquisition.",[4542,174377,174379],{"id":174378},"springone-2022","SpringOne 2022",[651,174381,173965,174382,174385],{},[812,174383,18205],{"href":82111,"rel":174384},[816]," is coming up December 6th to 8th, in San Francisco. There will be an amazing lineup of speakers and sessions and we are back in person. I will be giving a workshop on getting started with Spring Boot and hopefully a general session. You can get $200 off when registering if you use the code S1VM22_Advocate_200.",[4542,174387,157574],{"id":157573},[5909,174389,164959],{"id":69848},[5316,174391,174392,174397,174404],{},[5332,174393,174394],{},[812,174395,174357],{"href":174363,"rel":174396},[816],[5332,174398,174399],{},[812,174400,174403],{"href":174401,"rel":174402},"https://spring.io/blog/2022/09/16/spring-cloud-sleuth-opentelemetry-otel-1-1-0-has-been-released",[816],"Spring Cloud Sleuth OpenTelemetry (OTel) 1.1.0 Has Been Released",[5332,174405,174406],{},[812,174407,174410],{"href":174408,"rel":174409},"https://spring.io/blog/2022/09/15/spring-framework-6-0-0-m6-and-5-3-23-available-now",[816],"Spring Framework 6.0.0-M6 and 5.3.23 are available now",[5909,174412,164971],{"id":157591},[5316,174414,174415,174422,174429],{},[5332,174416,174417],{},[812,174418,174421],{"href":174419,"rel":174420},"https://www.youtube.com/watch?v=LGOhejS1Itc",[816],"Kubernetes Native Java by Josh Long",[5332,174423,174424],{},[812,174425,174428],{"href":174426,"rel":174427},"https://www.youtube.com/watch?v=FMZckqbPGq0",[816],"Welcome, Spring for GraphQL by Rossen Stoyanchev @ Spring I/O 2022",[5332,174430,174431],{},[812,174432,174435],{"href":174433,"rel":174434},"https://nipafx.dev/inside-java-newscast-32/",[816],"Java Newscast #32",[5909,174437,164983],{"id":39439},[5316,174439,174440,174447,174454,174461],{},[5332,174441,174442],{},[812,174443,174446],{"href":174444,"rel":174445},"https://badass.dev/podcast/course-builders",[816],"badass.dev podcasts",[5332,174448,174449],{},[812,174450,174453],{"href":174451,"rel":174452},"https://spring.io/blog/2022/09/08/a-bootiful-podcast-hashicorp-s-rosemary-wang-on-securing-the-intersection-of-apps-and-ops-with-hashicorp-vault",[816],"Hashicorp's Rosemary Wang on securing the intersection of apps and ops with Hashicorp Vault",[5332,174455,174456],{},[812,174457,174460],{"href":174458,"rel":174459},"https://bootifulpodcast.fm/#/episodes/486db005-2e7f-4c64-a6a5-c4835a525b5b",[816],"Big data legend, former Pivot, and friend to the Spring community, Tim Spann",[5332,174462,174463],{},[812,174464,174467],{"href":174465,"rel":174466},"https://www.youtube.com/watch?v=7vXgOw6KMeQ",[816],"Between Chair and Keyboard with Justin Reock",[5909,174469,167862],{"id":160019},[5316,174471,174472],{},[5332,174473,174474,174479],{},[812,174475,174478],{"href":174476,"rel":174477},"https://spring.io/blog/2022/09/13/this-week-in-spring-september-13th-2022",[816],"This Week in Spring - September 13th, 2022"," (Thanks for the shout-outs Josh!)",[4542,174481,157704],{"id":157703},[651,174483,174484,174485,166271],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of the newsletter and I will talk to you in the next one. If you have any links you would like me to include please ",[812,174486,41499],{"href":44086,"rel":174487},[816],[651,174489,41105,174490,69920,174492,174494,174496,174498,174501],{},[41107,174491],{},[41107,174493],{},[812,174495,161560],{"href":161111},[41107,174497],{},[812,174499,53869],{"href":53869,"rel":174500},[816],[41107,174502],{},{"title":674,"searchDepth":790,"depth":790,"links":174504},[174505,174506,174512,174513,174514,174515,174521],{"id":173848,"depth":790,"text":173849},{"id":43653,"depth":790,"text":43654,"children":174507},[174508,174509,174510,174511],{"id":97344,"depth":892,"text":97345},{"id":61224,"depth":892,"text":15432},{"id":165116,"depth":892,"text":165117},{"id":174271,"depth":892,"text":174272},{"id":173887,"depth":790,"text":173888},{"id":174356,"depth":790,"text":174357},{"id":174378,"depth":790,"text":174379},{"id":157573,"depth":790,"text":157574,"children":174516},[174517,174518,174519,174520],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":160019,"depth":892,"text":167862},{"id":157703,"depth":790,"text":157704},{"slug":165116,"date":174523},"2022-09-19T10:00:00.000Z","/newsletter/2022/09/19/youtube-shorts",{"title":174179,"description":174184},"newsletter/2022/09/19/youtube-shorts","XvNSuxLbPJQ_N1f8WaoVHHkWeSoq1rCmmiMv5e8PK1Y",{"id":174529,"title":174530,"body":174531,"description":174535,"extension":793,"meta":174907,"navigation":797,"path":174910,"seo":174911,"stem":174912,"__hash__":174913},"content/newsletter/2022/09/26/spring-one-announcements.md","SpringOne, Java 19, CJUG and new content",{"type":648,"value":174532,"toc":174885},[174533,174536,174538,174541,174550,174553,174556,174559,174562,174564,174573,174576,174582,174591,174595,174603,174612,174615,174645,174648,174651,174654,174660,174663,174666,174672,174674,174677,174679,174682,174685,174689,174692,174695,174697,174700,174716,174720,174738,174741,174747,174749,174751,174792,174794,174808,174810,174833,174835,174842,174844,174852,174854,174857,174859,174865,174867,174872],[651,174534,174535],{},"Happy Monday and welcome to another edition of the newsletter. It was another busy week here as the weather has started to cool off and we are officially into fall 🍁 In this week's newsletter, I want to talk about the upcoming SpringOne conference, Java 19, a Java User Group meeting I attended, and a bunch of new content that I was able to put out there.",[4542,174537,173849],{"id":173848},[651,174539,174540],{},"If you have been paying attention this is the 3rd week in a row that I am releasing a newsletter after telling everyone that I was moving to a 2-week cadence. If you haven’t noticed, what am I doing here? I love to write so when I have something to talk about I will tell you about it. This means that my official cadence has moved to “Whenever I have something to share with you”.",[651,174542,174543,174544,174549],{},"I’m currently reading ",[812,174545,174548],{"href":174546,"rel":174547},"https://amzn.to/3fiiJad",[816],"Deep Work by Cal Newport"," and what a great time this book has come into my life. I found Cal through his podcast and YouTube channel where he often talks about time management and productivity. Deep work is the ability to focus without distraction on a cognitively demanding task.",[651,174551,174552],{},"I have gotten into a good grove at work and find myself taking on as much as I can and I like to keep busy. With that though I really need to be focused, prioritize my work and come up with a good quarterly, weekly, and daily plan. This has helped me shift from being reactive to proactive. I’m not longer heading into a workday asking myself what’s next, I have that ready to go and my days planned out as much as possible.",[651,174554,174555],{},"There is just something about reading or listening to someone who has devoted so much time and research to a particular subject. I’m enjoying binging on all of his content right now and so far his book is exactly what I hoped it would be.",[651,174557,174558],{},"I also want to give a huge shout out to the Cleveland Guardians who did what nobody thought they could do and that is win the division. They are the youngest team in all of baseball and the 2nd lowest payroll in the league. I can’t wait for the playoffs to start and see what kind of October magic they can put together.",[651,174560,174561],{},"I have a lot to share with you so let's get into it.",[4542,174563,82113],{"id":82579},[651,174565,174566,174567,174572],{},"This week we made the first of our announcements around speakers, workshops, and sessions. If you haven’t had a chance you should head over to ",[812,174568,174571],{"href":174569,"rel":174570},"http://www.springone.io",[816],"www.springone.io"," and check out the content. The list of speakers, sessions, and workshops is one of the best I have ever seen. I seriously can’t wait to be in San Francisco and learn from some of the best in our community.",[651,174574,174575],{},"In that list of speakers and sessions, you will find me presenting on Spring for GraphQL. I’m so excited to be giving this presentation at SpringOne and have already begun working on it. I have a lot of content on GraphQL and spent the year talking about it on the SpringOne tour but this presentation will be an updated one.",[651,174577,174578],{},[660,174579],{"alt":174580,"src":174581},"Join me at SpringOne","/images/newsletter/2022/09/26/spring-one-join-me.png",[651,174583,174584,174585,174590],{},"I’m also giving a workshop on getting started with Spring Boot which you can now ",[812,174586,174589],{"href":174587,"rel":174588},"https://springone.io/2022/workshops/spring-boot-beginner",[816],"register for",". You can get $200 off when registering if you use the code S1VM22_Advocate_200.",[4542,174592,174594],{"id":174593},"java-19","Java 19",[651,174596,174597,174598,174602],{},"Java 19 was released last week and what an exciting time it is to be a Java developer. Ever since Java has moved to a new release cadence we get a new version every 6 months that is packed with new features, performance & security improvements, and did I mention we get all of this for ",[7300,174599,174600],{},[2939,174601,43837],{},"? Hats off 🧢 to everyone who helped make the newest version of Java possible.",[651,174604,174605,174606,174611],{},"Java 19 is packed full of some big features and while a lot of them are incubator or preview they will shape the future of Java. We have features that full under project Amber and Panama but the highlight of this release is Project Loom. ",[812,174607,174610],{"href":174608,"rel":174609},"https://openjdk.org/jeps/425",[816],"JEP 425 brings"," us Virtual Threads in preview mode. Virtual Threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications.",[651,174613,174614],{},"I am really looking forward to learning more about Virtual Threads and seeing what the Java community does with them. I have some resources below that will help you get caught up with everything new in Java 19.",[5316,174616,174617,174624,174631,174638],{},[5332,174618,174619],{},[812,174620,174623],{"href":174621,"rel":174622},"https://openjdk.org/projects/jdk/19/",[816],"Open JDK 19",[5332,174625,174626],{},[812,174627,174630],{"href":174628,"rel":174629},"https://www.youtube.com/watch?v=6pN0Ymsl1H0",[816],"Moving Java Forward with Java 19",[5332,174632,174633],{},[812,174634,174637],{"href":174635,"rel":174636},"https://www.infoq.com/news/2022/09/java19-released",[816],"InfoQ: Java 19 Released",[5332,174639,174640],{},[812,174641,174644],{"href":174642,"rel":174643},"https://foojay.io/today/the-5-most-pivotal-and-innovative-additions-to-openjdk-19/",[816],"The 5 Most Pivotal and Innovative Additions to OpenJDK 19",[4542,174646,97315],{"id":174647},"cleveland-java-user-group",[651,174649,174650],{},"This months topic at the Cleveland Java User Group was a deep dive into Java Concurrency. We were lucky to have Daniel Mikusa and Scott Seighman both present on the topic. Dan went through and walk through the basics of how concurrency works in Java and I thought it was a really great introduction to something that can be a pretty complicated topic.",[651,174652,174653],{},"Next, right on topic Scott was able to break down the new Virtual Threads feature in Java 19. I thought his examples were a really great introduction to what you can do with them.",[651,174655,174656],{},[660,174657],{"alt":174658,"src":174659},"Virtual Threads (Preview) ","/images/newsletter/2022/09/26/virtual-threads.png",[651,174661,174662],{},"Virtual Threads (Preview)",[651,174664,174665],{},"If you want to check out the demos you can visit the following Github Repo:",[651,174667,174668],{},[812,174669,174670],{"href":174670,"rel":174671},"https://github.com/dmikusa/java-concurrency-demo/",[816],[4542,174673,82720],{"id":47833},[651,174675,174676],{},"Another busy week producing content and preparing for a bunch of content I will be focusing on in the near future. I hope you’re learning some new 🤩 and enjoying what I am getting out there.",[5909,174678,97345],{"id":97344},[651,174680,174681],{},"In this weeks episode DaShuan and I took a look at what’s new in the Spring community and answered your questions. We also had an opportunity to show some demos of things we are currently working on. I’m also pleased to report back that my camera did not overheat this week. It was defiantly due to me changing the recording settings to 4k which is ok for normal videos but not great for streaming.",[5988,174683],{"id":174684},"mquczsmTIm8",[5909,174686,174688],{"id":174687},"h2-database-spring-boot","H2 Database Spring Boot",[651,174690,174691],{},"In this tutorial, you will learn everything you need to know to connect and configure an in-memory H2 database in Spring Boot. I will also walk you through what you need to look out for if you are using Spring Security in your application.",[5988,174693],{"id":174694},"PSrHcCwvfVQ",[5909,174696,165117],{"id":165116},[651,174698,174699],{},"I am really having fun putting short form videos together. Obviously the shorter videos take less time to produce and edit but they also give me a chance to get in front of a larger audience. Seeing how much YouTube is investing in shorts it just makes sense for me to create both short and long form content. Here are a coupe of shorts I published last week:",[5316,174701,174702,174709],{},[5332,174703,174704],{},[812,174705,174708],{"href":174706,"rel":174707},"https://youtube.com/shorts/KirPiS-M_Gg?feature=share",[816],"Java 19 Short",[5332,174710,174711],{},[812,174712,174715],{"href":174713,"rel":174714},"https://youtube.com/shorts/BP71pgx1bi4?feature=share",[816],"SpringOne 2022 Short",[4542,174717,174719],{"id":174718},"upcoming-talks-content","Upcoming Talks / Content",[5316,174721,174722,174725,174732],{},[5332,174723,174724],{},"Oct 5th: Creating GraphQL APIs in Java I am giving a lunch and learn to over 100 developers for an awesome Fortune 500 company. In this talk, I am going to introduce them to GraphQL and how they can start using it today in their Java / Spring applications.",[5332,174726,174727],{},[812,174728,174731],{"href":174729,"rel":174730},"https://springone.io/2022/sessions/a-gentle-introduction-to-spring-for-graphql",[816],"SpringOne - A Gentle Introduction to Spring for GraphQL",[5332,174733,174734],{},[812,174735,174737],{"href":174587,"rel":174736},[816],"SpringOne Workshop - Getting Started with Spring Boot",[651,174739,174740],{},"I’m working on a YouTube video that will teach the basics of SpringBoot. I put this poll out there. What do you think is better? 1 long video or a playlist of multiple videos. I’m not sure on the length yet but it could be in the 3-4 hour range. If you have thoughts on this please feel to reply to this email or reach out to me on Twitter.",[651,174742,174743],{},[812,174744,174745],{"href":174745,"rel":174746},"https://twitter.com/therealdanvega/status/1573744850117316608",[816],[4542,174748,157574],{"id":157573},[5909,174750,164959],{"id":69848},[5316,174752,174753,174759,174766,174771,174778,174785],{},[5332,174754,174755],{},[812,174756,174637],{"href":174757,"rel":174758},"https://www.infoq.com/news/2022/09/java19-released/",[816],[5332,174760,174761],{},[812,174762,174765],{"href":174763,"rel":174764},"https://inside.java/2022/09/20/the-arrival-of-java-19/",[816],"The arrival of Java 19",[5332,174767,174768],{},[812,174769,174644],{"href":174642,"rel":174770},[816],[5332,174772,174773],{},[812,174774,174777],{"href":174775,"rel":174776},"https://blog.youtube/news-and-events/supporting-the-next-wave-of-creative-entrepreneurs/",[816],"Made on YouTube: supporting the next wave of creative entrepreneurs",[5332,174779,174780],{},[812,174781,174784],{"href":174782,"rel":174783},"https://spring.io/blog/2022/09/19/spring-data-rest-vulnerability-cve-2022-31679",[816],"Spring Data REST Vulnerability (CVE-2022-31679)",[5332,174786,174787],{},[812,174788,174791],{"href":174789,"rel":174790},"https://jakartaee-ambassadors.io/2022/09/22/jakarta-ee-10-released/",[816],"Jakarta 10 has Landed!",[5909,174793,164971],{"id":157591},[5316,174795,174796,174801],{},[5332,174797,174798],{},[812,174799,174630],{"href":174628,"rel":174800},[816],[5332,174802,174803],{},[812,174804,174807],{"href":174805,"rel":174806},"https://www.youtube.com/watch?v=h6TrvCV3NdU",[816],"Made on YouTube: New ways to join YPP, Shorts Monetization & Creator Music",[5909,174809,164983],{"id":39439},[5316,174811,174812,174819,174826],{},[5332,174813,174814],{},[812,174815,174818],{"href":174816,"rel":174817},"https://inside.java/2022/09/20/podcast-026/",[816],"Inside Java Podcast: Java 19 is here",[5332,174820,174821],{},[812,174822,174825],{"href":174823,"rel":174824},"https://www.youtube.com/watch?v=VgfoqtPNA8s",[816],"Between Chair and Keyboard with Simon Brown",[5332,174827,174828],{},[812,174829,174832],{"href":174830,"rel":174831},"https://bootifulpodcast.fm/#/episodes/6b898e9a-c89f-449f-ae33-8c744c713402",[816],"Bootiful Podcast: Couchbase and Cloud legend Laurent Doguin",[5909,174834,164995],{"id":133422},[5316,174836,174837],{},[5332,174838,174839],{},[812,174840,174623],{"href":174621,"rel":174841},[816],[5909,174843,165005],{"id":39340},[5316,174845,174846],{},[5332,174847,174848],{},[812,174849,174851],{"href":174546,"rel":174850},[816],"Deep Work - Cal Newport",[5909,174853,165017],{"id":165016},[651,174855,174856],{},"“The happiest, most passionate employees are not those who followed their passion into a position, but instead those who have been around long enough to become good at what they do. On reflection, this makes sense. - Cal Newport”",[5909,174858,165430],{"id":165429},[651,174860,174861],{},[812,174862,174863],{"href":174863,"rel":174864},"https://twitter.com/devnexus/status/1571861700071940096",[816],[4542,174866,157704],{"id":157703},[651,174868,174484,174869,166271],{},[812,174870,41499],{"href":44086,"rel":174871},[816],[651,174873,41105,174874,69920,174876,174878,174880,174882],{},[41107,174875],{},[41107,174877],{},[812,174879,161560],{"href":161111},[41107,174881],{},[812,174883,53869],{"href":53869,"rel":174884},[816],{"title":674,"searchDepth":790,"depth":790,"links":174886},[174887,174888,174889,174890,174891,174896,174897,174906],{"id":173848,"depth":790,"text":173849},{"id":82579,"depth":790,"text":82113},{"id":174593,"depth":790,"text":174594},{"id":174647,"depth":790,"text":97315},{"id":47833,"depth":790,"text":82720,"children":174892},[174893,174894,174895],{"id":97344,"depth":892,"text":97345},{"id":174687,"depth":892,"text":174688},{"id":165116,"depth":892,"text":165117},{"id":174718,"depth":790,"text":174719},{"id":157573,"depth":790,"text":157574,"children":174898},[174899,174900,174901,174902,174903,174904,174905],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":174908,"date":174909},"spring-one-announcements","2022-09-26T17:00:00.000Z","/newsletter/2022/09/26/spring-one-announcements",{"title":174530,"description":174535},"newsletter/2022/09/26/spring-one-announcements","CDONsdDOWt501InX_T8PkgLnCYnVpQD5WIniNyVqc0Y",{"id":174915,"title":174916,"body":174917,"description":174921,"extension":793,"meta":175426,"navigation":797,"path":175429,"seo":175430,"stem":175431,"__hash__":175432},"content/newsletter/2022/10/03/accepted-to-codemash.md","Accepted to CodeMash, Learning Spring, and new content",{"type":648,"value":174918,"toc":175400},[174919,174922,174924,174927,174930,174936,174939,174942,174945,174949,174952,174955,174966,174969,174972,174975,174998,175001,175004,175027,175036,175039,175042,175055,175058,175061,175064,175067,175070,175073,175075,175106,175108,175111,175113,175122,175148,175151,175154,175157,175160,175163,175168,175171,175175,175178,175181,175183,175191,175198,175205,175211,175213,175216,175222,175226,175229,175234,175236,175265,175267,175269,175299,175301,175324,175326,175335,175337,175346,175348,175364,175366,175369,175371,175374,175380,175382,175387],[651,174920,174921],{},"Happy Monday and welcome to another edition of the newsletter. Are you reading this? If so and you have any feedback for me on ways to improve this newsletter please feel free to reply to this email or reach out to me on Twitter. Today I want to talk to you about being accepted to CodeMash, How to learn Spring, and lots of new content.",[4542,174923,173849],{"id":173848},[651,174925,174926],{},"Throughout the week I collect things I want to talk about in Notion. This is the tool I use to collect all of my thoughts, write articles and newsletters, plan videos, and schedule all of the content I am working on. I tell you this because it’s currently Friday afternoon and this is when I begin to collect these thoughts and turn them into the newsletter you’re reading now.",[651,174928,174929],{},"As I began typing this out I received the exciting news that I have been accepted to speak at CodeMash 2023. I will be giving a talk on GraphQL and I’m also giving a workshop on Getting Started with Spring. I immediately brought this excellent graphic into Notion and so I am sharing that with you.",[651,174931,174932],{},[660,174933],{"alt":174934,"src":174935},"Codemash 2023","/images/newsletter/2022/10/03/codemash.jpeg",[651,174937,174938],{},"I am beginning to submit to conferences for next year and it’s always tough. You have to put yourself out there and unless you’re a top speaker in our industry you’re going to get more declines than acceptances. It is a really competitive space right now and I get that, it comes with the position I am in.",[651,174940,174941],{},"However when I do get accepted to talk you can bet I am going to celebrate them. Make sure you stop to celebrate your wins friends. A big thank you to CodeMash for having me back and accepting my talks. I haven’t given a precompiled at CodeMash before and I am beyond excited to talk about my favorite topic, Spring!",[651,174943,174944],{},"I can’t wait to be back at Kalahari Resort next year surrounded by some amazing people. I’m also looking forward to telling the kids, they love that place 🤩",[4542,174946,174948],{"id":174947},"learning-spring","Learning Spring",[651,174950,174951],{},"I often get asked at conferences, meetups, on Twitter, or via email “What is the best way to learn Spring?”. This is one of those questions that I get often and I need to work on some content so I can point people towards that as my official answer. I’m going to brain dump some initial thoughts here but I would love to hear your feedback.",[651,174953,174954],{},"I think when someone tells me they want to learn Spring my first question to them is why do you want to Spring? There isn’t a wrong answer here but it's a pretty loaded question so I need a little more information to answer it. The answers I usually get look something like this:",[27665,174956,174957,174960,174963],{},[5332,174958,174959],{},"I’m new to Java and I want to learn how to build web applications.",[5332,174961,174962],{},"My job has asked me to learn it.",[5332,174964,174965],{},"I want to get a job and Spring is in demand.",[651,174967,174968],{},"The reason I ask this is that Spring can be used to build a wide range of applications and provide solutions to a variety of problems. When I want to learn something new I start with my why? Why do I want to learn this thing? This allows me to narrow my focus. If you wanted to learn to speak another language to visit a country vs learning another a language to become a teacher those are 2 entirely different paths.",[651,174970,174971],{},"When I want to learn a new programming language, framework or tool having an end goal gives me clarity and makes the path towards that goal a straight one. Let’s say for example I wanted to learn Go. Learning an entire programming language can be a little overwhelming but if my goal was focused on learning Go to so that I could write a REST API that seems more manageable.",[651,174973,174974],{},"The reason you want to narrow your focus is that Spring as a solution to a number of problems. You can use Spring to build :",[5316,174976,174977,174980,174983,174986,174989,174992,174995],{},[5332,174978,174979],{},"Web Applications (Synchronous & Asynchronous)",[5332,174981,174982],{},"Cloud Applications",[5332,174984,174985],{},"Microservices (Distributed Applications)",[5332,174987,174988],{},"Serverless",[5332,174990,174991],{},"Event Driven Applications",[5332,174993,174994],{},"Batch",[5332,174996,174997],{},"CLIs",[651,174999,175000],{},"When you have it narrowed down to what you’re going to learn Spring for I like to tackle this problem like I do any other problem, by breaking it down into a series of smaller problems. Let’s say that for example, you wanted to learn Spring to build a REST API. While this might seem like a simple problem statement we can actually break this down into a series of problems.",[651,175002,175003],{},"Your first tasks might be to learn the mechanics of how to build out a REST Controller in Spring. As you start building this out you might realize you also need to understand the following:",[5316,175005,175006,175009,175012,175015,175018,175021,175024],{},[5332,175007,175008],{},"How to add logging to your application.",[5332,175010,175011],{},"How Dependency Injection works in Spring.",[5332,175013,175014],{},"How to validate incoming data.",[5332,175016,175017],{},"How to connect to a database.",[5332,175019,175020],{},"How to configure properties in your application.",[5332,175022,175023],{},"How to write Unit & Integration tests.",[5332,175025,175026],{},"and more…",[651,175028,175029,175030,175035],{},"As you start learning about Spring you will hear the terms Spring Framework and Spring Boot. This might be pretty confusing at first, are these 2 different frameworks? These are valid questions and I promise this will make more sense in time. My friend Mark Heckler has a ",[812,175031,175034],{"href":175032,"rel":175033},"https://www.youtube.com/watch?v=Spzug_SjJnM",[816],"really great video"," on this and I suggest starting there.",[651,175037,175038],{},"Most people like to jump into official documentation and while our documentation is really good this can be overwhelming with a project as large as Spring. Not to mention now that you know there is the Spring Framework, Spring Boot and all of the projects in the ecosystem that is a lot of documentation to go through.",[651,175040,175041],{},"I like to start with a guide that tells me how to perform a specific task. We have 2 collections of guides that you can use for free and I think are a great place to get started:",[5316,175043,175044,175050],{},[5332,175045,175046],{},[812,175047,175049],{"href":78015,"rel":175048},[816],"Spring IO Guides",[5332,175051,175052],{},[812,175053,97002],{"href":101172,"rel":175054},[816],[651,175056,175057],{},"You can find a specific guide for creating a REST API. This will walk you through step-by-step on the mechanics of creating a REST API and what to expect along the way. Once you have that down you can find guides for each of the other tasks you have identified to complete your application.",[651,175059,175060],{},"If you get stuck on any of these guides you could always look for another tutorial either in written form or on YouTube. Just make sure that you are searching for the exact thing you are trying to do and not go down the rabbit hole of watching hours and hours of videos.",[651,175062,175063],{},"My biggest problem with tutorials is that you can’t just follow along with the author and then call it day. Take what you have just learned and build something completely different. If the tutorial you were watching used the domain of a ToDo list build something related to something you’re interested in. The constant repetition of taking what you learn, applying it, failing and figuring out what went wrong is where you will learn the most.",[651,175065,175066],{},"After you have spent some time building some of the different parts to your application this is now a good time to check out the official documentation. For me personally this is where I will usually jump all the way in.",[651,175068,175069],{},"I will pick up a book or 2 but I treat these as reference materials and read specific chapters to accomplish a task but I avoid reading it cover to cover. I also find some YouTubers who are big in that space and binge on their content. Finally, I am big on consuming content on Twitter so I will find people I can connect with there. This will also lead me to other blogs, videos, podcasts etc..",[651,175071,175072],{},"As I said from the start, this is just my initial brain dump of thoughts on the subject. I will try and put together a more thought out response to this question but for now I will leave you with a few resources go get you started.",[5909,175074,21931],{"id":21930},[5316,175076,175077,175083,175090,175096,175101],{},[5332,175078,175079],{},[812,175080,175082],{"href":101139,"rel":175081},[816],"Spring Framework Reference Documentation",[5332,175084,175085],{},[812,175086,175089],{"href":175087,"rel":175088},"https://docs.spring.io/spring-boot/docs/current/reference/html/",[816],"Spring Boot Reference Documentation",[5332,175091,175092],{},[812,175093,166746],{"href":175094,"rel":175095},"https://spring.io/blog",[816],[5332,175097,175098],{},[812,175099,97002],{"href":168725,"rel":175100},[816],[5332,175102,175103],{},[812,175104,169735],{"href":169741,"rel":175105},[816],[4542,175107,82720],{"id":47833},[651,175109,175110],{},"Another busy week for me producing content. We had a great guest during Spring Office hours this week and I feel like this show is really starting to take off. If you count the replay of that show (I do) I was able to post 5 videos to YouTube this week. Yes, 1 was a YouTube Short and 1 was a replay of a live stream but that is impressive and unsustainable. I’m really just trying to find my feet here and find a number that",[5909,175112,97345],{"id":97344},[651,175114,175115,175116,175121],{},"Last week during Spring Office Hours we welcomed in our guest, ",[812,175117,175120],{"href":175118,"rel":175119},"https://twitter.com/jonatan_ivanov",[816],"Jonatan Ivanov",". Jonatan is on the Spring Engineering team and focuses on Observability. I was really interested in chatting with him because I feel like this is an area in Spring that I need to learn more about. A few things stuck out for me.",[5316,175123,175124,175136,175139],{},[5332,175125,175126,175127],{},"The 3 pillars of observability are:\n",[27665,175128,175129,175131,175133],{},[5332,175130,56408],{},[5332,175132,40367],{},[5332,175134,175135],{},"Distributed Tracing",[5332,175137,175138],{},"If you're currently using Spring Cloud Sleuth that will be going away in Spring Boot 3 and the core of that project is being moved to Micrometer Tracing.",[5332,175140,175141,175142,175147],{},"If you want to learn more check out the following ",[812,175143,175146],{"href":175144,"rel":175145},"https://github.com/micrometer-metrics/micrometer-samples",[816],"repository on Github"," to see some of the examples Jonatan went through during our live stream.",[5988,175149],{"id":175150},"zgaAEZ4Y_uI",[5909,175152,175153],{"id":92885},"Spring Security CORS",[651,175155,175156],{},"The first video I published this week was in response to a question I have been receiving a lot lately. The question was about the different ways to configure CORS and specifically when Spring Security is a part of your application.",[5988,175158],{"id":175159},"HRwlT_etr60",[5909,175161,175162],{"id":93824},"Domain Class Converter",[651,175164,175165,175166,664],{},"Next up I came across this handy class in Spring Data called the Domain Class Converter. It allows you to take an id and automatically convert it to a domain object by using Spring Data’s ",[676,175167,16365],{},[5988,175169],{"id":175170},"_QBe2ZiXV-0",[5909,175172,175174],{"id":175173},"spring-security-jpa","Spring Security JPA",[651,175176,175177],{},"I have been putting out a lot of content lately on Spring Security and this was the next progression of that material. In previous videos, I took a look at how to configure an in-memory user and connect to a database using JDBC. There is no built-in mechanism for authenticating against a database using Spring Data JPA but it’s fairly simple to do when you understand what you need to do.",[5988,175179],{"id":175180},"awcCiqBO36E",[5909,175182,165117],{"id":165116},[651,175184,175185,175186,175190],{},"I mentioned this last week but I am really enjoying creating short-form videos. They take less time to film, edit and produce and it’s a great way to practice. Java 19 was recently released and I thought I would put together a quick video on how to install it using ",[812,175187,175189],{"href":121585,"rel":175188},[816],"SdkMan",". If you’re not aware SdkMan is a really great tool for managing parallel versions of SDKs.",[651,175192,175193],{},[812,175194,175197],{"href":175195,"rel":175196},"https://youtube.com/shorts/VZoqz9PPHNs?feature=share",[816],"The best and fastest way to Install Java 19 #shorts",[651,175199,175200,175201,175204],{},"Speaking of Java 19, did you see that it is now an option over at ",[812,175202,77478],{"href":30440,"rel":175203},[816]," 🤩",[651,175206,175207],{},[660,175208],{"alt":175209,"src":175210},"Java 19 - Spring Initializr","/images/newsletter/2022/10/03/java-19-spring.png",[5909,175212,15432],{"id":61224},[651,175214,175215],{},"Just to close out this section on YouTube. I ended up posting a total of 17 videos in the month of September and your support means the world to me. With some consistency and talking about topics like Spring Security that more of you are searching for I had my best month ever. I ended up adding 1,000+ subscribers over the last 28 days and that is just mind blowing 🤯 to me. I’m closing in on 20,000 and have a goal of hitting it before the year ends!",[651,175217,175218],{},[660,175219],{"alt":175220,"src":175221},"YouTube Stats","/images/newsletter/2022/10/03/youtube-stats.png",[5909,175223,175225],{"id":175224},"spring-recipes-recording","Spring Recipes Recording",[651,175227,175228],{},"A couple of weeks ago my friend Nate Schutta and I gave an online presentation “Spring Recipes: A collection of common-sense solutions”. This was a similar talk to the one that we gave at KCDC over the summer and it was fun to give this talk another spin. We could seriously write a book (should we) with the amount of content we have piled up on this talk. If you want to watch the recording you can do so using the link below.",[651,175230,175231],{},[812,175232,173895],{"href":173895,"rel":175233},[816],[4542,175235,173888],{"id":173887},[5316,175237,175238,175241,175246,175252,175259],{},[5332,175239,175240],{},"Oct 5th: Creating GraphQL APIs in Java I am giving a lunch and learning to over 100 developers for an awesome Fortune 500 company. In this talk, I am going to introduce them to GraphQL and how they can start using it today in their Java / Spring applications.",[5332,175242,175243],{},[812,175244,174731],{"href":174729,"rel":175245},[816],[5332,175247,175248],{},[812,175249,175251],{"href":174587,"rel":175250},[816],"SpringOne - Getting Started with Spring Boot Workshop",[5332,175253,175254,175258],{},[812,175255,175257],{"href":162046,"rel":175256},[816],"CodeMash 2023",": Give your APIs a REST & Make the move to GraphQL",[5332,175260,175261,175264],{},[812,175262,175257],{"href":162046,"rel":175263},[816],": Getting Started with Spring",[4542,175266,157574],{"id":157573},[5909,175268,164959],{"id":69848},[5316,175270,175271,175278,175285,175292],{},[5332,175272,175273],{},[812,175274,175277],{"href":175275,"rel":175276},"https://spring.io/blog/2022/09/26/native-support-in-spring-boot-3-0-0-m5",[816],"Native Support in Spring Boot 3.0.0-M5",[5332,175279,175280],{},[812,175281,175284],{"href":175282,"rel":175283},"https://spring.io/blog/2022/09/23/my-springone-2022",[816],"My SpringOne - Josh Long",[5332,175286,175287],{},[812,175288,175291],{"href":175289,"rel":175290},"https://www.infoq.com/news/2022/09/spring-boot-migrator",[816],"Introducing Spring Boot Migrator",[5332,175293,175294],{},[812,175295,175298],{"href":175296,"rel":175297},"https://github.blog/2022-09-14-8-things-you-didnt-know-you-could-do-with-github-copilot/",[816],"8 things you didn’t know you could do with GitHub Copilot",[5909,175300,164971],{"id":157591},[5316,175302,175303,175310,175317],{},[5332,175304,175305],{},[812,175306,175309],{"href":175307,"rel":175308},"https://www.youtube.com/watch?v=byNh7sWJnRE",[816],"Knative and Spring Native - Bringing back the func by Thomas Vitale and Mauricio Salatino",[5332,175311,175312],{},[812,175313,175316],{"href":175314,"rel":175315},"https://www.youtube.com/watch?v=vmS9vNbta-w",[816],"IntelliJ IDEA Conf 2022 (day 1)",[5332,175318,175319],{},[812,175320,175323],{"href":175321,"rel":175322},"https://www.youtube.com/watch?v=-8epeIFdKWo",[816],"IntelliJ IDEA Conf 2022 (day 2)",[5909,175325,164983],{"id":39439},[5316,175327,175328],{},[5332,175329,175330],{},[812,175331,175334],{"href":175332,"rel":175333},"https://www.youtube.com/watch?v=6wJ69TPWRZg",[816],"Between Chair and Keyboard with Coté",[5909,175336,165005],{"id":39340},[5316,175338,175339],{},[5332,175340,175341,69050,175344],{},[812,175342,174851],{"href":174546,"rel":175343},[816],[7300,175345,164925],{},[5909,175347,167862],{"id":160019},[5316,175349,175350,175357],{},[5332,175351,175352],{},[812,175353,175356],{"href":175354,"rel":175355},"https://tanzu.vmware.com/content/blog/this-month-in-spring-september-2022",[816],"This Month in Spring - September 2022",[5332,175358,175359],{},[812,175360,175363],{"href":175361,"rel":175362},"https://blog.jetbrains.com/idea/2022/10/java-annotated-monthly-october-2022/",[816],"Java Annotated Monthly – October 2022",[5909,175365,165017],{"id":165016},[651,175367,175368],{},"\"I am a great believer in luck, and I find the harder I work, the more I have of it.”",[5909,175370,165430],{"id":165429},[651,175372,175373],{},"Thanks for this great little tip Marcio!",[651,175375,175376],{},[812,175377,175378],{"href":175378,"rel":175379},"https://twitter.com/marcioendo/status/1574375251521294341",[816],[4542,175381,157704],{"id":157703},[651,175383,174484,175384,166271],{},[812,175385,41499],{"href":44086,"rel":175386},[816],[651,175388,41105,175389,69920,175391,175393,175395,175397],{},[41107,175390],{},[41107,175392],{},[812,175394,161560],{"href":161111},[41107,175396],{},[812,175398,53869],{"href":53869,"rel":175399},[816],{"title":674,"searchDepth":790,"depth":790,"links":175401},[175402,175403,175406,175415,175416,175425],{"id":173848,"depth":790,"text":173849},{"id":174947,"depth":790,"text":174948,"children":175404},[175405],{"id":21930,"depth":892,"text":21931},{"id":47833,"depth":790,"text":82720,"children":175407},[175408,175409,175410,175411,175412,175413,175414],{"id":97344,"depth":892,"text":97345},{"id":92885,"depth":892,"text":175153},{"id":93824,"depth":892,"text":175162},{"id":175173,"depth":892,"text":175174},{"id":165116,"depth":892,"text":165117},{"id":61224,"depth":892,"text":15432},{"id":175224,"depth":892,"text":175225},{"id":173887,"depth":790,"text":173888},{"id":157573,"depth":790,"text":157574,"children":175417},[175418,175419,175420,175421,175422,175423,175424],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":39340,"depth":892,"text":165005},{"id":160019,"depth":892,"text":167862},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":175427,"date":175428},"accepted-to-codemash","2022-10-03T17:00:00.000Z","/newsletter/2022/10/03/accepted-to-codemash",{"title":174916,"description":174921},"newsletter/2022/10/03/accepted-to-codemash","XlfLRk0Z43O26QNd5ikDE6avIJ53g6msc59gQHFYq5k",{"id":175434,"title":175435,"body":175436,"description":175440,"extension":793,"meta":175856,"navigation":797,"path":175859,"seo":175860,"stem":175861,"__hash__":175862},"content/newsletter/2022/10/17/spring-to-production.md","Spring Data, Security, and taking your apps to Production",{"type":648,"value":175437,"toc":175838},[175438,175441,175443,175446,175455,175458,175464,175469,175473,175482,175489,175496,175499,175502,175515,175518,175520,175528,175536,175553,175556,175559,175563,175572,175575,175581,175585,175598,175610,175614,175617,175686,175688,175710,175712,175714,175748,175750,175780,175782,175805,175807,175810,175812,175818,175820,175825],[651,175439,175440],{},"Happy Monday and welcome to another edition of the newsletter. After missing a week I’m excited to be back and sharing some updates with you. In today’s newsletter I want to talk about Spring Data, Spring Security, A GraphQL testing tip, and taking your Spring apps to Production.",[4542,175442,173849],{"id":173848},[651,175444,175445],{},"I’m writing this status update on Friday while I am enjoying a random day off. VMware gives us an EPIC (Execution/Passion/Integrity/Customers/Community) day off each quarter. This is a chance for us to unplug, unwind and spend the day doing something that makes us happy. My wife is currently spending some time in Arizona which means I am solo for a few days and enjoying some alone time with my daughters. I’m mentioning this because I absolutely love the company I work for. They really look out for you and where core values might have been some words in a document in the past I feel like they really stand for them here.",[651,175447,175448,175449,175454],{},"While I am talking about VMware I was able to purchase a new ",[812,175450,175453],{"href":175451,"rel":175452},"https://amzn.to/3eCJcQ3",[816],"NordicTrack Treadmill"," this year thanks to our well-being benefit. I prefer to run outside but as soon as it's raining or it is cold I turn into a baby and would much rather be inside. This treadmill is my absolute favorite piece of fitness equipment I have ever owned. I really love the iFit program where I can run with a coach in amazing places all over the world. This is mostly why I joined CrossFit way back in the day so that someone else could tell me what to do. I’m currently going through a 10k training series with my sight set on a half or full marathon next year.",[651,175456,175457],{},"I wanted to bring up running for a couple of reasons. This weekend I am heading down to Ocean Isle NC for a wedding so if you have any running recommendations please let me know. I am staying on the ocean so my plan is to run along the ocean but you never know if the sand is going to be runnable or too deep. The other reason is that DaShaun and I are throwing around the idea of putting together a run at SpringOne. This will probably be a 5k but you can run at your own pace or walk. If you’re interested please let me know.",[651,175459,175460,175461,664],{},"Finally, YouTube rolled out handles this week which is a new way to identify your YouTube channel. If you want to visit my YouTube channel you can now get to it using my handle, ",[2939,175462,175463],{},"danvega",[651,175465,175466],{},[812,175467,101295],{"href":101295,"rel":175468},[816],[4542,175470,175472],{"id":175471},"dependency-injection-in-spring","Dependency Injection in Spring",[651,175474,175475,175476,175481],{},"I recently had the opportunity to collaborate on a guide with my coworker ",[812,175477,175480],{"href":175478,"rel":175479},"https://twitter.com/mariogray",[816],"Mario Gray",". With some upcoming workshops on Getting Started with Spring Boot, I thought this would be a great opportunity to talk about some of the core concepts in Spring starting with Dependency Injection.",[651,175483,175484,175485,175488],{},"The guide is available at the ",[812,175486,97002],{"href":168725,"rel":175487},[816]," where you will find tons of free guides, articles, videos, and workshops. The guide starts off by explaining what Dependency Injection is and why you should care about it. From there you will go into building a real application that will demonstrate the issues you will run into without DI and how to solve them in Spring using Dependency Injection.",[651,175490,175491,175492,175495],{},"I also have the video version of this recorded so if you haven’t already subscribed to my ",[812,175493,101297],{"href":101295,"rel":175494},[816]," you should do so now and hit that notification icon!",[4542,175497,105038],{"id":175498},"spring-data-jdbc",[651,175500,175501],{},"If you have been following my tutorials you will know that I have used Spring Data JDBC in the past. I have to admit that while I have used it I only did it at a basic level by defining a single model. This is because I didn’t understand some of the core features and how to model relationships between objects. I thought it was time to sit down and learn all about Spring Data JDBC and over the past month that's exactly what I did.",[651,175503,175504,175505,175510,175511,175514],{},"In this tutorial, I discuss Domain Driven Design, aggregates, and aggregate roots which are core to understanding where to start. When it comes to defining relationships something ",[812,175506,175509],{"href":175507,"rel":175508},"https://twitter.com/jensschauder",[816],"Jens Schauder"," said has really helped ",[7300,175512,175513],{},"“There are 2 types of relationships you have to define and those are the ones between aggregates and the ones within aggregate roots”."," I hope you enjoy this tutorial where you will create a Spring Boot project from scratch and learn how to use Spring Data JDBC.",[5988,175516],{"id":175517},"l_T0nQNbFiM",[4542,175519,120255],{"id":91853},[651,175521,175522,175523,175527],{},"I recently created a ",[812,175524,175526],{"href":120253,"rel":175525},[816],"tutorial"," on using JSON Web Tokens (JWTs) with Spring Security. I received a lot of really good feedback from that video. The one question I heard the most was how can we authenticate against a username and password that is sent in the request body and remove HTTP Basic.",[651,175529,175530,175531,175535],{},"This was a great question and I know this is something I have done in the past but I wasn’t quite sure how to do it in the newer versions of Spring Security. I did what anyone with access to the Spring Security team would do, I asked them. ",[812,175532,91815],{"href":175533,"rel":175534},"https://twitter.com/kehrlann",[816]," reached out and was able to help me solve all of my problems.",[651,175537,175538,175539,175542,175543,175546,175547,175549,175550],{},"My biggest question was how to get an instance of the ",[676,175540,175541],{},"AuthenticationManager",". I know that I needed an instance of that to be able to use the ",[676,175544,175545],{},"authenticate()"," method so that was my starting point. After searching the web I found some answers but a lot of them involved getting an instance of the ",[676,175548,175541],{}," from the ",[676,175551,175552],{},"AuthenticationConfiguration.",[651,175554,175555],{},"After speaking with the Spring Security team I was informed that we should avoid this approach and instead create our own Authentication Manager. Once I was armed with that knowledge I had what I needed to complete this tutorial. If you want to see me walk through how to authenticate and return a JWT from a single endpoint check out the video below.",[5988,175557],{"id":175558},"UaB-0e76LdQ",[4542,175560,175562],{"id":175561},"graphql-testing-tip","GraphQL Testing Tip",[651,175564,175565,175566,175571],{},"I created a ",[812,175567,175570],{"href":175568,"rel":175569},"https://youtube.com/shorts/7qdyalH7mf4?feature=share",[816],"YouTube Short"," on a really handy tip I picked up for testing your GraphQL APIs. When you’re writing tests you usually start with a document which is the query, mutation, or subscription you would like to execute.",[651,175573,175574],{},"You will find that you end up writing these over and over and usually, they are duplicated from a previous test. Whenever you start copying/pasting code an alarm should go off in your head that there might be an easier way. I found out that you can move these queries into a file which will now allow you to reuse them in multiple tests.",[651,175576,175577],{},[812,175578,175579],{"href":175579,"rel":175580},"https://twitter.com/therealdanvega/status/1577653689996742658",[816],[4542,175582,175584],{"id":175583},"azure-spring-apps","Azure Spring Apps",[651,175586,175587,175588,175592,175593,175597],{},"In a recent episode of ",[812,175589,97345],{"href":175590,"rel":175591},"https://youtu.be/1cLu3sz56wY",[816]," DaShaun and I covered how to get your Spring Applications to production. Since that live stream, I have had some time to dive into ",[812,175594,175584],{"href":175595,"rel":175596},"https://azure.microsoft.com/en-us/products/spring-apps/",[816]," and I have to say I am really impressed with the platform. It makes moving enterprise applications to production and where it really gets interesting is if you work with distributed architectures. With built-in support for configuration and service registration deploying Microservices just got easy.",[651,175599,175600,175601,175605,175606,175609],{},"I put together a ",[812,175602,69264],{"href":175603,"rel":175604},"https://github.com/danvega/spring-blog",[816]," that has a sample application I am going to use to deploy to a number of hosting providers. I also have a Github Workflow in there to show off how you can automatically build and deploy your applications on Azure Spring Apps. The first platform is Azure and I should have a video out this week we will discuss it further during ",[812,175607,97345],{"href":97000,"rel":175608},[816],". If there is anything specific you would like to see me cover feel free to reach out to me on Twitter or reply to this email.",[4542,175611,175613],{"id":175612},"content-links","Content Links",[651,175615,175616],{},"I talked about a few of the things I worked on above but here is the entire collection of things that got my attention over the last 2 weeks.",[5316,175618,175619,175638,175657,175675],{},[5332,175620,175621,175622],{},"YouTube\n",[5316,175623,175624,175631],{},[5332,175625,175626],{},[812,175627,175630],{"href":175628,"rel":175629},"https://youtu.be/l_T0nQNbFiM",[816],"Spring Data JDBC Introduction",[5332,175632,175633],{},[812,175634,175637],{"href":175635,"rel":175636},"https://www.youtube.com/watch?v=UaB-0e76LdQ",[816],"Spring Security JWT with username and password auth",[5332,175639,175640,175641],{},"YouTube Shorts\n",[5316,175642,175643,175650],{},[5332,175644,175645],{},[812,175646,175649],{"href":175647,"rel":175648},"https://youtube.com/shorts/JetJaKkDLoE?feature=share",[816],"The best way to learn spring boot",[5332,175651,175652],{},[812,175653,175656],{"href":175654,"rel":175655},"https://youtube.com/shorts/7qdyalH7mf4?",[816],"Spring for GrqphQL Testing Tip",[5332,175658,175659,175660],{},"Spring Office Hours\n",[5316,175661,175662,175669],{},[5332,175663,175664],{},[812,175665,175668],{"href":175666,"rel":175667},"https://youtu.be/QPTSDrEJztg",[816],"Episode 14 - Ahead of Time (AOT) Compilation",[5332,175670,175671],{},[812,175672,175674],{"href":175590,"rel":175673},[816],"Episode 15 - Spring to Production",[5332,175676,175677,175678],{},"Tanzu Developer Center\n",[5316,175679,175680],{},[5332,175681,175682],{},[812,175683,175685],{"href":23260,"rel":175684},[816],"Spring Dependency Injection",[4542,175687,173888],{"id":173887},[5316,175689,175690,175695,175700,175705],{},[5332,175691,175692],{},[812,175693,174731],{"href":174729,"rel":175694},[816],[5332,175696,175697],{},[812,175698,175251],{"href":174587,"rel":175699},[816],[5332,175701,175702,175258],{},[812,175703,175257],{"href":162046,"rel":175704},[816],[5332,175706,175707,175264],{},[812,175708,175257],{"href":162046,"rel":175709},[816],[4542,175711,157574],{"id":157573},[5909,175713,164959],{"id":69848},[5316,175715,175716,175723,175730,175736,175743],{},[5332,175717,175718],{},[812,175719,175722],{"href":175720,"rel":175721},"https://spring.io/blog/2022/10/12/spring-framework-6-0-goes-rc1",[816],"Spring Framework 6.0 goes RC1",[5332,175724,175725],{},[812,175726,175729],{"href":175727,"rel":175728},"https://tanzu.vmware.com/developer/guides/graalvm-with-spring/",[816],"Getting Started with GraalVM and Spring Native",[5332,175731,175732],{},[812,175733,175735],{"href":104963,"rel":175734},[816],"Embracing Virtual Threads",[5332,175737,175738],{},[812,175739,175742],{"href":175740,"rel":175741},"https://medium.com/@mlvandijk/keeping-dependencies-up-to-date-with-maven-be8f7fb6441e",[816],"Keeping dependencies up to date with Maven",[5332,175744,175745],{},[812,175746,166873],{"href":166871,"rel":175747},[816],[5909,175749,164971],{"id":157591},[5316,175751,175752,175759,175766,175773],{},[5332,175753,175754],{},[812,175755,175758],{"href":175756,"rel":175757},"https://www.youtube.com/watch?v=Q_0XW46IlHY",[816],"Getting modules right with Domain-driven Design by Michael Plöd - Spring I/O 2022 Sessions",[5332,175760,175761],{},[812,175762,175765],{"href":175763,"rel":175764},"https://www.youtube.com/watch?v=-VmhytwBZVs",[816],"Let's build components, not layers by Tom Hombergs @ Spring I/O 2022",[5332,175767,175768],{},[812,175769,175772],{"href":175770,"rel":175771},"https://spring.io/blog/2022/10/10/spring-tips-spring-boot-apache-kafka",[816],"Spring Tips: Spring Boot & Apache Kafka",[5332,175774,175775],{},[812,175776,175779],{"href":175777,"rel":175778},"https://www.youtube.com/watch?v=g_Y6VqKVjuM&list=PLRsbF2sD7JVolUH45EkGXsT-3spU7cqnS",[816],"Devoxx Belgium 2022",[5909,175781,164983],{"id":39439},[5316,175783,175784,175791,175798],{},[5332,175785,175786],{},[812,175787,175790],{"href":175788,"rel":175789},"https://gotopia.tech/bookclub/episodes/spring-boot-up-and-running",[816],"goto; book club - Thomas Vitale & Mark Heckler",[5332,175792,175793],{},[812,175794,175797],{"href":175795,"rel":175796},"https://bootifulpodcast.fm/#/episodes/5b6eea77-d792-4856-8770-2719844ef01d",[816],"Google mad scientist Josh Suereth",[5332,175799,175800],{},[812,175801,175804],{"href":175802,"rel":175803},"https://player.fm/series/compressedfm/ep-088-breaking-into-dev-rel-featuring-tessa-mero",[816],"Compressed.fm - Breaking into DevRel",[5909,175806,165017],{"id":165016},[651,175808,175809],{},"\"We are what we repeatedly do. Excellence, then, is not an act, but a habit.\" — Aristotle",[5909,175811,165430],{"id":165429},[651,175813,175814],{},[812,175815,175816],{"href":175816,"rel":175817},"https://twitter.com/springcentral/status/1577314901525204992",[816],[4542,175819,157704],{"id":157703},[651,175821,174484,175822,166271],{},[812,175823,41499],{"href":44086,"rel":175824},[816],[651,175826,41105,175827,69920,175829,175831,175833,175835],{},[41107,175828],{},[41107,175830],{},[812,175832,161560],{"href":161111},[41107,175834],{},[812,175836,53869],{"href":53869,"rel":175837},[816],{"title":674,"searchDepth":790,"depth":790,"links":175839},[175840,175841,175842,175843,175844,175845,175846,175847,175848,175855],{"id":173848,"depth":790,"text":173849},{"id":175471,"depth":790,"text":175472},{"id":175498,"depth":790,"text":105038},{"id":91853,"depth":790,"text":120255},{"id":175561,"depth":790,"text":175562},{"id":175583,"depth":790,"text":175584},{"id":175612,"depth":790,"text":175613},{"id":173887,"depth":790,"text":173888},{"id":157573,"depth":790,"text":157574,"children":175849},[175850,175851,175852,175853,175854],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":175857,"date":175858},"spring-to-production","2022-10-17T17:00:00.000Z","/newsletter/2022/10/17/spring-to-production",{"title":175435,"description":175440},"newsletter/2022/10/17/spring-to-production","aXPuRDvLcIIsfNwr3uSZ_xLecoebSSOSGbh7De-rzFA",{"id":175864,"title":175865,"body":175866,"description":176280,"extension":793,"meta":176281,"navigation":797,"path":176283,"seo":176284,"stem":176285,"__hash__":176286},"content/newsletter/2022/10/31/deep-work.md","Deep Work, Spring Office Hours, and YouTube’s Redesign",{"type":648,"value":175867,"toc":176259},[175868,175876,175878,175881,175895,175900,175903,175906,175909,175912,175915,175918,175932,175935,175940,175960,175964,175973,175975,175982,175985,175988,175990,175993,175999,176002,176008,176010,176013,176056,176060,176101,176103,176109,176111,176116,176121,176123,176125,176141,176143,176173,176175,176191,176193,176212,176214,176223,176225,176228,176230,176233,176239,176241,176246],[651,175869,175870,175871,175875],{},"Happy Monday and welcome to another edition of my newsletter. I want to wish all of you a very happy and spooky Halloween 🎃 If you’re enjoying this newsletter and wouldn’t mind ",[812,175872,175874],{"href":82778,"rel":175873},[816],"sharing"," it with a friend that would mean the world to me. In today’s episode, I want to talk about some books I’m currently reading, YouTube, and Spring Office Hours. I will also give you a little bit of a peek into what I am working on this week.",[4542,175877,173849],{"id":173848},[651,175879,175880],{},"I took a short little trip to Ocean Isle, NC last weekend for a friend's wedding. I ended up having to go by myself because my wife and I couldn’t find anyone to watch the kids. I decided that I would take this opportunity to get some running in along the ocean, which by the way is one of the most therapeutic things I have ever had the pleasure of doing and something I recommend to everyone.",[651,175882,175883,175884,175888,175889,175894],{},"I also decided to take this opportunity to do a little reading. I decided to take 2 books with me not knowing how much I would get done but to my surprise, I was able to finish them both. I forgot that traveling without kids, especially little ones can actually be relaxing. I ended up taking ",[812,175885,174548],{"href":175886,"rel":175887},"https://amzn.to/3TRc8TC",[816]," which I was already 30 pages into and ",[812,175890,175893],{"href":175891,"rel":175892},"https://amzn.to/3SKBilz",[816],"Create Something by Roberto Blake",". I will start by giving a mini-review of Deep Work this week and in the next newsletter, I will talk about Create Something awesome. Mini spoiler, I thought they were both really great. 👏🏻",[651,175896,175897],{},[660,175898],{"alt":6016,"src":175899},"/images/newsletter/2022/10/31/books.jpeg",[5909,175901,97192],{"id":175902},"deep-work",[651,175904,175905],{},"I was really excited to get through this book as I believe it found me at an appropriate time in life. Deep work is a productivity book on how to be focused in a distracted world. I was already a big fan of Cal Newport's when I happened to come across his Podcast which I have been binging on lately.",[651,175907,175908],{},"The first part of the book went into describing what deep work is. Deep work is all about the ability to focus with distraction on a cognitively demanding task. This becomes especially true when you have little ones in the house every day all day. For me, my meaningful work is creating content that includes articles, videos, courses, this newsletter you’re reading, or a presentation for an upcoming conference.",[651,175910,175911],{},"If that is Deep work shallow work is everything else that is not contributing to the goals you have set out for yourself. This could be anything from meetings, catching up on emails, or those pesky expense reports. It’s not to say that these things are not important but we need to find ways to batch these tasks and create time blocks to focus on our deep work.",[651,175913,175914],{},"Cal talks extensively about creating time blocks for your work day and being more proactive than reactive. If you wake up on Monday and ask what should I work on today there is too much room for distractions to creep in. Each Sunday I take a look at my quarterly goals, what items I would like to complete this week, and plan out my week.",[651,175916,175917],{},"The rest of the book is all about the rules to deep work:",[5316,175919,175920,175923,175926,175929],{},[5332,175921,175922],{},"Work Deeply",[5332,175924,175925],{},"Embrace Boredom",[5332,175927,175928],{},"Quit Social Media",[5332,175930,175931],{},"Drain the shallows",[651,175933,175934],{},"Each of these rules is well thought out and backed up by case studies and real-world experiences. I think what really stood out for me was the ability to embrace boredom. Once your brain becomes accustomed to on-demand distractions (like looking at your smartphone while standing in line at the grocery store) it’s harder to shake the addiction, even when you want to concentrate.",[651,175936,175937],{},[2939,175938,175939],{},"My Takeaways from the book",[5316,175941,175942,175945,175948,175951,175954,175957],{},[5332,175943,175944],{},"I know my cutoff is 5 PM each day. I take each day and plan backward from there based on the tasks I want to accomplish that day.",[5332,175946,175947],{},"As I mentioned earlier I spend a little bit of time on Sunday reflecting on the previous week, what my quarterly goals are and what I would like to accomplish this week.",[5332,175949,175950],{},"I have turned off all notifications from my phone for anything that is urgent. This includes personal email, Twitter, YouTube, etc…",[5332,175952,175953],{},"Both my personal and work email are not open on my computer. I will spend time blocks checking email but I don’t want the distraction of the notification each time I receive an email.",[5332,175955,175956],{},"I want to quit all social media but I just can’t. I took the first step by getting rid of TikTok which was just mindless scrolling. I run a Facebook group for Spring Developer and for my non-profit that I run so for now that will have to stick around. I would love to get rid of Facebook and Instagram so I will try and convince myself to do that next.",[5332,175958,175959],{},"Spend a few minutes in my shallow work blocks unsubscribing from spam and cleaning out my email boxes.",[5909,175961,175963],{"id":175962},"next-up","Next Up",[651,175965,175966,175967,175972],{},"Next up on the reading list is actually something I was referred to by Cal Newport. The book is by Adam Grant and it’s called ",[812,175968,175971],{"href":175969,"rel":175970},"https://amzn.to/3zunHYN",[816],"Give and Take",", Why helping others drive our success. I have already started this one and I can’t wait to make my way through it.",[4542,175974,97345],{"id":97344},[651,175976,175977,175978,175981],{},"If you haven’t been paying attention my coworker and friend DaShaun and I have been running a weekly live stream called ",[812,175979,97345],{"href":97000,"rel":175980},[816],". This is a chance for us to share with you what is new in the world of Spring. We also like to show off demos of whatever we are currently working on and most importantly it’s a chance for you to ask your questions and hopefully, we can answer them. Not only has this show been extremely helpful to the community but it’s the thing I look to most every week.",[651,175983,175984],{},"Lately, we have gotten more consistent with the show which airs every Tuesday at 3:30 EDT. With that, we have seen a rise in our viewers where almost 100 people joined us live last week, and thousands on the replay. In the latest episode, which you can find below, we talked all about the release of Spring Boot 3.0 RC1.",[5988,175986],{"id":175987},"WgcP00BOs5I",[4542,175989,15432],{"id":61224},[651,175991,175992],{},"YouTube launched a redesign of its desktop and mobile applications. I am a big fan of the new look which is cleaner and the slightly rounded corners on the thumbnails. I’m also a big fan of the tabs across the top where they have split out the video, shorts, and live streams. This makes it easier for viewers to consume the appropriate content. I’m also pretty proud of how my page looks with my current thumbnail design. I am not a designer but these turned out well. 🥳",[651,175994,175995],{},[660,175996],{"alt":175997,"src":175998},"YouTube Redesign","/images/newsletter/2022/10/31/youtube_redesign.png",[651,176000,176001],{},"Speaking of those thumbnails, I updated to macOS Ventura this week. There is a really great feature that allows you to copy just the subject from an image in your photos app.",[651,176003,176004],{},[812,176005,176006],{"href":176006,"rel":176007},"https://twitter.com/therealdanvega/status/1585102836312203264",[816],[4542,176009,82720],{"id":47833},[651,176011,176012],{},"I released a bunch of content over the last 2 weeks and I hope you enjoy them. I have talked about this in the past but I am having a lot of fun creating YouTube shorts. If you have randomly come across one of my short-form videos and are enjoying them please let me know 🤩",[5316,176014,176015,176023,176028,176035,176042,176049],{},[5332,176016,176017,176022],{},[812,176018,176021],{"href":176019,"rel":176020},"https://youtu.be/53T_R3xAPTg",[816],"Spring Boot Azure"," - In this tutorial",[5332,176024,176025],{},[812,176026,175472],{"href":23779,"rel":176027},[816],[5332,176029,176030],{},[812,176031,176034],{"href":176032,"rel":176033},"https://youtu.be/m40_FKUCbq0",[816],"Spring Office Hours: Spring to Production Part 2",[5332,176036,176037],{},[812,176038,176041],{"href":176039,"rel":176040},"https://youtu.be/WgcP00BOs5I",[816],"Spring Office Hours: Spring Boot 3.0 RC1",[5332,176043,176044],{},[812,176045,176048],{"href":176046,"rel":176047},"https://youtu.be/_Gdb-jK3Sr4",[816],"Docker PostgreSQL Spring Boot",[5332,176050,176051],{},[812,176052,176055],{"href":176053,"rel":176054},"https://youtube.com/shorts/zPg4z5d3WVo?feature=share",[816],"Spring Boot 3.0 Short",[4542,176057,176059],{"id":176058},"upcoming-content","Upcoming Content",[5316,176061,176062,176075,176078,176081,176086,176091,176096],{},[5332,176063,176064,176069,176070,176074],{},[812,176065,176068],{"href":176066,"rel":176067},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0018/",[816],"Spring Office Hours 18: Testing with Ted"," - We are excited to be joined by Ted Young AKA ",[812,176071,176073],{"href":61026,"rel":176072},[816],"JitterTed"," to talk all about testing in Spring.",[5332,176076,176077],{},"Serverless Spring - I wrote a new guide for the Tanzu Developer Center which should be published by the time you read this on Serverless Spring Boot. In this tutorial, you will learn how to build and deploy serverless functions using Spring Boot and Spring Cloud Function. I should be able to record a video version of this guide for my YouTube channel very soon.",[5332,176079,176080],{},"Spring Boot 3 - First Look: I am working on a video that introduces you to a lot of the new features in Spring Boot 3.",[5332,176082,176083],{},[812,176084,174731],{"href":174729,"rel":176085},[816],[5332,176087,176088],{},[812,176089,175251],{"href":174587,"rel":176090},[816],[5332,176092,176093,175258],{},[812,176094,175257],{"href":162046,"rel":176095},[816],[5332,176097,176098,175264],{},[812,176099,175257],{"href":162046,"rel":176100},[816],[4542,176102,82113],{"id":82579},[651,176104,176105,176106,174572],{},"This week we made the last of our announcements around speakers, workshops, and sessions. If you haven’t had a chance you should head over to ",[812,176107,174571],{"href":174569,"rel":176108},[816],[651,176110,174575],{},[651,176112,176113],{},[660,176114],{"alt":82113,"src":176115},"/images/newsletter/2022/10/31/spring_one_talk.png",[651,176117,174584,176118,174590],{},[812,176119,174589],{"href":174587,"rel":176120},[816],[4542,176122,157574],{"id":157573},[5909,176124,164959],{"id":69848},[5316,176126,176127,176134],{},[5332,176128,176129],{},[812,176130,176133],{"href":176131,"rel":176132},"https://medium.com/graalvm/graalvm-22-3-is-here-jdk-19-builds-jlink-support-new-monitoring-features-and-more-f6e2b2eeff95",[816],"GraalVM 22.3 is here: JDK 19 builds, jlink support, new monitoring features, and more!",[5332,176135,176136],{},[812,176137,176140],{"href":176138,"rel":176139},"https://blog.jetbrains.com/idea/2022/10/intellij-idea-2022-3-eap-4/",[816],"IntelliJ IDEA 2022.3 EAP 4 is out!",[5909,176142,164971],{"id":157591},[5316,176144,176145,176152,176159,176166],{},[5332,176146,176147],{},[812,176148,176151],{"href":176149,"rel":176150},"https://www.youtube.com/watch?v=aUm5WZjh8RA",[816],"Spring Tips: the road to Spring Boot 3: Spring Framework 6",[5332,176153,176154],{},[812,176155,176158],{"href":176156,"rel":176157},"https://www.youtube.com/watch?v=BzsdYIOuNWQ",[816],"GraalVM 22.3 Release stream 🚀",[5332,176160,176161],{},[812,176162,176165],{"href":176163,"rel":176164},"https://www.youtube.com/watch?v=LlVy9Roh_bQ",[816],"Laurențiu Spilcă - The new Spring Security",[5332,176167,176168],{},[812,176169,176172],{"href":176170,"rel":176171},"https://www.youtube.com/watch?v=rT4fJNbfe14",[816],"Prometheus: The Documentary",[5909,176174,164983],{"id":39439},[5316,176176,176177,176184],{},[5332,176178,176179],{},[812,176180,176183],{"href":176181,"rel":176182},"https://bootifulpodcast.fm/#/episodes/8240ea1e-1084-47a7-bfd8-9210bb4d837c",[816],"Spring mad scientist Andy Clement on a Bootiful Podcast",[5332,176185,176186],{},[812,176187,176190],{"href":176188,"rel":176189},"https://tanzu.vmware.com/developer/tv/bcak/69/",[816],"Between Chair and Keyboard - The one with Billy Williams",[5909,176192,165005],{"id":39340},[5316,176194,176195,176200,176206],{},[5332,176196,176197],{},[812,176198,174548],{"href":175886,"rel":176199},[816],[5332,176201,176202],{},[812,176203,176205],{"href":175891,"rel":176204},[816],"Create Something Awesome by Roberto Blake",[5332,176207,176208],{},[812,176209,176211],{"href":175969,"rel":176210},[816],"Give and Take by Adam Grant",[5909,176213,170434],{"id":79608},[5316,176215,176216],{},[5332,176217,176218],{},[812,176219,176222],{"href":176220,"rel":176221},"https://www.youtube.com/watch?v=Znd11rVHQOE",[816],"ViteConf 2022 | Full day | 42 talks about the tools and frameworks innovating in the Vite Ecosystem",[5909,176224,165017],{"id":165016},[651,176226,176227],{},"The principle of give and take; that is diplomacy — give one and take ten.",[5909,176229,165430],{"id":165429},[651,176231,176232],{},"I updated to macOS Ventura this week and so far, no issues. 🤞🏻",[651,176234,176235],{},[812,176236,176237],{"href":176237,"rel":176238},"https://twitter.com/therealdanvega/status/1585090917991870464",[816],[4542,176240,157704],{"id":157703},[651,176242,174484,176243,166271],{},[812,176244,41499],{"href":44086,"rel":176245},[816],[651,176247,41105,176248,69920,176250,176252,176254,176256],{},[41107,176249],{},[41107,176251],{},[812,176253,161560],{"href":161111},[41107,176255],{},[812,176257,53869],{"href":53869,"rel":176258},[816],{"title":674,"searchDepth":790,"depth":790,"links":176260},[176261,176265,176266,176267,176268,176269,176270,176279],{"id":173848,"depth":790,"text":173849,"children":176262},[176263,176264],{"id":175902,"depth":892,"text":97192},{"id":175962,"depth":892,"text":175963},{"id":97344,"depth":790,"text":97345},{"id":61224,"depth":790,"text":15432},{"id":47833,"depth":790,"text":82720},{"id":176058,"depth":790,"text":176059},{"id":82579,"depth":790,"text":82113},{"id":157573,"depth":790,"text":157574,"children":176271},[176272,176273,176274,176275,176276,176277,176278],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":39340,"depth":892,"text":165005},{"id":79608,"depth":892,"text":170434},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},"Happy Monday and welcome to another edition of my newsletter. I want to wish all of you a very happy and spooky Halloween 🎃 If you’re enjoying this newsletter and wouldn’t mind sharing it with a friend that would mean the world to me. In today’s episode, I want to talk about some books I’m currently reading, YouTube, and Spring Office Hours. I will also give you a little bit of a peek into what I am working on this week.",{"slug":175902,"date":176282},"2022-10-31T17:00:00.000Z","/newsletter/2022/10/31/deep-work",{"title":175865,"description":176280},"newsletter/2022/10/31/deep-work","qyt5ucMPhz8a42uTZ6LBhtDoG6Nmyh4Xw4233x2MKZ8",{"id":176288,"title":176289,"body":176290,"description":176294,"extension":793,"meta":176646,"navigation":797,"path":176649,"seo":176650,"stem":176651,"__hash__":176652},"content/newsletter/2022/11/14/create-something-awesome.md","Create Something Awesome, Twitter, SpringOne, and the march to 20,000 subscribers",{"type":648,"value":176291,"toc":176626},[176292,176295,176301,176304,176306,176324,176333,176336,176339,176342,176344,176347,176350,176353,176356,176358,176366,176369,176375,176377,176388,176431,176433,176452,176456,176459,176462,176465,176468,176471,176477,176481,176484,176492,176494,176496,176519,176521,176537,176539,176555,176557,176566,176568,176592,176594,176597,176599,176605,176607,176613],[651,176293,176294],{},"Happy Monday and welcome to another edition of the newsletter. It’s hard to believe that it is the middle of November and Thanksgiving is next week. We had our first snowfall over the weekend and I can say with confidence that I am not ready for this 🤦♂️",[651,176296,176297],{},[660,176298],{"alt":176299,"src":176300},"First Snowfall in Ohio","/images/newsletter/2022/11/14/first_snowfall.jpeg",[651,176302,176303],{},"In this week's edition, I want to talk to you about creating something awesome, Twitter, SpringOne, and what I have been working on lately.",[4542,176305,173849],{"id":173848},[651,176307,176308,176309,176313,176314,176319,176320,664],{},"The last time I checked in with you I told you that I finished 2 new books on my quick trip to Ocean Isle NC for a friend's wedding. I gave you a ",[812,176310,176312],{"href":97196,"rel":176311},[816],"quick review"," of the first book ",[812,176315,176318],{"href":176316,"rel":176317},"https://amzn.to/3WWSrMh",[816],"Deep work"," and today I want to talk quickly about the other book, ",[812,176321,176205],{"href":176322,"rel":176323},"https://amzn.to/3O5aQ5p",[816],[651,176325,176326,176327,176332],{},"I have followed ",[812,176328,176331],{"href":176329,"rel":176330},"https://www.youtube.com/channel/UCovtFObhY9NypXcyHxAS7-Q",[816],"Roberto’s work"," for a while now so I was really excited to pick this up when it came out. Roberto is a creative entrepreneur, YouTuber, and Keynote Speaker. I have learned a lot about YouTube from Roberto’s channel so my mindset going into this book was that it was going to be an extension of that.",[651,176334,176335],{},"This book is really geared towards someone who is interested in turning their passion into profits in the creator economy. I think this book is packed with real-world case studies, personal experiences as well as actionable advice. Even if you have been in the content creation game for a while I think there is still a lot you can and I certainly picked up some tips and motivation that I was looking for.",[651,176337,176338],{},"The message that runs throughout the book is that there is no better time to monetize your passion. This starts with understanding what the creator economy is, how to build a powerful brand and how to monetize by providing value first. The chapter on authenticity and boundaries and why being yourself is so important in this game really stood out to me. So much so that I went back and read it again before writing this review.",[651,176340,176341],{},"Roberto is a great person that embodies what creators should aspire to and it’s apparent that he poured his experience and passion into this book. If you are a creator looking to turn your passion into a career this is a must-read. Thank you, Roberto, 👏🏻",[4542,176343,51474],{"id":53742},[651,176345,176346],{},"If you’re on Twitter you don’t need me to tell you what’s been going on. Instead, I want to offer a bit of a different insight. I went back and looked and I have been on Twitter since 2008 and have authored over 30,000 tweets. I have discussed with you my desire to rid myself of social media platforms like Facebook and Instagram in the past because I believe they do more harm than good.",[651,176348,176349],{},"Twitter for me a place where I connect with people like me. I get to talk with people who live near me, creators who like to create content, and the biggest community of all, developers. I don’t ever look at Twitter as toxic because as soon as someone becomes that way to me I stop following and will go as far as I need to to make sure those individuals don’t show up on my feed.",[651,176351,176352],{},"Like other social media platforms, there is still a problem with Twitter that I try and work on often. I am constantly comparing myself to other creators, developers, and people I look up to. On some level, it’s good to look up to others but the constant need to prove your worth to yourself because of something or someone you saw online is no way to live life. We all have to remember that the majority of what we see on social networks is the best view of them. Not many people rush to Twitter to tell you how hard life is and how they are dealing with it.",[651,176354,176355],{},"With all that said I don’t want to give up on Twitter. Yes, they are going through a pretty rough transition right now and I’m not sure what the future holds for them. I love the people I learn from every day and all of the new tech, software, and products I learn about on the platform. I saw an influx of people scrambling to create accounts on other platforms and I won’t be one of them. I’m hopefully optimistic that some good will come out of this change.",[4542,176357,82113],{"id":82579},[651,176359,176360,176361,176365],{},"By now I am sure that you heard but SpringOne in December was canceled. I was so sad when I heard this because I was looking forward to so many things. I was looking forward to giving a workshop on how to get started with Spring Boot. I was also giving a breakout session on Spring for GraphQL. I was really looking forward to the amazing lineup of speakers and getting a chance to meet so many of you in person for the first time. There is now a free ",[812,176362,176364],{"href":82111,"rel":176363},[816],"virtual conference"," happening in January so if you’re interested please signup now.",[651,176367,176368],{},"As sad as I am about SpringOne, the show must go on. I know there will be many more opportunities to connect with all of you. I am speaking at CodeMash in January where I will be giving a talk on GraphQL as well as a workshop with my friend DaShaun. I am submitting to a lot of conferences in 2023 so if you would like me to speak at your event please reach out.",[651,176370,176371],{},[812,176372,176373],{"href":176373,"rel":176374},"https://twitter.com/therealdanvega/status/1591094107530731520",[816],[4542,176376,82720],{"id":47833},[651,176378,176379,176380,63504,176383,176387],{},"I wrote a guide for the ",[812,176381,97002],{"href":168725,"rel":176382},[816],[812,176384,95859],{"href":176385,"rel":176386},"https://tanzu.vmware.com/developer/guides/serverless-spring/",[816],". Prior to working on this article, I didn’t have a ton of experience building serverless applications so I was excited to jump in and learn from the ground floor. Before I could start writing serverless applications with Spring I wanted to start with just Java and experience what that was like and experience some of the pain points to understand where something like Spring Cloud Function would help me. That is why you will find 3 new videos on YouTube related to creating your first lambda functions on AWS with Java.",[5316,176389,176390,176396,176403,176410,176417,176424],{},[5332,176391,176392],{},[812,176393,176395],{"href":176385,"rel":176394},[816],"Serverless Spring Guide",[5332,176397,176398],{},[812,176399,176402],{"href":176400,"rel":176401},"https://youtu.be/Mjq9g8hRaoI",[816],"Spring Office Hours with Greg Turnquist",[5332,176404,176405],{},[812,176406,176409],{"href":176407,"rel":176408},"https://youtu.be/MaHxZEBRcT4",[816],"Hello AWS Lambda",[5332,176411,176412],{},[812,176413,176416],{"href":176414,"rel":176415},"https://youtu.be/kyWllXOGMWQ",[816],"AWS Lambda Java Core",[5332,176418,176419],{},[812,176420,176423],{"href":176421,"rel":176422},"https://youtu.be/K1OI-S0ET70",[816],"AWS Lambda RDS PostgreSQL",[5332,176425,176426],{},[812,176427,176430],{"href":176428,"rel":176429},"https://www.youtube.com/watch?v=4cRbPH_GkBQ",[816],"Code on the Beach - Dan Vega - Full Stack Java Development with Spring Boot",[5909,176432,173888],{"id":173887},[5316,176434,176435,176442,176447],{},[5332,176436,176437],{},[812,176438,176441],{"href":176439,"rel":176440},"https://youtu.be/0i6Xu3Pf83Q",[816],"Spring Office Hours with Daniel",[5332,176443,176444,175258],{},[812,176445,175257],{"href":162046,"rel":176446},[816],[5332,176448,176449,175264],{},[812,176450,175257],{"href":162046,"rel":176451},[816],[4542,176453,176455],{"id":176454},"office-hours-repository","Office Hours Repository",[651,176457,176458],{},"There is something that has been weighing on me heavily lately. I receive a lot of comments, questions, suggestions, and feedback from a variety of sources. A big source of this is YouTube, Spring Office Hours, and Twitter but I also get notifications through my website, email, and LinkedIn.",[651,176460,176461],{},"When it’s a comment I usually reply and thank that person for taking time out of their day to leave me a comment. When it’s a question and there is a simple answer to it I will reply to it. The problem is what if it isn’t an easy question to answer? What If I don’t have the answer and it will take some time to investigate? What if it's a suggestion for a future video?",[651,176463,176464],{},"I realize that I simply can’t reply to every single comment, suggestion, or question but I also don’t want to ignore people. With a variety of mediums, where do I store these questions so they don’t get lost? This is something that I have been thinking about lately and I have come up with an answer. Well, I have a solution and we shall see in the long term if it’s the answer.",[651,176466,176467],{},"I started a Github Repository called Office Hours. There is no code in this repository but there are some discussion boards. These boards are split up into content suggestions, Q&A, general chat, and polls. If you have a comment, question, or suggestion that falls into one of these buckets please consider submitting it below. I am going to find a good cadence to try and respond to what comes in there.",[651,176469,176470],{},"I know others who use Discord for this but I have to say, I am just not a big fan of Discord. For as much slack as Slack gets (see what I did there) it is just so much more of an enjoyable user experience for me. I find Discord clunky, and hard to use and the lack of threads gives me a headache. You can find the repository below and I would love your feedback on this going forward.",[651,176472,176473],{},[812,176474,176475],{"href":176475,"rel":176476},"https://github.com/danvega/office-hours/discussions",[816],[4542,176478,176480],{"id":176479},"youtube-20000","YouTube 20,000",[651,176482,176483],{},"Speaking of YouTube I’m currently sitting around 19,500 subscribers on YouTube. This means that I will hopefully hit 20k before the end of the month. I haven’t really celebrated any milestones lately but I have been super excited about this one. I’m trying to figure out what I should do for this huge milestone. I will at the very least put together a video and thank all of you who have supported me over the years and helped me reach this huge goal of mine. Maybe we can do a giveaway? How about free Spring Boot 3 licenses for everyone 🤣",[651,176485,176486,176487,176491],{},"If you haven’t ",[812,176488,164098],{"href":176489,"rel":176490},"http://youtube.com/@danvega",[816]," yet, what are you waiting for 🤦♂️",[4542,176493,157574],{"id":157573},[5909,176495,164959],{"id":69848},[5316,176497,176498,176505,176512],{},[5332,176499,176500],{},[812,176501,176504],{"href":176502,"rel":176503},"https://medium.com/tinder/how-we-built-the-tinder-api-gateway-831c6ca5ceca",[816],"How we built the Tinder API Gateway",[5332,176506,176507],{},[812,176508,176511],{"href":176509,"rel":176510},"https://maciejwalkowiak.com/blog/guide-java-publish-to-maven-central/",[816],"How to publish a Java library to Maven Central - Complete Guide",[5332,176513,176514],{},[812,176515,176518],{"href":176516,"rel":176517},"https://aws.amazon.com/blogs/opensource/improving-developer-productivity-at-disney-with-serverless-and-open-source/?tag=mochaglobal19-20",[816],"Improving Developer Productivity at Disney with Serverless and Open Source",[5909,176520,164971],{"id":157591},[5316,176522,176523,176530],{},[5332,176524,176525],{},[812,176526,176529],{"href":176527,"rel":176528},"https://www.youtube.com/watch?v=zr7pEWKtVy4&t=348s",[816],"What You Need to Know About Java Text Blocks",[5332,176531,176532],{},[812,176533,176536],{"href":176534,"rel":176535},"https://www.youtube.com/watch?v=TMQZ921XvLg",[816],"A video for anyone feeling behind in life",[5909,176538,164983],{"id":39439},[5316,176540,176541,176548],{},[5332,176542,176543],{},[812,176544,176547],{"href":176545,"rel":176546},"https://bootifulpodcast.fm/#/episodes/b7f51cf1-6fac-4b11-9a75-ed35bda273f2",[816],"Java Champion, legend, and prolific open-source contributor Andres Almiray",[5332,176549,176550],{},[812,176551,176554],{"href":176552,"rel":176553},"https://tanzu.vmware.com/developer/tv/bcak/70/",[816],"Between Chair and Keyboard - with Billy Korando",[5909,176556,164995],{"id":133422},[5316,176558,176559],{},[5332,176560,176561],{},[812,176562,176565],{"href":176563,"rel":176564},"https://blog.jetbrains.com/idea/2022/10/intellij-idea-2022-3-eap-5",[816],"IntelliJ IDEA 2022.3 EAP 5",[5909,176567,165005],{"id":39340},[5316,176569,176570,176575,176580,176585],{},[5332,176571,176572],{},[812,176573,174548],{"href":175886,"rel":176574},[816],[5332,176576,176577],{},[812,176578,176205],{"href":175891,"rel":176579},[816],[5332,176581,176582],{},[812,176583,176211],{"href":175969,"rel":176584},[816],[5332,176586,176587],{},[812,176588,176591],{"href":176589,"rel":176590},"https://leanpub.com/gettingtoknowIntelliJIDEA",[816],"Getting to Know IntelliJ IDEA",[5909,176593,165017],{"id":165016},[651,176595,176596],{},"“It’s easier to win if everybody wants you to win. If you don’t make enemies out there it’s easier to succeed.” - Randy Komisar",[5909,176598,165430],{"id":165429},[651,176600,176601],{},[812,176602,176603],{"href":176603,"rel":176604},"https://twitter.com/therealdanvega/status/1590796343710453760",[816],[4542,176606,157704],{"id":157703},[651,176608,176609,176610,166271],{},"Thanks for sitting down and sharing a cup of coffee with me my friend. I hope you enjoyed this installment of the newsletter and I will talk to you in the next one. If you have any links you would like me to include please get i",[812,176611,172818],{"href":44086,"rel":176612},[816],[651,176614,41105,176615,69920,176617,176619,176621,176623],{},[41107,176616],{},[41107,176618],{},[812,176620,161560],{"href":161111},[41107,176622],{},[812,176624,53869],{"href":53869,"rel":176625},[816],{"title":674,"searchDepth":790,"depth":790,"links":176627},[176628,176629,176630,176631,176634,176635,176636,176645],{"id":173848,"depth":790,"text":173849},{"id":53742,"depth":790,"text":51474},{"id":82579,"depth":790,"text":82113},{"id":47833,"depth":790,"text":82720,"children":176632},[176633],{"id":173887,"depth":892,"text":173888},{"id":176454,"depth":790,"text":176455},{"id":176479,"depth":790,"text":176480},{"id":157573,"depth":790,"text":157574,"children":176637},[176638,176639,176640,176641,176642,176643,176644],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":176647,"date":176648},"create-something-awesome","2022-11-14T19:00:00.000Z","/newsletter/2022/11/14/create-something-awesome",{"title":176289,"description":176294},"newsletter/2022/11/14/create-something-awesome","faSuf3hN3pco6N1yuClNSOQ2vdpdiJSELqw_nQ6Qi8Q",{"id":176654,"title":176655,"body":176656,"description":176660,"extension":793,"meta":177042,"navigation":797,"path":177044,"seo":177045,"stem":177046,"__hash__":177047},"content/newsletter/2022/11/28/spring-boot-3.md","Spring Boot 3, Conference Talks and 20,000 Subscribers",{"type":648,"value":176657,"toc":177026},[176658,176661,176663,176666,176669,176672,176675,176678,176682,176689,176692,176698,176710,176748,176751,176753,176756,176759,176765,176768,176812,176815,176821,176825,176828,176834,176837,176840,176843,176845,176847,176861,176863,176880,176882,176884,176921,176923,176965,176967,176983,176985,177008,177010,177013],[651,176659,176660],{},"Happy Monday and welcome to another edition of my newsletter. I hope everyone who celebrated Thanksgiving had a great holiday week spent with great food and family. In this week's newsletter, I am going to talk about Spring Boot 3, submitting conference talks, and hitting a big milestone on YouTube.",[4542,176662,173849],{"id":173848},[651,176664,176665],{},"As I mentioned in the open last week we celebrated Thanksgiving last week. I absolutely love this holiday because it’s all about my favorite things, family, friends, and food. I also turned to my wife during the week with a revelation that is absolutely the longest most stressful week of the whole entire year.",[651,176667,176668],{},"We host a pretty large group every year so I end up taking off of work so we can prepare for that. It takes us all week of running around, preparing food, and getting the house ready for company. All of this while trying to make sure our little ones have a fun week amidst the chaos.",[651,176670,176671],{},"Thanksgiving finally comes around and the whirlwind of family, food and fun is gone way to fast in comparison to the time it took to put together. Then when you finally make it through the day and you have to quickly regroup and turn your attention towards Christmas.",[651,176673,176674],{},"We get all of our decorations from storage and then get the inside decorated along with setting up the tree. The little ones love putting up the tree and hanging the ornaments so we put on some Christmas music and made a morning of it. Finally, I had to get on the roof and hang up all of the outside lights up.",[651,176676,176677],{},"I love Thanksgiving but I am really glad to see the calendar turn and get back to work this week. I have a busy week ahead of me as I am working on a bunch of videos and preparing for a Spring Boot workshop at CodeMash in early January.",[4542,176679,176681],{"id":176680},"spring-boot-3","Spring Boot 3",[651,176683,176684,176685,176688],{},"Spring Framework 6 and Spring Boot 3 are now both GA and what an exciting time it is to be a Spring Developer. If you head over to ",[812,176686,77478],{"href":30440,"rel":176687},[816]," and create a new project the default will now be Spring Boot 3 which includes Spring Framework 6 under the hood.",[651,176690,176691],{},"If you need further proof run the project you downloaded and there in plain ASCII art you can see the new version and I don’t know if you know this but ASCII art never lies 😜",[651,176693,176694],{},[660,176695],{"alt":176696,"src":176697},"hello-spring-boot-3.png","/images/newsletter/2022/11/28/hello-spring-boot-3.png",[651,176699,176700,176701,51393,176705,176709],{},"If you want to find out more I encourage you to check out the Spring Boot 3 ",[812,176702,95704],{"href":176703,"rel":176704},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes",[816],[812,176706,109096],{"href":176707,"rel":176708},"https://spring.io/blog/2022/11/24/spring-boot-3-0-goes-ga",[816]," by the Spring Team. This release includes over 5700 commits by 151 individuals. I am excited about so much when it comes to Spring Framework 6 and Spring Boot 3 but here are a few of my favorites.",[5316,176711,176712,176715,176718,176727,176730,176733,176736,176739,176742,176745],{},[5332,176713,176714],{},"Java 17 Baseline - This is exciting to me because so many projects are going to get an instant upgrade in security and performance just by moving to Java 17.",[5332,176716,176717],{},"Jakarta EE 9/10 - Moving from javax → jakarta namespace means we now get to take advantage of an evolving API.",[5332,176719,176720,176721,176726],{},"Support for generating ",[812,176722,176725],{"href":176723,"rel":176724},"https://docs.spring.io/spring-boot/docs/3.0.0/reference/html/native-image.html#native-image",[816],"Native Images"," with GraalVM.",[5332,176728,176729],{},"Improved observability with Micrometer and Micrometer Tracing",[5332,176731,176732],{},"Problem Detail Support",[5332,176734,176735],{},"Declarative HTTP Clients",[5332,176737,176738],{},"Spring Security 6",[5332,176740,176741],{},"Spring Data 2022",[5332,176743,176744],{},"Spring for GraphQL 1.1",[5332,176746,176747],{},"Spring Authorization Server 1.0",[651,176749,176750],{},"I am working on a few videos around Spring Boot 3 and I am just trying to figure out how I want to structure some of this content. You should see at least 1 video get published on my YouTube channel this week. If there is anything you are struggling with when it comes to Spring Framework 6 and Spring Boot 3 please let me know.",[4542,176752,79609],{"id":79608},[651,176754,176755],{},"I have begun putting together a list of conferences that I would like to submit proposals for next year. To do so I need to put a list of available conferences, locations, dates, and when the Call for Papers (CFP) closes. With that list in place I could take a look at my schedule and decide which conferences I would be able to submit to.",[651,176757,176758],{},"I had some resources that I was using to pull this list together which started with what conferences my team attended last year. After that, I wasn’t sure where I could find up-to-date lists. I am looking for anything in Java, Spring, Web Development, and JavaScript space. I decided to ask Twitter and I got a lot of really great responses",[651,176760,176761],{},[812,176762,176763],{"href":176763,"rel":176764},"https://twitter.com/therealdanvega/status/1592246011803897856",[816],[651,176766,176767],{},"Here is a collection of links I compiled from those responses.",[5316,176769,176770,176776,176782,176788,176794,176800,176806],{},[5332,176771,176772],{},[812,176773,176774],{"href":176774,"rel":176775},"https://dev.java/community/events/",[816],[5332,176777,176778],{},[812,176779,176780],{"href":176780,"rel":176781},"https://github.com/javaconferences/javaconferences.github.io",[816],[5332,176783,176784],{},[812,176785,176786],{"href":176786,"rel":176787},"https://github.com/scraly/developers-conferences-agenda",[816],[5332,176789,176790],{},[812,176791,176792],{"href":176792,"rel":176793},"https://confs.tech/",[816],[5332,176795,176796],{},[812,176797,176798],{"href":176798,"rel":176799},"https://www.papercall.io/",[816],[5332,176801,176802],{},[812,176803,176804],{"href":176804,"rel":176805},"https://twitter.com/TechDailyCFP",[816],[5332,176807,176808],{},[812,176809,176810],{"href":176810,"rel":176811},"https://trello.com/b/I263Hm5u/events-for-speakers",[816],[651,176813,176814],{},"If you have any lists that I am missing in the Java, JavaScript, Web space please let me know. I put together a list based on those links and I have identified some conferences I will begin submitting to as the Call For Papers opens up. This is still a work in progress but here is a screenshot of that list in Notion.",[651,176816,176817],{},[660,176818],{"alt":176819,"src":176820},"Notion Conference List","/images/newsletter/2022/11/28/notion-conference-list.png",[4542,176822,176824],{"id":176823},"_20000-subscriber-milestone-on-youtube","20,000 Subscriber Milestone on YouTube",[651,176826,176827],{},"I hit a huge milestone last week on YouTube passing 20,000 subscribers.",[651,176829,176830],{},[812,176831,176832],{"href":176832,"rel":176833},"https://twitter.com/therealdanvega/status/1596340618917076993",[816],[651,176835,176836],{},"It’s crazy to me even writing that number after all the work that I have done producing videos over the years. It’s crazy to look back on your career after all this time and see that the signs were there even in the beginning. In 2001 I was doing technical support for a VoIP call center software that was written in Java. I got tired of answering the same questions over and over and decided to put together videos showing customers how to perform common tasks. This might seem as normal as walking these days but back in 2001 YouTube hadn’t even been created yet. I still remember to this day enjoying putting those videos and documentation together to help people. Like I said, the signs were there.",[651,176838,176839],{},"I want to start off by saying thank you to everyone who has supported me and the channel over the years. I appreciate your support and your feedback and you push me every single day to get better. I remember struggling early on in my career to learn new things and it's my hope that through my tutorials something clicks and you are able to learn a new language, framework, product, or skill. I hope you can use these new skills to build something amazing or land that dream job of yours.",[651,176841,176842],{},"I sat down and made a video talking about this huge milestone. In this video, I decided to go back and take a look at some of my earlier videos on the channel and provide some feedback. I hope you enjoy this and I look forward to making a couple more of these at 50,000 and 100,000 🤯",[5988,176844],{"id":97067},[4542,176846,82720],{"id":47833},[5316,176848,176849,176855],{},[5332,176850,176851],{},[812,176852,176854],{"href":176439,"rel":176853},[816],"Spring Office Hours with Daniel Garnier on Spring Security",[5332,176856,176857],{},[812,176858,95859],{"href":176859,"rel":176860},"https://youtu.be/gj1DDymw5iY",[816],[5909,176862,173888],{"id":173887},[5316,176864,176865,176870,176875],{},[5332,176866,176867],{},[812,176868,176441],{"href":176439,"rel":176869},[816],[5332,176871,176872,175258],{},[812,176873,175257],{"href":162046,"rel":176874},[816],[5332,176876,176877,175264],{},[812,176878,175257],{"href":162046,"rel":176879},[816],[4542,176881,157574],{"id":157573},[5909,176883,164959],{"id":69848},[5316,176885,176886,176893,176900,176907,176914],{},[5332,176887,176888],{},[812,176889,176892],{"href":176890,"rel":176891},"https://www.notion.so/blog/introducing-notion-ai",[816],"Introducing Notion AI",[5332,176894,176895],{},[812,176896,176899],{"href":176897,"rel":176898},"https://dashaun.com/posts/spring-boot-3-buildpacks-with-testcontainers-cloud/",[816],"Spring Boot 3 Buildpacks with Testcontainers Cloud",[5332,176901,176902],{},[812,176903,176906],{"href":176904,"rel":176905},"https://dashaun.com/posts/paketo-aarch64-builder-spring-boot-3-rc1/",[816],"A new builder for Spring Boot 3 RC1 on ARM64",[5332,176908,176909],{},[812,176910,176913],{"href":176911,"rel":176912},"https://springbootlearning.medium.com/twas-the-night-before-spring-boot-3-0-went-ga-f0b51c1b0a7b",[816],"Twas the night before Spring Boot 3.0 went GA…",[5332,176915,176916],{},[812,176917,176920],{"href":176918,"rel":176919},"https://betterprogramming.pub/going-native-with-spring-boot-3-ga-4e8d91ab21d3",[816],"Going Native With Spring Boot 3 GA",[5909,176922,164971],{"id":157591},[5316,176924,176925,176932,176937,176944,176951,176958],{},[5332,176926,176927],{},[812,176928,176931],{"href":176929,"rel":176930},"https://www.youtube.com/watch?v=qaK6N21tbVQ",[816],"6 WAYS to be a SPRING CODER",[5332,176933,176934],{},[812,176935,176151],{"href":176149,"rel":176936},[816],[5332,176938,176939],{},[812,176940,176943],{"href":176941,"rel":176942},"https://www.youtube.com/watch?v=TOfYlLjXufw&t=731s",[816],"Spring Tips: the road to Spring Boot 3: ahead-of-time compilation and GraalVM",[5332,176945,176946],{},[812,176947,176950],{"href":176948,"rel":176949},"https://www.youtube.com/watch?v=VWdNfvNFXUc",[816],"Spring boot 3.0 - The full migration guide",[5332,176952,176953],{},[812,176954,176957],{"href":176955,"rel":176956},"https://www.youtube.com/watch?v=4YyJUS_7rQE&t=691s",[816],"Spring 6 and Problem Details",[5332,176959,176960],{},[812,176961,176964],{"href":176962,"rel":176963},"https://www.youtube.com/watch?v=QXgq_e7etXc&t=33s",[816],"Spring 6 and Declarative Clients",[5909,176966,164983],{"id":39439},[5316,176968,176969,176976],{},[5332,176970,176971],{},[812,176972,176975],{"href":176973,"rel":176974},"https://bootifulpodcast.fm/#/episodes/3259c661-0524-46dc-805d-2388011030b9",[816],"Java Champion, legendary engineer, and author Trisha Gee",[5332,176977,176978],{},[812,176979,176982],{"href":176980,"rel":176981},"https://foojay.io/today/foojay-podcast-6/",[816],"Foojay Podcast #6: Welcome to Foojay!",[5909,176984,164995],{"id":133422},[5316,176986,176987,176994,177001],{},[5332,176988,176989],{},[812,176990,176993],{"href":176991,"rel":176992},"https://polypane.app/",[816],"Polypane",[5332,176995,176996],{},[812,176997,177000],{"href":176998,"rel":176999},"https://www.descript.com/blog/article/all-new-descript-backed-by-openai-startup-fund",[816],"It's here: the all-new Descript, backed by OpenAI Startup Fund",[5332,177002,177003],{},[812,177004,177007],{"href":177005,"rel":177006},"https://www.notion.so/ai",[816],"Notion AI",[4542,177009,157704],{"id":157703},[651,177011,177012],{},"I hope you enjoyed this installment of the newsletter and I will talk to you in the next one. I hope you have a great week and as always friends...",[651,177014,41105,177015,69920,177017,177019,177021,177023],{},[41107,177016],{},[41107,177018],{},[812,177020,161560],{"href":161111},[41107,177022],{},[812,177024,53869],{"href":53869,"rel":177025},[816],{"title":674,"searchDepth":790,"depth":790,"links":177027},[177028,177029,177030,177031,177032,177035,177041],{"id":173848,"depth":790,"text":173849},{"id":176680,"depth":790,"text":176681},{"id":79608,"depth":790,"text":79609},{"id":176823,"depth":790,"text":176824},{"id":47833,"depth":790,"text":82720,"children":177033},[177034],{"id":173887,"depth":892,"text":173888},{"id":157573,"depth":790,"text":157574,"children":177036},[177037,177038,177039,177040],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":164995},{"id":157703,"depth":790,"text":157704},{"slug":176680,"date":177043},"2022-11-28T19:00:00.000Z","/newsletter/2022/11/28/spring-boot-3",{"title":176655,"description":176660},"newsletter/2022/11/28/spring-boot-3","K7uiVIWWovx-mOmVKfw0hmyjSeyvGg6RollyfY23MPI",{"id":177049,"title":177050,"body":177051,"description":177055,"extension":793,"meta":177334,"navigation":797,"path":177337,"seo":177338,"stem":177339,"__hash__":177340},"content/newsletter/2022/12/05/aws-lambda-snapstart.md","AWS Lambda SnapStart, Spring Security 6 & IntelliJ 2022.3",{"type":648,"value":177052,"toc":177320},[177053,177056,177060,177069,177083,177086,177089,177098,177106,177109,177114,177116,177124,177129,177133,177140,177143,177149,177152,177158,177161,177167,177169,177197,177199,177216,177218,177220,177257,177259,177274,177276,177299,177301,177307],[651,177054,177055],{},"Happy Monday and welcome to another edition of the newsletter. It’s hard to believe that we have crossed into December and that the new year is right around the corner. Today I would like to talk to you about AWS SnapStart, Spring Security 6, and IntelliJ 2022.3",[4542,177057,177059],{"id":177058},"aws-snapstart","AWS SnapStart",[651,177061,177062,177063,177068],{},"Last week was Amazon’s big conference ",[812,177064,177067],{"href":177065,"rel":177066},"https://reinvent.awsevents.com/",[816],"re:Invent 2022"," which took place in Las Vegas. There were a ton of really exciting announcements that came out of the conference but for me, Lambda SnapStart really got my attention. What is AWS Lambda SnapStart you ask?",[1004,177070,177071],{},[651,177072,177073,177077,177078,177082],{},[812,177074,95742],{"href":177075,"rel":177076},"https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html",[816]," is a new performance optimization developed by AWS that can significantly improve the startup time for applications. Announced at ",[812,177079,177081],{"href":177065,"rel":177080},[816],"AWS re:Invent","\n 2022, the first capability to feature SnapStart is Lambda SnapStart for Java. This feature delivers up to 10x faster function startup times for latency-sensitive Java applications at no extra cost, and with minimal or no code changes.",[651,177084,177085],{},"This is huge because one of the only problems with writing serverless functions in Java is the cold start times. Each subsequent request of the function is never really an issue so this promise of up to 10x faster is huge. This is currently only supported for Java and is available in certain regions.",[651,177087,177088],{},"The one limitation is that currently it only supports Java 11 (Corretto) so you won’t be able to use Spring Boot 3 because that requires a baseline of Java 17. I did reach out to someone and they said they are working on support for Java 17 in Lambda but it isn’t available at this time.",[651,177090,177091,177092,177097],{},"I decided to give this a try with plain old Java at first and it worked out really great. It is easy to enable and I could see the performance improvements right away. I created a ",[812,177093,177096],{"href":177094,"rel":177095},"https://www.youtube.com/shorts/L2oJ-nL-Zfk",[816],"YouTube short"," talking about how to enable this in your Lambda Function.",[651,177099,177100,177101,177105],{},"After that, I wanted to see what this meant for Spring developers. I decided to create a new Spring Boot project using 2.7.6 and Java 11 with Spring Web and Spring Cloud Function. If you’re new to ",[812,177102,95761],{"href":177103,"rel":177104},"https://spring.io/projects/spring-cloud-function",[816]," it allows you to use a uniform programming model across serverless providers, as well as the ability to run standalone (locally or in a PaaS).",[651,177107,177108],{},"I created a very simple function that takes in a String and reverses it. While this was a trivial example it allowed me to see that the performance improvements were HUGE for Spring developers. The initial cold start time for my function before enabling SnapStart was almost 5 seconds and 312 ms after enabling it. I decided to create a video on this which you can check out below.",[651,177110,177111],{},[676,177112,177113],{},"youtube:https://www.youtu.be/isS6m6aj_Ak",[4542,177115,176738],{"id":95723},[651,177117,177118,177119,177123],{},"With the release of Spring Boot 3 comes the shiny and new Spring Security 6. There are a number of breaking changes in this version that include some deprecations that have been removed. If you want to learn more about these changes you can read through the ",[812,177120,40844],{"href":177121,"rel":177122},"https://docs.spring.io/spring-security/reference/whats-new.html",[816],". If you are updating your Spring Boot projects from 2.7.x to 3 and you use Spring Security these are changes that you will need to take a look at. I decided to pick out 3 of those changes and create a tutorial on them.",[651,177125,177126],{},[676,177127,177128],{},"youtube:https://www.youtu.be/TDOHbK39Oxg",[4542,177130,177132],{"id":177131},"intellij-20223","IntelliJ 2022.3",[651,177134,177135,177136,664],{},"If you follow me you know that I am a huge fan of IntelliJ. They just released a new version of IntelliJ IDEA and it comes packed with some really great features. If you want to learn more about it you can check out the ",[812,177137,95704],{"href":177138,"rel":177139},"https://blog.jetbrains.com/idea/2022/11/intellij-idea-2022-3/",[816],[651,177141,177142],{},"There were 3 changes that really stood out for me. First was the new UI that you can enable by going into Settings > Appearance & Behavior > New UI and enabling the new UI. My initial thoughts on the UI are WOW 🤩 I love the new look, icons shiny buttons at the top. I also go asked about my new setup a lot and I am using the new UI with the GitHub Dark (Experimental) theme.",[651,177144,177145],{},[660,177146],{"alt":177147,"src":177148},"IntelliJ New UI","/images/newsletter/2022/12/05/intellij_new_ui_2.jpeg",[651,177150,177151],{},"As a Spring Developer, I really love the new feature that allows you to add a dependency to the class you’re working on. To show off this feature I created a quick short-form video and posted it to Twitter.",[651,177153,177154],{},[812,177155,177156],{"href":177156,"rel":177157},"https://twitter.com/therealdanvega/status/1598421479846285314",[816],[651,177159,177160],{},"Finally, I am a big fan of Vue and Vite. You can now create a new Vite project right within IntelliJ using the new project generator",[651,177162,177163],{},[660,177164],{"alt":177165,"src":177166},"IntelliJ Vite Project","/images/newsletter/2022/12/05/intellij_vite.jpeg",[4542,177168,82720],{"id":47833},[5316,177170,177171,177178,177184,177191],{},[5332,177172,177173],{},[812,177174,177177],{"href":177175,"rel":177176},"https://www.youtube.com/shorts/C_njlumpFc4",[816],"Spring Framework 6 Trailing Slash Changes #shorts",[5332,177179,177180],{},[812,177181,177183],{"href":177094,"rel":177182},[816],"AWS Lambda SnapStart for Java #shorts",[5332,177185,177186],{},[812,177187,177190],{"href":177188,"rel":177189},"https://www.youtube.com/watch?v=TDOHbK39Oxg",[816],"What’s new in Spring Security 6",[5332,177192,177193],{},[812,177194,366],{"href":177195,"rel":177196},"https://www.youtube.com/watch?v=isS6m6aj_Ak",[816],[5909,177198,173888],{"id":173887},[5316,177200,177201,177209],{},[5332,177202,177203,177208],{},[812,177204,177207],{"href":177205,"rel":177206},"https://www.codemash.org/session-details/?id=380331",[816],"CodeMash 2023: Getting Started with Spring"," (Workshop)",[5332,177210,177211],{},[812,177212,177215],{"href":177213,"rel":177214},"https://www.codemash.org/session-details/?id=380324",[816],"CodeMash 2023: Give your APIs a REST & Make the move to GraphQL",[4542,177217,157574],{"id":157573},[5909,177219,164959],{"id":69848},[5316,177221,177222,177229,177236,177243,177250],{},[5332,177223,177224],{},[812,177225,177228],{"href":177226,"rel":177227},"https://maciejwalkowiak.com/blog/whats-new-in-spring/",[816],"What's new in Spring?",[5332,177230,177231],{},[812,177232,177235],{"href":177233,"rel":177234},"https://devblogs.microsoft.com/java/two-million-java-developers-on-visual-studio-code-november-2022-update/",[816],"Two million Java developers on Visual Studio Code! November 2022 Update",[5332,177237,177238],{},[812,177239,177242],{"href":177240,"rel":177241},"https://medium.com/javarevisited/my-first-javafx-application-ee70a1d48cb3",[816],"My first JavaFX application",[5332,177244,177245],{},[812,177246,177249],{"href":177247,"rel":177248},"https://aws.amazon.com/de/blogs/aws/new-accelerate-your-lambda-functions-with-lambda-snapstart/",[816],"New – Accelerate Your Lambda Functions with Lambda SnapStart",[5332,177251,177252],{},[812,177253,177256],{"href":177254,"rel":177255},"https://www.moderne.io/blog/speed-your-spring-boot-3-0-migration",[816],"Speed Your Spring Boot 3.0 Migration",[5909,177258,164971],{"id":157591},[5316,177260,177261,177268],{},[5332,177262,177263],{},[812,177264,177267],{"href":177265,"rel":177266},"https://www.youtube.com/watch?v=ypN-Uwshc5M",[816],"Why Container Queries Will Revolutionize Responsive Web Dev",[5332,177269,177270],{},[812,177271,177272],{"href":177272,"rel":177273},"https://www.youtube.com/watch?v=AgxvrZLI1mc",[816],[5909,177275,164983],{"id":39439},[5316,177277,177278,177285,177292],{},[5332,177279,177280],{},[812,177281,177284],{"href":177282,"rel":177283},"https://www.youtube.com/watch?v=WP0063oIyJ0",[816],"Between Chair and Keyboard with Ekaterina Novoseltseva",[5332,177286,177287],{},[812,177288,177291],{"href":177289,"rel":177290},"https://www.tanzutalk.com/e/what-is-graavm-what-is-spring-native-and-why-do-you-need-native-java/",[816],"What is GraalVM? What is Spring Native? And why do you need native Java?",[5332,177293,177294],{},[812,177295,177298],{"href":177296,"rel":177297},"https://hubermanlab.com/dr-lex-fridman-navigating-conflict-finding-purpose-and-maintaining-drive/",[816],"Huberman Lab - Dr. Lex Fridman: Navigating Conflict, Finding Purpose & Maintaining Drive",[4542,177300,157704],{"id":157703},[651,177302,177303,177304,166271],{},"I hope you enjoyed this installment of the newsletter and I will talk to you in the next one. If you have any links you would like me to include please ",[812,177305,41499],{"href":44086,"rel":177306},[816],[651,177308,41105,177309,69920,177311,177313,177315,177317],{},[41107,177310],{},[41107,177312],{},[812,177314,161560],{"href":161111},[41107,177316],{},[812,177318,53869],{"href":53869,"rel":177319},[816],{"title":674,"searchDepth":790,"depth":790,"links":177321},[177322,177323,177324,177325,177328,177333],{"id":177058,"depth":790,"text":177059},{"id":95723,"depth":790,"text":176738},{"id":177131,"depth":790,"text":177132},{"id":47833,"depth":790,"text":82720,"children":177326},[177327],{"id":173887,"depth":892,"text":173888},{"id":157573,"depth":790,"text":157574,"children":177329},[177330,177331,177332],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":157703,"depth":790,"text":157704},{"slug":177335,"date":177336},"aws-lambda-snapstart","2022-12-05T19:00:00.000Z","/newsletter/2022/12/05/aws-lambda-snapstart",{"title":177050,"description":177055},"newsletter/2022/12/05/aws-lambda-snapstart","tgHpiq2Eqtn60a-yeSa4xMP7VgQ9UaR5PvooUv20-_U",{"id":177342,"title":177343,"body":177344,"description":177348,"extension":793,"meta":177552,"navigation":797,"path":177555,"seo":177556,"stem":177557,"__hash__":177558},"content/newsletter/2022/12/12/whats-new-spring-boot-3.md","What’s new in Spring Boot 3",{"type":648,"value":177345,"toc":177538},[177346,177349,177351,177354,177357,177366,177369,177374,177378,177393,177396,177399,177405,177407,177419,177421,177424,177426,177449,177451,177473,177475,177491,177495,177505,177507,177510,177512,177518,177520,177525],[651,177347,177348],{},"Happy Monday and welcome to another edition of the newsletter. In today’s newsletter, I want to talk to you about the newest release of Spring Framework 6 and Spring Boot 3. I also want to spend a little time talking to you about a 4-hour workshop I’m working on for CodeMash on Getting Started with Spring.",[4542,177350,176681],{"id":176680},[651,177352,177353],{},"In case you have been living under a rock Spring Framework 6 and Spring Boot 3 have been released. I have spent some time over the last couple of months really trying to understand everything that has gone into this release.",[651,177355,177356],{},"While I still have a lot to learn I feel like I have a good handle on the key features of these releases. Last week (alongside my colleagues Nate and DaShaun) I gave a presentation internally to a large group of coworkers about what is new in Spring Boot 3. I thought it was a great high-level discussion about what we were excited about in the next generation of Spring.",[651,177358,177359,177360,177365],{},"A lot of the questions we received were about what customers would need to do to Upgrade from 2.x to 3.0. I think the biggest advice we had in this area was to make the upgrade incrementally. If you aren't already on 2.7.x try and make this upgrade first. The latest 2.7 versions support Java 17 so make the move to Java 17 in that version if possible. Once you are there it’s a much smoother process to get to 3. Finally, there is a really great ",[812,177361,177364],{"href":177362,"rel":177363},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide",[816],"migration guide"," that you can go through that will help you with this process.",[651,177367,177368],{},"I have been building a tutorial that would show off a lot of these features in a video tutorial for a few weeks now. I was finally able to record and edit this video that ended up coming in at 1 hour and 10 minutes. In this tutorial, I will walk you through some of the major features in Spring Framework 6 and Spring Boot as well as some of my favorite features that are flying in under the radar.",[651,177370,177371],{},[676,177372,177373],{},"youtube:https://www.youtu.be/TR254zh-f3c",[4542,177375,177377],{"id":177376},"getting-started-with-spring-workshop","Getting Started with Spring Workshop",[651,177379,177380,177383,177384,177387,177388,177392],{},[812,177381,172677],{"href":173048,"rel":177382},[816]," and I are building a workshop for ",[812,177385,97312],{"href":177205,"rel":177386},[816]," and we thought we would discuss our plan for the 4-hour workshop on the latest episode of ",[812,177389,97345],{"href":177390,"rel":177391},"https://youtu.be/OtaQZij3emQ",[816],". We broke down the agenda we came up for the workshop and discussed some of the decisions we had to make.",[651,177394,177395],{},"I think when you hear 4 hours you generally think that is a long time to learn something. The problem with that thinking is that Spring is large and there is a lot to cover. When you’re doing an in person workshop you also have to take into account helping attendees work through any issues and built-in breaks every hour or so.",[651,177397,177398],{},"We had to end up cutting testing, security and some of details in the Spring to Production section. With that said I am really happy with our initial outline. We are going to work through our presentation over the next few weeks and this will probably changed based on our dry runs but its a great start. If you’re interested in what we came up with you can check out the agenda in the GitHub repository below that will be changing over the next month.",[651,177400,177401],{},[812,177402,177403],{"href":177403,"rel":177404},"https://github.com/danvega/getting-started-spring-workshop",[816],[4542,177406,173888],{"id":173887},[5316,177408,177409,177414],{},[5332,177410,177411,175258],{},[812,177412,175257],{"href":162046,"rel":177413},[816],[5332,177415,177416,175264],{},[812,177417,175257],{"href":162046,"rel":177418},[816],[4542,177420,157574],{"id":157573},[651,177422,177423],{},"These are a few of the links I found interesting around the web. In the new year I am going to condense this list to just a few items but give you more details on my thoughts on each of them. I hope you’re enjoying this newsletter and if you are please consider sharing it with a friend 🙏",[5909,177425,164959],{"id":69848},[5316,177427,177428,177435,177442],{},[5332,177429,177430],{},[812,177431,177434],{"href":177432,"rel":177433},"https://firebase.blog/posts/2022/11/investing-in-vuefire-nuxt-vue",[816],"Investing in Vue, Nuxt, and VueFire",[5332,177436,177437],{},[812,177438,177441],{"href":177439,"rel":177440},"https://www.wimdeblauwe.com/blog/2022/12/11/release-1.0.0-and-2.0.0-of-htmx-spring-boot-thymeleaf/",[816],"Release 1.0.0 and 2.0.0 of htmx-spring-boot-thymeleaf",[5332,177443,177444],{},[812,177445,177448],{"href":177446,"rel":177447},"https://ticketguy.dev/blogs/2022-12-05-cold-start-java/",[816],"Serverless cold start for the rest of us",[5909,177450,164971],{"id":157591},[5316,177452,177453,177460,177466],{},[5332,177454,177455],{},[812,177456,177459],{"href":177457,"rel":177458},"https://www.youtube.com/watch?v=ZbnAithBNYY",[816],"AWS re:Invent 2022 - AWS Lambda SnapStart: Fast cold starts for your Java functions",[5332,177461,177462],{},[812,177463,177464],{"href":177464,"rel":177465},"https://www.youtube.com/watch?v=qHI8E92W9ZA&t=1s",[816],[5332,177467,177468],{},[812,177469,177472],{"href":177470,"rel":177471},"https://www.youtube.com/watch?v=eUPejYY1B3U",[816],"How to build a Google Photos Clone in Java - Part 1",[5909,177474,165005],{"id":39340},[5316,177476,177477,177484],{},[5332,177478,177479],{},[812,177480,177483],{"href":177481,"rel":177482},"https://www.manning.com/books/cloud-native-spring-in-action",[816],"Cloud Native Spring in Action",[5332,177485,177486],{},[812,177487,177490],{"href":177488,"rel":177489},"https://amzn.to/3iZVRhy",[816],"Learning Spring Boot 3",[5909,177492,177494],{"id":177493},"️newsletters","🗞️ Newsletters",[5316,177496,177497],{},[5332,177498,177499,177504],{},[812,177500,177503],{"href":177501,"rel":177502},"https://kenkousen.substack.com/p/tales-from-the-jar-side-spring-6",[816],"Tales from the Jar Side"," - Thanks for the mention Ken 👋🏻",[5909,177506,165017],{"id":165016},[651,177508,177509],{},"“I’ve used enough programming languages to understand there is no perfect language and I’m ok using the one I prefer vs the one that is better” - Kelsey Hightower",[5909,177511,165430],{"id":165429},[651,177513,177514],{},[812,177515,177516],{"href":177516,"rel":177517},"https://twitter.com/therealdanvega/status/1600291708486307840",[816],[4542,177519,157704],{"id":157703},[651,177521,177303,177522,166271],{},[812,177523,41499],{"href":44086,"rel":177524},[816],[651,177526,41105,177527,69920,177529,177531,177533,177535],{},[41107,177528],{},[41107,177530],{},[812,177532,161560],{"href":161111},[41107,177534],{},[812,177536,53869],{"href":53869,"rel":177537},[816],{"title":674,"searchDepth":790,"depth":790,"links":177539},[177540,177541,177542,177543,177551],{"id":176680,"depth":790,"text":176681},{"id":177376,"depth":790,"text":177377},{"id":173887,"depth":790,"text":173888},{"id":157573,"depth":790,"text":157574,"children":177544},[177545,177546,177547,177548,177549,177550],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39340,"depth":892,"text":165005},{"id":177493,"depth":892,"text":177494},{"id":165016,"depth":892,"text":165017},{"id":165429,"depth":892,"text":165430},{"id":157703,"depth":790,"text":157704},{"slug":177553,"date":177554},"whats-new-spring-boot-3","2022-12-12T19:00:00.000Z","/newsletter/2022/12/12/whats-new-spring-boot-3",{"title":177343,"description":177348},"newsletter/2022/12/12/whats-new-spring-boot-3","842T1fGyGUD-sZWg00bUn56DlUvdPZqdS71nL6Q69Fc",{"id":177560,"title":177561,"body":177562,"description":177566,"extension":793,"meta":177811,"navigation":797,"path":177814,"seo":177815,"stem":177816,"__hash__":177817},"content/newsletter/2022/12/19/happy-holidays.md","Happy Holidays, Spring Workshop, and new content",{"type":648,"value":177563,"toc":177798},[177564,177567,177573,177576,177578,177586,177589,177592,177622,177629,177633,177636,177645,177648,177652,177671,177677,177685,177693,177701,177713,177720,177723,177732,177735,177753,177756,177759,177765,177767,177775,177778,177780,177785],[651,177565,177566],{},"Happy Monday and welcome to the last edition of the newsletter in 2022. I’m sure we say this every single year but it’s really hard to believe that another year is almost behind us. Before we get to the good stuff I want to take this opportunity to thank you. This has been one of the best professional years of my career and I owe a lot of that to the support that you have given to me. I hope you and your family have a Happy Holiday and a wonderful time ringing in the new year.",[651,177568,177569],{},[660,177570],{"alt":177571,"src":177572},"Happy Holidays","/images/newsletter/2022/12/19/clint-patterson-1-wBnPhsmJY-unsplash.jpg",[651,177574,177575],{},"I am working a few days this week and then I am out until the new year. During that time I hope to work on my end-of-the-year reflections as well as goals / what I want to focus on in the new year. In today’s newsletter, I want to talk to you about the workshop I am currently working on, new content, and some travel plans for 2023.",[4542,177577,177377],{"id":177376},[651,177579,177580,177581,177585],{},"I’m currently working on a workshop that I will be delivering the 2nd week of January at CodeMash. I have talked about this before but this will be a 4-hour workshop which I think is the longest live presentation I have ever given. I have created a few 12-14 hour courses but those are totally different animals. DaShaun and I had another ",[812,177582,97345],{"href":177583,"rel":177584},"https://youtu.be/Rx-qq3likgM",[816]," last week where we discussed our plans for the workshop.",[651,177587,177588],{},"In this episode, we had a chance to talk about the application we are going to build in the workshop which is called Runnerz. We both love to run and needed a domain to speak on so we figured this would be perfect. We had a chance to discuss what part of the domain model we would cover without getting too far into it and off-topic.",[651,177590,177591],{},"I also had a chance to really start diving into each of the sections that I am going to deliver and come up with a plan. The first section I went through was the core fundamentals and features of Spring & Spring Boot. I want to keep this section around an hour long so I had to be selective with what I wanted to teach and this is what I settled on:",[5316,177593,177594],{},[5332,177595,177596,177597],{},"Core Fundamentals & Features\n",[5316,177598,177599,177602,177605,177608,177611,177613,177616,177618,177620],{},[5332,177600,177601],{},"Structuring your code",[5332,177603,177604],{},"Spring IoC Container / Application Context",[5332,177606,177607],{},"Spring Beans",[5332,177609,177610],{},"Dependency Injection",[5332,177612,6478],{},[5332,177614,177615],{},"Profiles",[5332,177617,39865],{},[5332,177619,56408],{},[5332,177621,57060],{},[651,177623,177624,177625,177628],{},"I have a video on ",[812,177626,177610],{"href":23786,"rel":177627},[816]," that comes in at 30 minutes so even getting through this list is going to be a challenge. This week I will be doing some dry runs of each of my sections and getting everything ready to go. When I get back from break I only have a week to have this workshop and my talk on GraphQL polished up and ready to go. I’m beyond excited about CodeMash and I hope to see some of you there!",[4542,177630,177632],{"id":177631},"presentations-in-2023","Presentations in 2023",[651,177634,177635],{},"I have spent a lot of time lately identifying what conferences, meetups, or anyone that will have me present next year. This involved going through past conferences as well as asking my friends on the bird app for some resources on upcoming conferences. I have also spent a lot of time working on abstracts of topics that I am interested in focusing on next year.",[651,177637,177638,177639,177644],{},"I’m happy to announce that some of that hard work has already paid off I have been accepted to speak at Devnexus in Atlanta. I will be presenting with my friend Nate Schutta on ",[812,177640,177643],{"href":177641,"rel":177642},"https://devnexus.com/presentations/spring-recipes-a-collection-of-common-sense-solutions",[816],"Spring Recipes - A collection of common sense solutions",". We gave this talk to KCDC last year but we are working on a major update so even if you had a chance to see this talk please consider checking it out as it will be different.",[651,177646,177647],{},"I was also excited to find out that DaShaun and I will be traveling to San Francisco in January for an event. We will be talking about Spring Framework 6 and Spring Boot 3. I actually have 3 talks so far in January on the same subject so it’s going to be a fun month talking to everyone about the next generation of The Spring Framework.",[5909,177649,177651],{"id":177650},"️upcoming-talks","🎙️Upcoming Talks",[5316,177653,177654,177659,177664],{},[5332,177655,177656,175258],{},[812,177657,175257],{"href":162046,"rel":177658},[816],[5332,177660,177661,175264],{},[812,177662,175257],{"href":162046,"rel":177663},[816],[5332,177665,177666,177670],{},[812,177667,177669],{"href":177641,"rel":177668},[816],"Devnexus 2023",": Spring Recipes - A collection of common-sense solutions",[5909,177672,177674],{"id":177673},"my-new-videos",[2939,177675,177676],{},"🎬 My New Videos",[651,177678,177679,177684],{},[812,177680,177683],{"href":177681,"rel":177682},"https://youtube.com/shorts/wOYTJLQ0xNM?feature=share",[816],"IntelliJ Postfix"," - Learn about a quick tip in IntelliJ called postfix autocompletion. This postfix allows you to create a new variable from a statement in Java.",[651,177686,177687,177692],{},[812,177688,177691],{"href":177689,"rel":177690},"https://youtu.be/dyA_Pbtbn_E",[816],"Google reCaptcha + Spring Security"," - In this tutorial, you will learn how to integrate Google reCaptcha v3 in a Spring Boot application using Spring Security. If you want to learn how to create your own Spring Security Filter and write custom code to integrate with a third-party service like Google reCaptcha this is the tutorial for you.",[651,177694,177695,177700],{},[812,177696,177699],{"href":177697,"rel":177698},"https://youtu.be/6kFzJZCW1Qw",[816],"Spring Security JWT Client"," - I have created a bunch of tutorials lately on Spring Security and the feedback has been amazing. The one suggestion I kept hearing was that you wanted to see how a front-end (client) would use the Spring Security JWT authentication. In this tutorial, I created a client from scratch using Vite + Vue 3.",[651,177702,177703,177708,177709,177712],{},[812,177704,177707],{"href":177705,"rel":177706},"https://youtu.be/B5Zrn1Tzyqw",[816],"Response Entity"," - In this tutorial, I discuss what the ",[676,177710,177711],{},"ResponseEntity"," class is in Spring and when you might want to reach for it. This is in response to code samples that I have seen lately that automatically use the response entity as the return type when there was no manipulation of the response headers or status.",[651,177714,177715,177719],{},[812,177716,177718],{"href":177583,"rel":177717},[816],"Spring Office Hours Episode 23"," - In this episode DaShaun and I discussed the plans for the upcoming workshop “Getting Started with Spring”. We had a great discussion about the domain of the application we are building for the workshop.",[4542,177721,177722],{"id":157573},"🌎 Around the web",[651,177724,177725,177726,177731],{},"As I hinted at in an earlier newsletter I am going to change the format of this section a bit. Part of my anxiety in putting this newsletter together is making sure I have enough links to interesting things around the web. Going forward I am going to share a few items but give a little more commentary on each. If I don’t happen to come across anything interesting this section will be empty 🤷♂️ I am drawing this inspiration from ",[812,177727,177730],{"href":177728,"rel":177729},"https://aliabdaal.com/newsletter/",[816],"Ali Abdaal’s Sunday Snippets Newsletter"," which I am a huge fan of. As always your feedback is welcome.",[5909,177733,177734],{"id":133422},"👨💻 Projects",[651,177736,177737,177742,177743,177748,177749,177752],{},[812,177738,177741],{"href":177739,"rel":177740},"https://just.maciejwalkowiak.com/",[816],"Just"," is a command-line tool for developing Spring Boot applications by ",[812,177744,177747],{"href":177745,"rel":177746},"https://twitter.com/maciejwalkowiak",[816],"Maciej Walkowiak",". While I haven’t had a chance to give this a try myself the video he made is amazing. This tool will give you single commands like ",[676,177750,177751],{},"just run"," which will run your application regardless of what build tool you’re using and offers live reloading. I’m not doing it justice here and you need to head over to the website (which is beautiful by the way) and check out everything Just has to offer.",[5909,177754,177755],{"id":165429},"🐦 Tweets",[651,177757,177758],{},"There are rumors going around that the crazy spaceman wants to increase the character limit from 280 to 4000. This tweet was a great visual representation of what that change would mean. I’m not a big fan of this because it would require way more time to scroll through information 🤦♂️",[651,177760,177761],{},[812,177762,177763],{"href":177763,"rel":177764},"https://twitter.com/BasicAppleGuy/status/1602032985611833344",[816],[5909,177766,165017],{"id":165016},[651,177768,177769,177770],{},"“Don’t stop when you’re tired, stop when you’re done.” - ",[812,177771,177774],{"href":177772,"rel":177773},"https://twitter.com/davidgoggins",[816],"@davidgoggins",[651,177776,177777],{},"I have consumed a lot of content from David over the past month and wow what an interesting human being. I need to pick up his book but I also have a whole bookshelf full of books I want to read 🤔",[4542,177779,157704],{"id":157703},[651,177781,177303,177782,166271],{},[812,177783,41499],{"href":44086,"rel":177784},[816],[651,177786,41105,177787,69920,177789,177791,177793,177795],{},[41107,177788],{},[41107,177790],{},[812,177792,161560],{"href":161111},[41107,177794],{},[812,177796,53869],{"href":53869,"rel":177797},[816],{"title":674,"searchDepth":790,"depth":790,"links":177799},[177800,177801,177805,177810],{"id":177376,"depth":790,"text":177377},{"id":177631,"depth":790,"text":177632,"children":177802},[177803,177804],{"id":177650,"depth":892,"text":177651},{"id":177673,"depth":892,"text":177676},{"id":157573,"depth":790,"text":177722,"children":177806},[177807,177808,177809],{"id":133422,"depth":892,"text":177734},{"id":165429,"depth":892,"text":177755},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":177812,"date":177813},"happy-holidays","2022-12-19T19:00:00.000Z","/newsletter/2022/12/19/happy-holidays",{"title":177561,"description":177566},"newsletter/2022/12/19/happy-holidays","0Wayjy4Ya5lEnuW73kG5DcwhaThHYNxlZostgpLvreA",{"id":177819,"title":177820,"body":177821,"description":177825,"extension":793,"meta":177969,"navigation":797,"path":177971,"seo":177972,"stem":177973,"__hash__":177974},"content/newsletter/2023/01/01/happy-new-year-2023.md","Wishing you a Happy New Year!",{"type":648,"value":177822,"toc":177954},[177823,177826,177830,177833,177846,177850,177853,177860,177862,177865,177870,177876,177878,177881,177884,177887,177890,177892,177901,177903,177912,177914,177929,177931,177934,177936,177941],[651,177824,177825],{},"Happy Monday and welcome to another edition of my newsletter. I want to start out by wishing you a Happy New Year. I hope your holidays were spent with family and friends and you were able to sneak in some rest, relaxation, learning, or all of the above. I have a couple of blog posts to share with you today and I’m preparing for a busy month of presentations.",[4542,177827,177829],{"id":177828},"reflections-and-goals","Reflections and Goals",[651,177831,177832],{},"I enjoy taking some time over the holidays to reflect on the previous year and think about what I want to focus on in the new year. I wrote 2 new blog posts that I hope you’ll find interesting as you prepare for a new year.",[5316,177834,177835,177840],{},[5332,177836,177837],{},[812,177838,357],{"href":97269,"rel":177839},[816],[5332,177841,177842],{},[812,177843,354],{"href":177844,"rel":177845},"https://www.danvega.dev/blog/2023/01/01/happy-new-year-2023/",[816],[4542,177847,177849],{"id":177848},"presentations","Presentations",[5909,177851,97312],{"id":177852},"codemash",[651,177854,177855,177856,177859],{},"I am heading to ",[812,177857,97312],{"href":162046,"rel":177858},[816]," next week to present, learn, and connect with the community. I am giving a talk on GraphQL in Java as well as an Introduction to Spring Workshop. This week will be spent fine-tuning those presentations and making sure I cover everything I want to talk about. I am beyond excited to see many friends I haven’t seen in a while as well and to connect with others in the community. If you are going to be there please make sure you stop me and say hi 👋🏻",[5909,177861,97315],{"id":174647},[651,177863,177864],{},"If you can’t make it to CodeMash and you’re in the Cleveland area I will be giving my GraphQL talk in person on Tuesday, January 17th at Tech Elevator. The title of this talk is “Give your APIs a REST & Make the move to GraphQL” and here is a description of what I plan to cover.",[651,177866,177867],{},[7300,177868,177869],{},"In a world where multiple applications and devices are calling your APIs, you end up having to create custom endpoints for different views of your data. What if you could provide fewer endpoints and let the consumer decide on which data they need? GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. In this session, you will learn the basic concepts of GraphQL. You will then learn how to use GraphQL in your Java applications with Spring for GraphQL. By the end of this presentation, you will be able to write and validate queries, create your own GraphQL API and write automated tests against them.",[651,177871,177872],{},[812,177873,177874],{"href":177874,"rel":177875},"https://www.meetup.com/cleveland-java/",[816],[4542,177877,176681],{"id":176680},[651,177879,177880],{},"I am giving 3 presentations in January on Spring Boot 3. I believe 2 of them will be internal but the 3rd one should be public. That means as soon as I have a link to that presentation I will be sure to share it.",[651,177882,177883],{},"I have been having so much fun learning about all the new features in Spring Boot 3 and sharing what I have learned with the community. If you haven’t had a chance to check it out yet here is my video on What’s new in Spring Framework 6 and Spring Boot 3.",[5988,177885],{"id":177886},"TR254zh-f3c",[4542,177888,177889],{"id":157573},"Around the web",[5909,177891,164959],{"id":69848},[651,177893,177894,177895,177900],{},"I really enjoyed this article on ",[812,177896,177899],{"href":177897,"rel":177898},"https://thegreatapi.com/blog/create-a-library-from-scratch/",[816],"How to create a Java Library from scratch to Maven Central",". This article walks you step by step through how to create a library and then publish it a library on Maven Central. If you have never gone through this process this is a great place to start.",[5909,177902,164971],{"id":157591},[651,177904,177905,177906,177911],{},"I really enjoyed this video on ",[812,177907,177910],{"href":177908,"rel":177909},"https://www.youtube.com/watch?v=JVPHSdHViMg",[816],"7 Awesome Libraries for Java Unit & Integration Testing"," by Marco Behler. Marco produces some of the best content around and I really enjoy his video tutorials. In this one, Marco walks through 7 awesome libraries and explains what they are and when you would want to use them.",[5909,177913,164983],{"id":39439},[651,177915,177916,177917,177922,177923,177928],{},"I have come to learn that not everyone knows about ",[812,177918,177921],{"href":177919,"rel":177920},"https://bootifulpodcast.fm/#/",[816],"A Bootiful Podcast"," with Josh Long. This is a podcast dedicated to the people behind Spring and its ecosystem. In this episode, Josh interviews ",[812,177924,177927],{"href":177925,"rel":177926},"https://bootifulpodcast.fm/#/episodes/ee1262fe-d0a4-4d3b-9f8d-1e6510fedd1a",[816],"DaShaun Carter: an amazing human being, father, friend, engineer, and fellow Spring Developer Advocate",". This episode not only featured 2 of my coworkers and friends but it was also a great conversation around Spring.",[5909,177930,165017],{"id":165016},[651,177932,177933],{},"“Not all readers are leaders, but all leaders are readers.” ― Harry S. Truman",[4542,177935,157704],{"id":157703},[651,177937,177303,177938,166271],{},[812,177939,41499],{"href":44086,"rel":177940},[816],[651,177942,41105,177943,69920,177945,177947,177949,177951],{},[41107,177944],{},[41107,177946],{},[812,177948,161560],{"href":161111},[41107,177950],{},[812,177952,53869],{"href":53869,"rel":177953},[816],{"title":674,"searchDepth":790,"depth":790,"links":177955},[177956,177957,177961,177962,177968],{"id":177828,"depth":790,"text":177829},{"id":177848,"depth":790,"text":177849,"children":177958},[177959,177960],{"id":177852,"depth":892,"text":97312},{"id":174647,"depth":892,"text":97315},{"id":176680,"depth":790,"text":176681},{"id":157573,"depth":790,"text":177889,"children":177963},[177964,177965,177966,177967],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":97543,"date":177970},"2023-01-01T09:00:00.000Z","/newsletter/2023/01/01/happy-new-year-2023",{"title":177820,"description":177825},"newsletter/2023/01/01/happy-new-year-2023","nEjvJZAa0K8L3DpooOrapAeqL8PyC2JcX7SSwV_PQP8",{"id":177976,"title":175257,"body":177977,"description":177981,"extension":793,"meta":178180,"navigation":797,"path":178183,"seo":178184,"stem":178185,"__hash__":178186},"content/newsletter/2023/01/16/codemash-2023.md",{"type":648,"value":177978,"toc":178166},[177979,177982,177995,177997,178003,178011,178014,178017,178021,178024,178029,178032,178049,178052,178056,178059,178068,178074,178082,178084,178087,178093,178095,178098,178104,178106,178108,178116,178118,178127,178136,178138,178141,178146,178148,178153],[651,177980,177981],{},"Happy Monday and welcome to another edition of the newsletter. This week I want to talk to you about my week in Sandusky Ohio for CodeMash 2023 and an upcoming presentation at the Cleveland Java User Group. I always feel so refreshed and excited to get back to work after a conference and this week is no different.",[651,177983,177984,177985,177990,177991,664],{},"I’m glad to be back home and in my office before a short trip to San Francisco next week for a ",[812,177986,177989],{"href":177987,"rel":177988},"https://connect.tanzu.vmware.com/SpringOne-Essentials-Watch-Parties-2023.html",[816],"SpringOne Essentials Watch Party",". That’s right SpringOne has moved to an online experience to bring together the Spring Community. This is a virtual event that is taking place January 24-26 and you can ",[812,177992,177994],{"href":82111,"rel":177993},[816],"register for free",[4542,177996,97312],{"id":177852},[651,177998,177999,178002],{},[812,178000,97312],{"href":162046,"rel":178001},[816]," is an annual developer conference that takes place in Sandusky Ohio and is attended by thousands of developers from all over the world. This year's conference featured a variety of talks and workshops on various topics related to software development.",[651,178004,178005,178006,178010],{},"This was my 2nd time attending and speaking at the conference and like the 1st it didn’t disappoint. I remember my ",[812,178007,178009],{"href":162552,"rel":178008},[816],"first CodeMash"," very well because it took place in January of 2020. This would be the last conference and public event of any kind for me because shortly after the pandemic happened and we were locked indoors for the next 2 years.",[651,178012,178013],{},"This year's event was pretty special because I had a few of my teammates there with me. I was able to bring my family and our daughter Juliana who is 2 now wasn’t at the last one. The whole family loves going to Kalahari and it's a great way to break up the miserable cold weather that takes place in Cleveland in January.",[651,178015,178016],{},"I honestly didn’t have a ton of time to attend many sessions because I was busy with my own talks so I will give you a quick rundown of those.",[5909,178018,178020],{"id":178019},"spring-workshop","Spring Workshop",[651,178022,178023],{},"My coworker DaShaun and I gave a 4-hour Introduction to Spring workshop. Overall I thought it went pretty well but this was the first time we gave this one and we learned a few things from it that we will take into the next one. Thank you to our coworker Whitney for grabbing this 📸",[651,178025,178026],{},[660,178027],{"alt":178020,"src":178028},"/images/newsletter/2023/01/15/spring_workshop_talk.jpeg",[651,178030,178031],{},"When you go into a workshop you think that 4 hours is a long time and it turns out that it's never enough time. We knew going in that we couldn’t cover everything but we were still pretty ambitious with what we did want to cover. This was the agenda that we ended up going through:",[5316,178033,178034,178036,178039,178042,178044,178047],{},[5332,178035,25857],{},[5332,178037,178038],{},"Getting Started with Spring",[5332,178040,178041],{},"Core Features and Fundamentals",[5332,178043,39764],{},[5332,178045,178046],{},"Data",[5332,178048,173321],{},[651,178050,178051],{},"I hope we get the chance to give this workshop again so if you’re interested in us doing so please feel free to reach out to me.",[5909,178053,178055],{"id":178054},"graphql-talk","GraphQL Talk",[651,178057,178058],{},"Next up was my solo talk titled “Give your APIs a REST and make the move to GraphQL”. This talk was an introduction to GraphQL in Java using Spring for GraphQL but a lot of the content was applicable to anyone getting started with it.",[651,178060,178061,178062,178067],{},"The room was packed out and it was great to see some familiar faces in the crowd. It was also my first time testing out my new ",[812,178063,178066],{"href":178064,"rel":178065},"https://amzn.to/3IRkCrw",[816],"Logitech Spotlight pointer"," (thanks Nate) and I have to say that it worked out great!",[651,178069,178070],{},[660,178071],{"alt":178072,"src":178073},"GraphQL Presentation","/images/newsletter/2023/01/15/dan_graphql_talk.jpeg",[651,178075,178076,178077,178081],{},"This presentation was a mix of slides and live coding and the pacing took me right up to my allotted time of 1 hour. The crowd was amazing and asked some really great questions during the presentation. I had to jot a couple of them down because they were really good and I need to get some answers to them. A huge Thank you to ",[812,178078,159178],{"href":178079,"rel":178080},"https://twitter.com/toddlibby/status/1613604500203532288",[816]," for shaping this 📸 during my talk.",[5909,178083,121428],{"id":121427},[651,178085,178086],{},"There is no official speaker dinner at CodeMash but since the beginning of time, Chris Judd and his company, Manifest Solutions have held a dinner down the street at Danny Boys. This was a chance to eat some good pizza and hang out with some really bright people. It’s really hard to grab people for meaningful conversations in a crowd of 2,000+ attendees at the conference so this setting did just that. Huge shoutout to Chris Judd and Manifest for putting this on!",[651,178088,178089],{},[660,178090],{"alt":178091,"src":178092},"Pizza Dinner","/images/newsletter/2023/01/15/pizza-dinner.jpeg",[4542,178094,97315],{"id":174647},[651,178096,178097],{},"If you weren’t able to attend my talk at CodeMash I will be giving it to the Cleveland Java User Group this Tuesday, January 17th at 5:30 PM. If you want to learn what GraphQL is and why you might want to reach for it in your next Java application please consider checking this out. There will be free pizza and really great group of people to hang out with.",[651,178099,178100],{},[812,178101,178102],{"href":178102,"rel":178103},"https://www.meetup.com/cleveland-java/events/290716282/",[816],[4542,178105,177889],{"id":157573},[5909,178107,164959],{"id":69848},[651,178109,177894,178110,178115],{},[812,178111,178114],{"href":178112,"rel":178113},"https://www.notion.so/CodeMash-2023-9cd7c45af51e49599259039500526505",[816],"Getting Started with Java Development in 2023"," by Java Champion Gunnar Morling. The idea for this blog post is to provide an opinionated guide for folks getting started with Java development in 2023, helping you with your very first steps with that amazing platform.",[5909,178117,165005],{"id":39340},[651,178119,178120,178121,178126],{},"I had a chance to start reading ",[812,178122,178125],{"href":178123,"rel":178124},"https://amzn.to/3H52uJi",[816],"Greg Turnquist’s new book Learning Spring Boot 3.0"," and so far I am really enjoying it. It’s not just another dump of every single thing you need to learn in Spring which can be overwhelming. Instead, he offers a practical set of information to get you up and running with Spring. I was reading this on my iPad but I just got the physical copy and will switch over to that as soon as I can get back to it.",[651,178128,178129,178130,178135],{},"At the same time, I am currently reading ",[812,178131,178134],{"href":178132,"rel":178133},"https://amzn.to/3iBJrgn",[816],"Can’t hurt me by David Goggins",". David has led an interesting life and his story is inspiring, to say the least. What I really love about this book is his message that we are all leaving so much potential on the table. The conversations about mindset have me waking up every morning ready to run through a brick wall.",[5909,178137,165017],{"id":165016},[651,178139,178140],{},"“I don’t drink coffee to wake up. I wake up to drink coffee.” ― Starbucks at Kalahari 🤣",[651,178142,178143],{},[660,178144],{"alt":91931,"src":178145},"/images/newsletter/2023/01/15/coffee.jpeg",[4542,178147,157704],{"id":157703},[651,178149,177303,178150,166271],{},[812,178151,41499],{"href":44086,"rel":178152},[816],[651,178154,41105,178155,69920,178157,178159,178161,178163],{},[41107,178156],{},[41107,178158],{},[812,178160,161560],{"href":161111},[41107,178162],{},[812,178164,53869],{"href":53869,"rel":178165},[816],{"title":674,"searchDepth":790,"depth":790,"links":178167},[178168,178173,178174,178179],{"id":177852,"depth":790,"text":97312,"children":178169},[178170,178171,178172],{"id":178019,"depth":892,"text":178020},{"id":178054,"depth":892,"text":178055},{"id":121427,"depth":892,"text":121428},{"id":174647,"depth":790,"text":97315},{"id":157573,"depth":790,"text":177889,"children":178175},[178176,178177,178178],{"id":69848,"depth":892,"text":164959},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":178181,"date":178182},"codemash-2023","2023-01-15T07:30:00.000Z","/newsletter/2023/01/16/codemash-2023",{"title":175257,"description":177981},"newsletter/2023/01/16/codemash-2023","Susva9ny018hgA7M_b0GaW0XIpT2FpZAylj-NnqKx_c",{"id":178188,"title":178189,"body":178190,"description":178396,"extension":793,"meta":178397,"navigation":797,"path":178400,"seo":178401,"stem":178402,"__hash__":178403},"content/newsletter/2023/01/30/spring-one-essentials.md","SpringOne Essentials, Jakarta EE 10, and Upcoming Content",{"type":648,"value":178191,"toc":178385},[178192,178200,178206,178209,178216,178219,178232,178240,178247,178253,178259,178262,178266,178269,178275,178283,178286,178289,178295,178298,178304,178306,178309,178318,178321,178323,178325,178334,178337,178343,178345,178359,178361,178364,178366,178372],[651,178193,178194,178195,178199],{},"Happy Monday and welcome to another edition of the newsletter! Last week, I had the opportunity to attend the ",[812,178196,178198],{"href":82111,"rel":178197},[816],"SpringOne Essentials"," virtual conference in San Francisco. Today, I'd like to share with you some of the announcements made during the event. I also want to tell you about a new video I made last week as well as what I will be working on in the near future. With only one day left in January, we are getting ever closer to the eventual end of winter and that makes me smile.",[651,178201,178202],{},[660,178203],{"alt":178204,"src":178205},"VMware office San Francisco","/images/newsletter/2023/01/30/vmware-sign.jpeg",[4542,178207,178198],{"id":178208},"springone-essentials",[651,178210,178211,178212,178215],{},"I was able to make the trip out to San Francisco for our virtual event called ",[812,178213,178198],{"href":82111,"rel":178214},[816],". This is the event that replaced SpringOne last year which we, unfortunately, had to cancel. It was a great few days and I really enjoyed the energy I get from being back in the office around people.",[651,178217,178218],{},"DaShaun and I were there to watch the day 1 event and then give a presentation on What’s new in Spring Framework 6 and Spring Boot 3. I thought it was packed full of some really great presentations along with some awesome announcements. I really enjoyed our in-person presentation and I thought we got some really great questions from everyone in attendance.",[651,178220,178221,178222,178227,178228,178231],{},"The release of Spring Framework 6, Spring Boot 3, and all its accompanying features was the talk of the town. I really enjoyed Juergen Hoeller's comprehensive overview of what to expect with this new release and beyond. I also enjoyed the fact that everyone can get their hands on the ",[812,178223,178226],{"href":178224,"rel":178225},"https://tanzu.vmware.com/content/ebooks/the-state-of-spring-2022",[816],"State of Spring 2022"," report. This report is packed full of information on what developers are doing with Spring and how they feel about the current state of the framework. If you want to hear some more insights into this you can join us for ",[812,178229,97345],{"href":97000,"rel":178230},[816]," on Tuesday as we take a deep dive into the report.",[651,178233,178234,178235,178239],{},"We also announced the launch of ",[812,178236,120369],{"href":178237,"rel":178238},"https://tanzu.vmware.com/content/blog/introducing-spring-academy",[816],". This is a valuable new resource to help developers of all levels improve their skills with Spring. Spring Academy provides an accessible way to start learning Spring and to grow skills with Spring at every level of your career as a developer. Created and curated by the Spring team, the foremost experts on all things Spring, Spring Academy delivers on-demand training so you can invest in yourself and your understanding of the latest developments in the Spring ecosystem.",[651,178241,178242,178243,664],{},"Finally, my colleague Rita Manachi has put together a great roundup of all the exciting announcements from SpringOne Essentials. Check it out ",[812,178244,18263],{"href":178245,"rel":178246},"https://tanzu.vmware.com/content/blog/springone-essentials-2023-news",[816],[651,178248,178249],{},[660,178250],{"alt":178251,"src":178252},"SpringOne Essentials Ballons","/images/newsletter/2023/01/30/baloons.jpeg",[651,178254,178255],{},[660,178256],{"alt":178257,"src":178258},"Full Snack Developers","/images/newsletter/2023/01/30/snack-bar.jpeg",[651,178260,178261],{},"I also just want to point out that it was great to be in San Francisco with my friend DaShaun. We got to have a couple of really great dinners together, we went on a nice run down by the pier and we even caught the new Avatar movie on a 3D IMAX screen which was incredible. I guess what I am trying to say is it’s really great working with people you love to be around and I highly recommend it 🥳",[4542,178263,178265],{"id":178264},"jakarta-ee-10-in-spring-framework-6","Jakarta EE 10 in Spring Framework 6",[651,178267,178268],{},"As I was waiting to board my plane in San Francisco on Wednesday I decided to send out a tweet about Jakarta EE 10 support in Spring Boot 3 and how you can use a UUID as an auto-generated primary key. Little did I know that this last-minute tweet would garner so much attention 🤩",[651,178270,178271],{},[812,178272,178273],{"href":178273,"rel":178274},"https://twitter.com/therealdanvega/status/1618310057699213314",[816],[651,178276,178277,178278,178282],{},"With a strong interest in the subject, I moved this video to the top of my queue and worked on it towards the end of the week. I also wrote an accompanying ",[812,178279,24879],{"href":178280,"rel":178281},"https://www.danvega.dev/blog/2023/01/27/jakarta-ee-10-uuid/",[816],", if you'd like to read it.",[651,178284,178285],{},"To stay true to my goal of creating better content, I followed my own advice and focused on improving one aspect of the video. I wanted to learn when and where to use adjustment layers. Adjustment layers are an excellent way to apply effects to only a part of a clip, without affecting the entire clip. You could split the clip and add effects directly, but the effects would be limited to that clip. The great thing about adjustment layers is that they can be reused in the future.",[651,178287,178288],{},"I added an adjustment layer to this video to blur it and bring some text to the foreground, to draw the viewer's attention. It's not the most thrilling thing, but I'm gaining new skills to help me tell stories more effectively.",[651,178290,178291],{},[660,178292],{"alt":178293,"src":178294},"Premiere Pro Timeline","/images/newsletter/2023/01/30/premiere-pro.png",[651,178296,178297],{},"In this tutorial, I demonstrated how to use the new UUID generator type in a Spring Data JPA application. It's exciting to start taking advantage of the new features available in Jakarta.",[651,178299,178300],{},[812,178301,178302],{"href":178302,"rel":178303},"https://www.youtube.com/watch?v=ZWwqcH__kr4",[816],[4542,178305,176059],{"id":176058},[651,178307,178308],{},"My schedule is actually pretty clear all things considered for the next couple of months. The next conference I have on the schedule is Devnexus which takes place April 4-6 in Atlanta. I’m looking forward to presenting on Spring Recipes with my friend Nate Schutta.",[651,178310,178311,178312,178317],{},"I’m also going to be sitting down to record an episode of the ",[812,178313,178316],{"href":178314,"rel":178315},"https://www.javapubhouse.com/",[816],"Java Pub House"," this week. We are going to talk about all things Spring Framework 6 and Spring Boot 3 and I am really looking forward to that. I am not sure when that will come out but I will be sure to let you know.",[651,178319,178320],{},"Other than that, I will be working on YouTube videos and have a large queue of videos to create. I'm also considering becoming a course creator again. I have three courses in mind and will likely start talking about them soon. The big question is where to publish them. I don't have an answer yet, but I'm leaning toward Udemy since I already have an audience there. I was waiting for YouTube to launch its course platform in the US, but that hasn't happened yet.",[4542,178322,177889],{"id":157573},[5909,178324,165005],{"id":39340},[651,178326,178327,178328,178333],{},"I started reading ",[812,178329,178332],{"href":178330,"rel":178331},"https://amzn.to/3WP7whD",[816],"SuperIntelligence"," by Nick Bostrom on my way out to San Francisco and I’m almost done with it. This book is an exploration of the potential consequences of creating machines that are more intelligent than humans. This was an eye-opening book that examined the likely outcomes of a world where machines can think faster and more accurately than humans and the ethical implications of such a world.",[651,178335,178336],{},"While we are on the subject of books I saw this cheerful, color-coordinated bookshelf at the VMware office in San Francisco. Few things bring me more joy than a well-organized book collection 🤩",[651,178338,178339],{},[660,178340],{"alt":178341,"src":178342},"VMware Office Bookshelf","/images/newsletter/2023/01/30/bookshelf.jpeg",[5909,178344,164983],{"id":39439},[651,178346,178347,178348,178353,178354,178358],{},"I really enjoyed ",[812,178349,178352],{"href":178350,"rel":178351},"https://aws.amazon.com/podcasts/567-introducing-aws-lambda-snapstart/",[816],"this AWS podcast"," with Mark Sailes on AWS Lambda SnapStart. I did a ",[812,178355,67564],{"href":178356,"rel":178357},"https://youtu.be/isS6m6aj_Ak",[816]," on AWS Lambda SnapStart for Spring Developers and I am really impressed with the technology. It was really great to hear a detailed explanation of the product and some of the edge cases you need to look out for.",[5909,178360,165017],{"id":165016},[651,178362,178363],{},"“This is quite possibly the most important and most daunting challenge humanity has ever faced. And—whether we succeed or fail—it is probably the last challenge we will ever face.” - Nick Bostrom",[4542,178365,157704],{"id":157703},[651,178367,178368,178369,166271],{},"I hope you enjoyed this newsletter installment, and I will talk to you in the next one. If you have any links you would like me to include please ",[812,178370,41499],{"href":44086,"rel":178371},[816],[651,178373,41105,178374,69920,178376,178378,178380,178382],{},[41107,178375],{},[41107,178377],{},[812,178379,161560],{"href":161111},[41107,178381],{},[812,178383,53869],{"href":53869,"rel":178384},[816],{"title":674,"searchDepth":790,"depth":790,"links":178386},[178387,178388,178389,178390,178395],{"id":178208,"depth":790,"text":178198},{"id":178264,"depth":790,"text":178265},{"id":176058,"depth":790,"text":176059},{"id":157573,"depth":790,"text":177889,"children":178391},[178392,178393,178394],{"id":39340,"depth":892,"text":165005},{"id":39439,"depth":892,"text":164983},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},"Happy Monday and welcome to another edition of the newsletter! Last week, I had the opportunity to attend the SpringOne Essentials virtual conference in San Francisco. Today, I'd like to share with you some of the announcements made during the event. I also want to tell you about a new video I made last week as well as what I will be working on in the near future. With only one day left in January, we are getting ever closer to the eventual end of winter and that makes me smile.",{"slug":178398,"date":178399},"spring-one-essentials","2023-01-30T08:30:00.000Z","/newsletter/2023/01/30/spring-one-essentials",{"title":178189,"description":178396},"newsletter/2023/01/30/spring-one-essentials","e-zxXtvhLIzg6cgreKKig2z4fGu_HFD9u5S6AIYg_Pg",{"id":178405,"title":178406,"body":178407,"description":178411,"extension":793,"meta":178613,"navigation":797,"path":178616,"seo":178617,"stem":178618,"__hash__":178619},"content/newsletter/2023/02/13/super-monday-2023.md","Super Monday, Chat GPT, Spring Office Hours and I’m kind of a big deal.",{"type":648,"value":178408,"toc":178602},[178409,178412,178415,178418,178421,178424,178430,178433,178438,178442,178445,178452,178455,178458,178460,178468,178471,178474,178477,178500,178502,178509,178534,178539,178548,178550,178552,178561,178563,178571,178574,178580,178582,178587],[651,178410,178411],{},"Happy Monday! Welcome to another week of me visiting your inbox for a few minutes. Last night was a thrilling Super Bowl, with Kansas City coming out on top 🎉 I'm not a huge fan of the referees' decisions, but I won't blame the players for that. My biggest takeaway is the same one I have every year: when are we going to move the Super Bowl to Saturday night?",[651,178413,178414],{},"It's been two weeks since we last spoke, which is a reminder that we should make this a weekly event. In this edition, I want to discuss YouTube Shorts and Chat GPT, and I’ll share with you a major milestone that I crossed yesterday.",[651,178416,178417],{},"I was disappointed to learn that I wasn't accepted to Devoxx UK or Devoxx France. Even though I know rejection is part of the process, it never gets any easier. I was really hoping to get an invitation to one of them, so I could make the trip across the pond. However, I'll try again next year. I still have CFPs out to many conferences, so let's cross our fingers 🤞🏻 and wish the conference committees luck in making their tough decisions.",[651,178419,178420],{},"In better news, we're less than a month away from March 12th - one of my favorite days of the year. It's Daylight Saving Time when we move our clocks forward an hour. January and February can be long, cold, depressing months here, so this is a welcome change. It means the days will be longer and we'll soon be through Winter. Plus, I can start running outside again as the weather warms up - I'm not a fan of running at 30°F!",[651,178422,178423],{},"I hit a big milestone yesterday as I crossed 10,000 followers on the bird app. Thank you to everyone who follows me there and I hope I am providing value to your timeline.",[651,178425,178426],{},[812,178427,178428],{"href":178428,"rel":178429},"https://twitter.com/therealdanvega/status/1624840872977113094",[816],[651,178431,178432],{},"I was really just excited about that milestone so I could share this 🤣",[651,178434,178435],{},[660,178436],{"alt":178437,"src":178437},"https://media.giphy.com/media/9LFBOD8a1Ip2M/giphy.gif",[4542,178439,178441],{"id":178440},"chat-gpt","Chat GPT",[651,178443,178444],{},"I worked on 2 new videos around Chat GPT. The first one was one that I was really excited to put together and I hope you enjoy watching as much fun as I had to put it together. In this tutorial, I built a CLI in Java called WTF GPT.",[651,178446,178447,178448,178451],{},"This application connects to the Open AI API (the same people behind Chat GPT) and responds to questions. I imagined it as a command line tool to help me remember commands I couldn't recall. Thanks to GraalVM, I was able to create a native image. Now, from the command line, I can type ",[676,178449,178450],{},"wtfgpt What is the command to list all docker images"," and get the correct response.",[5988,178453],{"id":178454},"6RXtDxapKX8",[651,178456,178457],{},"I recently worked on a video to demonstrate how Chat GPT can write a Spring Boot application. It was recorded and is awaiting editing and publication. I'm pleased with the outcome and believe it showcases some of the tasks Chat GPT excels at. Ultimately, I'm a big fan of Chat GPT and think it could be a great addition to any developer's toolkit.",[4542,178459,97345],{"id":97344},[651,178461,178462,178463,664],{},"In the latest edition of Spring Office Hours, we had a mailbag show where we planned to answer as many questions as possible. However, due to the high number of live questions, we didn't get to any of the mailbag questions. This is great news, as it shows that you enjoy these types of shows. Therefore, we will try to do them more often. In fact, the one we were supposed to do last week has been moved to ",[812,178464,178467],{"href":178465,"rel":178466},"http://www.springofficehours.io",[816],"this week",[5988,178469],{"id":178470},"iX8bMlR8Nsw",[651,178472,178473],{},"I created a few YouTube Shorts from this week's Spring Office Hours episode. I believe that taking a 1-minute clip of me answering a question is an excellent way to produce short-form content and provides me with an opportunity to repurpose content for YouTube Shorts.",[651,178475,178476],{},"Here are the 3 shorts I made from last week's episode:",[5316,178478,178479,178486,178493],{},[5332,178480,178481],{},[812,178482,178485],{"href":178483,"rel":178484},"https://youtube.com/shorts/JZu94r1CTYI?feature=share",[816],"Will virtual threads render WebFlux Obsolete?",[5332,178487,178488],{},[812,178489,178492],{"href":178490,"rel":178491},"https://youtube.com/shorts/vas12Uao1UQ?feature=share",[816],"Spring RestTemplate vs WebClient vs Java HttpClient",[5332,178494,178495],{},[812,178496,178499],{"href":178497,"rel":178498},"https://youtube.com/shorts/RW6DsRQV_4M?feature=share",[816],"What does it take to become a Developer Advocate?",[4542,178501,82720],{"id":47833},[651,178503,178504,178505,178508],{},"If you aren’t following me on Twitter or haven’t subscribed to my ",[812,178506,101297],{"href":101295,"rel":178507},[816]," here is some of my latest content that you missed:",[5316,178510,178511,178518,178526],{},[5332,178512,178513,178517],{},[812,178514,178516],{"href":111570,"rel":178515},[816],"GraphQL Custom Scalars"," - Learn what the default scalars in GraphQL are defined by the specification and how you can use custom scalars in Spring for GraphQL.",[5332,178519,178520,178525],{},[812,178521,178524],{"href":178522,"rel":178523},"https://youtu.be/g78is10FjF0",[816],"My IntelliJ Setup"," - Everyone always asks me what theme I’m using in IntelliJ so I decided to go through and document my look and feel and some of my favorite features of IntelliJ.",[5332,178527,178528,178533],{},[812,178529,178532],{"href":178530,"rel":178531},"https://youtu.be/Rk4zfvVvRks",[816],"Native Images in Java using GraalVM"," - I took this opportunity to look at how GraalVM works when dealing with dynamic features of the JVM.",[651,178535,178536],{},[2939,178537,178538],{},"Shorts",[5316,178540,178541],{},[5332,178542,178543],{},[812,178544,178547],{"href":178545,"rel":178546},"https://youtube.com/shorts/m3MkJQ_Hn0k?feature=share",[816],"Will Artificial Intelligence (AI) replace Software Developers",[4542,178549,177889],{"id":157573},[5909,178551,164959],{"id":69848},[651,178553,178554,178555,178560],{},"I was aware that YouTube Shorts was gaining traction, but I was surprised to learn that it had reached ",[812,178556,178559],{"href":178557,"rel":178558},"https://techcrunch.com/2023/02/03/google-says-youtube-shorts-has-crossed-50-billion-daily-views/",[816],"50 billion daily views",". If you're interested in creating short-form content, now is a great time to get involved, as this impressive figure is only set to increase.",[5909,178562,164971],{"id":157591},[651,178564,178565,178566,178570],{},"My friend and colleague Josh Long has been producing a lot of live streams on his ",[812,178567,178569],{"href":101267,"rel":178568},[816],"new channel"," recently. I particularly enjoyed his videos on integrating with the YouTube API, as I recently went through this process myself.",[5909,178572,178573],{"id":95961},"🐦 Tweet",[651,178575,178576],{},[812,178577,178578],{"href":178578,"rel":178579},"https://twitter.com/therealdanvega/status/1623459242920255489",[816],[4542,178581,157704],{"id":157703},[651,178583,178368,178584,166271],{},[812,178585,41499],{"href":44086,"rel":178586},[816],[651,178588,41105,178589,69920,178591,178593,178595,178597,178600],{},[41107,178590],{},[41107,178592],{},[812,178594,161560],{"href":161111},[41107,178596],{},[812,178598,53869],{"href":53869,"rel":178599},[816],[41107,178601],{},{"title":674,"searchDepth":790,"depth":790,"links":178603},[178604,178605,178606,178607,178612],{"id":178440,"depth":790,"text":178441},{"id":97344,"depth":790,"text":97345},{"id":47833,"depth":790,"text":82720},{"id":157573,"depth":790,"text":177889,"children":178608},[178609,178610,178611],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":178614,"date":178615},"super-monday-2023","2023-02-13T08:30:00.000Z","/newsletter/2023/02/13/super-monday-2023",{"title":178406,"description":178411},"newsletter/2023/02/13/super-monday-2023","tNkanDixJQyR9xKQULd4eTmZZK-LHYGQG8dEVpCJL_U",{"id":178621,"title":178622,"body":178623,"description":178627,"extension":793,"meta":178809,"navigation":797,"path":178811,"seo":178812,"stem":178813,"__hash__":178814},"content/newsletter/2023/02/20/spring-boot-crash-course.md","Spring Boot Crash Course, Chat GPT and YouTube news",{"type":648,"value":178624,"toc":178797},[178625,178628,178630,178633,178636,178642,178644,178647,178650,178653,178659,178663,178666,178669,178672,178683,178686,178688,178691,178694,178697,178706,178709,178712,178720,178725,178731,178736,178742,178744,178746,178755,178757,178766,178768,178771,178777,178779,178784],[651,178626,178627],{},"Happy Monday and welcome to another edition of my newsletter. In today’s episode, I want to tell you all about the Spring Boot Crash Course I published last week. I also want to answer the question “Can Chat GPT write a Spring Boot application”? Finally, I will wrap up today’s newsletter with some YouTube news.",[4542,178629,23034],{"id":101064},[651,178631,178632],{},"This is a video that I have wanted to put together for a long time now. This introduction to Spring will teach you how to build a REST API using Spring Boot 3 and deploy it to production. This video ended up coming in under 4 hours and I am really happy with how it turned out.",[651,178634,178635],{},"I think I have talked about almost everything in this video in other videos but I wanted a single video where someone new to Spring could get up and running quickly. I start out by defining the prerequisites for this crash course, defining what The Spring Framework is and where Spring Boot fits in. Finally, we discuss what you will build in this course and what you will learn.",[651,178637,178638],{},[660,178639],{"alt":178640,"src":178641},"Excalidraw","/images/newsletter/2023/02/20/excalidraw.png",[5988,178643],{"id":101112},[651,178645,178646],{},"While this was a lot of fun to put together and I think a lot of people are going to get some value out of it I’m glad I am done with that. Creating a 4-minute video for YouTube is a lot of work so you can only imagine how much work goes into creating a 4-hour video. You have to plan for hours of waiting for tasks like exporting, uploading, and processing time.",[651,178648,178649],{},"The video had something like 3000 views in the first 24 hours which is just amazing but I have a feeling it’s going to blow up over the next month or so 🤞🏻 If you found value in this please feel free to drop me some feedback.",[651,178651,178652],{},"With that done I will start recording a complete course that will include testing & security and go more in-depth into a few topics. This will be a paid course but should be pretty affordable for most. If this is something you’re interested in please let me know.",[651,178654,178655],{},[660,178656],{"alt":178657,"src":178658},"Spring Boot Course Outline","/images/newsletter/2023/02/20/spring-boot-course-outline.png",[4542,178660,178662],{"id":178661},"chat-gpt-spring-boot","Chat GPT & Spring Boot",[651,178664,178665],{},"I released another video last week where I set out to answer the question “Can Chat GPT create a Spring Boot Application?”. This was a lot of fun to work on and if you have been following this newsletter you know I am a big fan of AI and Chat GPT.",[5988,178667],{"id":178668},"CXbXoMIVpRg",[651,178670,178671],{},"I started off this experiment by asking Chat GPT for a list of high-level steps needed to write a REST API in Spring Boot. For the most part, it did a really great job of giving us an outline of what we needed to do to accomplish that task. As I stated in the video I think Chat GPT is really good at 3 things when it comes to code:",[27665,178673,178674,178677,178680],{},[5332,178675,178676],{},"Providing an outline on how to solve a particular problem.",[5332,178678,178679],{},"Writing specific code when given the proper details.",[5332,178681,178682],{},"Explaining a certain block of code.",[651,178684,178685],{},"In the end, you still need to understand Java and Spring but I thought it did an excellent job of helping us write our application. There were many times when my knowledge helped me correct some things and I think that was a big takeaway for me. It’s not going to replace developers but it is a valuable tool that you should have in your toolbox.",[4542,178687,15432],{"id":61224},[651,178689,178690],{},"Towards the end of last year, I crossed a pretty big milestone on YouTube when I hit 20,000 subscribers. Last week I crossed 25,000 and I want to thank all of you for your support. In particular, the Java and Spring community has shown me so much love over the last year and I’m very thankful 🙏",[651,178692,178693],{},"Speaking of YouTube I hired 2 editors from Upwork a couple of weeks ago and I wanted to let you know how that little experiment was going. This is coming out of my pocket so I don’t have much of a budget for this. I ended up using Chat GPT to craft a job description and I soon had a number of great candidates.",[651,178695,178696],{},"The first editor was really responsive and while he did a good job I just felt like it wasn’t providing a ton of value. These were edits that I could have made and they honestly took him about 2x as long as they would have taken me.",[651,178698,178699,178700,178705],{},"The 2nd editor provided me with a 1st cut of the video on a service called ",[812,178701,178704],{"href":178702,"rel":178703},"http://frame.io",[816],"frame.io"," where I was able to watch it and leave comments with timestamps. I never heard of this service and I thought it was a wonderful way to provide feedback during the editing process. The only problem was he absolutely nailed the video and I only had 1 small comment. He is the one that did the Chat GPT & Spring Boot video I talked about above.",[651,178707,178708],{},"I have a ton of work I would like to give this person but again it comes back to budgeting. This is why I want to put together some paid courses so that I can feed that money back into the video creation process. Also if you or your business is interested in sponsoring an upcoming video please feel free to reach out. I’m 25,000 subscribers in and have yet to take any brand deals on my channel.",[4542,178710,126904],{"id":178711},"ai",[651,178713,178714,178715,178719],{},"I’m a big fan of ",[812,178716,97495],{"href":178717,"rel":178718},"https://midjourney.com/",[816]," and recently signed up for a monthly membership that gets me access to 200 images a month and faster response times. I’m still learning how to craft good prompts to get the desired outcomes but so far it's really impressive. Here are 2 images I was experimenting with this week.",[651,178721,166923,178722],{},[2939,178723,178724],{},"person reading a book RGB --ar 16:9 --stylize 100 --s 5000",[651,178726,178727],{},[660,178728],{"alt":178729,"src":178730},"Girl Reading a Book","/images/newsletter/2023/02/20/girl-reading-book.png",[651,178732,7424,178733],{},[2939,178734,178735],{},"as Vegas if it were taken over by zombies",[651,178737,178738],{},[660,178739],{"alt":178740,"src":178741},"Zombies in Vegas","/images/newsletter/2023/02/20/zombies-in-vegas.png",[4542,178743,177889],{"id":157573},[5909,178745,164983],{"id":39439},[651,178747,178748,178749,178754],{},"I was recently on the ",[812,178750,178753],{"href":178751,"rel":178752},"https://www.notion.so/momoir-com-a10c4d2dc1494cd0a68be2121cdc4b93",[816],"Java PubHouse"," podcast with Freddy and Bob and we talked all about Spring Boot 3. This was a really fun conversation and I’m really looking forward to meeting both of them at Devnexus in April.",[5909,178756,164971],{"id":157591},[651,178758,178759,178760,178765],{},"I really enjoyed this video ",[812,178761,178764],{"href":178762,"rel":178763},"https://www.youtube.com/watch?v=LeoCh7VK9cg",[816],"Demystifying Spring Internals"," by Madhura Bhave who is an Engineer on the Spring team. She did an excellent job revealing some of the things that occur in your Spring application that you may not be aware of.",[5909,178767,178573],{"id":95961},[651,178769,178770],{},"I had some free time (a whole hour) on Saturday night so I did what any self-respecting programmer would do. I decided to dive in and learn a completely new language and wrote my first Rust application.",[651,178772,178773],{},[812,178774,178775],{"href":178775,"rel":178776},"https://twitter.com/therealdanvega/status/1627122138850246656",[816],[4542,178778,157704],{"id":157703},[651,178780,178368,178781,166271],{},[812,178782,41499],{"href":44086,"rel":178783},[816],[651,178785,41105,178786,69920,178788,178790,178792,178794],{},[41107,178787],{},[41107,178789],{},[812,178791,161560],{"href":161111},[41107,178793],{},[812,178795,53869],{"href":53869,"rel":178796},[816],{"title":674,"searchDepth":790,"depth":790,"links":178798},[178799,178800,178801,178802,178803,178808],{"id":101064,"depth":790,"text":23034},{"id":178661,"depth":790,"text":178662},{"id":61224,"depth":790,"text":15432},{"id":178711,"depth":790,"text":126904},{"id":157573,"depth":790,"text":177889,"children":178804},[178805,178806,178807],{"id":39439,"depth":892,"text":164983},{"id":157591,"depth":892,"text":164971},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":101064,"date":178810},"2023-02-20T07:30:00.000Z","/newsletter/2023/02/20/spring-boot-crash-course",{"title":178622,"description":178627},"newsletter/2023/02/20/spring-boot-crash-course","hQyTq0LppFqDD_7lJGtq-SzqAU6PHCSHF2CD81ca9IY",{"id":178816,"title":178817,"body":178818,"description":178822,"extension":793,"meta":179004,"navigation":797,"path":179007,"seo":179008,"stem":179009,"__hash__":179010},"content/newsletter/2023/03/06/spring-shell.md","Live Streaming, Spring Shell & Nuxt 3",{"type":648,"value":178819,"toc":178992},[178820,178823,178825,178828,178835,178840,178843,178880,178887,178891,178894,178897,178900,178902,178905,178908,178911,178914,178920,178923,178925,178927,178935,178937,178951,178953,178961,178963,178966,178972,178974,178979],[651,178821,178822],{},"Welcome to another edition of the newsletter! First, I want to congratulate anyone who lives in a cold-weather area for making it through the worst part of winter. March is officially here! Today I want to talk to you about Live Streaming, Spring Shell, and Nuxt 3.",[4542,178824,169217],{"id":169216},[651,178826,178827],{},"If you’re a regular around here you know that I host a show every week with my friend DaShaun called Spring Office Hours. That show is so much fun and I look forward to it every single week. There is some level of planning that goes into that show and we don’t have the chance to do a ton of live coding on that show.",[651,178829,178830,178831,178834],{},"Recently, I made the decision to incorporate live coding into my regular routine. The idea had been on my mind for the last couple of weeks and I was excited to try it out. For my streaming software, I'm using ",[812,178832,61184],{"href":43604,"rel":178833},[816],", which is different from the software I use on Spring Office Hours. I knew it would take a few episodes to get used to, but I finally feel like I have a handle on it.",[651,178836,178837],{},[660,178838],{"alt":169217,"src":178839},"/images/newsletter/2023/03/06/live-streaming-screenshot.png",[651,178841,178842],{},"It can get lonely working at home in my dungeon (I work out of the basement) so it’s been a lot of fun connecting with you. I want to thank everyone for joining me during my live streams including Simon, Thomas, and Pad 5 who have been amazing supporters. I was able to go live 5 times over the last 2 weeks and if you missed them you can check out the replays below.",[5316,178844,178845,178852,178859,178866,178873],{},[5332,178846,178847],{},[812,178848,178851],{"href":178849,"rel":178850},"https://youtube.com/live/uQDa5eNUSyo?feature=share",[816],"Spring Office Hours - Video Editing",[5332,178853,178854],{},[812,178855,178858],{"href":178856,"rel":178857},"https://youtube.com/live/FDRfg77MJsk?feature=share",[816],"Introduction to Spring Shell (Part 1)",[5332,178860,178861],{},[812,178862,178865],{"href":178863,"rel":178864},"https://youtube.com/live/1xuTqgilNzM?feature=share",[816],"Introduction to Spring Shell (Part 2)",[5332,178867,178868],{},[812,178869,178872],{"href":178870,"rel":178871},"https://youtube.com/live/E84E7c7IlAQ?feature=share",[816],"Introduction to Spring Shell (Part 3)",[5332,178874,178875],{},[812,178876,178879],{"href":178877,"rel":178878},"https://youtube.com/live/e8wEcemxcfI?feature=share",[816],"Introduction to Spring Shell (Part 4)",[651,178881,178882,178883,664],{},"My plan is to use these live streams as an opportunity to share some insight into what I am working on next. I have plans for a few live streams this week, but I need to find time in my schedule for them. If you want to be notified when I go live, please make sure you are subscribed to my ",[812,178884,178886],{"href":101295,"rel":178885},[816],"YouTube Channel",[4542,178888,178890],{"id":178889},"spring-shell-introduction","Spring Shell Introduction",[651,178892,178893],{},"During those live streams, I worked on the demo that I would ultimately use in my Introduction to Spring Shell. Spring Shell is a great project in the Spring ecosystem that enables you to build command-line applications using the same language, framework, and tools that you use every day.",[651,178895,178896],{},"This tutorial will teach you the basics of Spring Shell by guiding you through the creation of a simple dad joke CLI. You can run this CLI right from your command line whenever you need a good laugh.",[5988,178898],{"id":178899},"8B0IjOIzicU",[4542,178901,169520],{"id":169519},[651,178903,178904],{},"I had a great time playing around with Nuxt 3 over the weekend and I'm excited to explore it further. Currently, I have two projects that I'm working on with Nuxt 3. One of them involves moving my website over to Nuxt 3, which I've been planning to do for a while now.",[651,178906,178907],{},"The other is a blog that is driven by a content management system. This isn’t just any CMS though, it’s using Notion. I entertained the idea of moving the content in Notion over to a headless CMS but that just seemed like a bunch of work with no gain.",[651,178909,178910],{},"The author of this blog enjoys writing in Notion so I figured I would just keep it all there. This doesn’t come without its challenges though. A notion page is not simply a dump of content. It’s a highly customizable structure of data so reading data from the API and reconstructing a page back on the nuxt side was a challenge.",[651,178912,178913],{},"Here is a screenshot of a new blog I started in Notion on the left and the Nuxt 3 application displaying those posts on the right. Notice that only the blog posts in the Done column are being displayed. I have a lot more to share on this front including pulling some similar functionality into Spring Boot so stay tuned!",[651,178915,178916],{},[660,178917],{"alt":178918,"src":178919},"Notion + Nuxt Blog","/images/newsletter/2023/03/06/notion_nuxt_blog.png",[651,178921,178922],{},"What I am still working out is how this will perform in production. There will probably be 1-2 new posts a week so this could almost be a statically generated site. I’m trying to understand all of the different build modes Nuxt 3 comes with to make the right choice here. I also need to figure out if I can use something like Nuxt Image with images that are hosted on Notion (AWS S3 Buckets).",[4542,178924,177889],{"id":157573},[5909,178926,164959],{"id":69848},[651,178928,178929,178930,178934],{},"I enjoyed reading this article by Mark Thomas on ",[812,178931,178933],{"href":104969,"rel":178932},[816],"Web Applications and Project Loom",". The article explains what Project Loom is and how it could benefit Spring developers. By switching to a virtual thread-based executor, web applications that currently use classic Spring MVC should see some scalability improvements.",[5909,178936,164983],{"id":39439},[651,178938,178939,178940,23212,178945,178950],{},"I enjoyed listening to two podcasts from a recent episode of the Bootiful Podcast. Josh has started live-streaming the recording of the podcast, which I think is a wonderful addition. In the latest episode, Josh interviewed ",[812,178941,178944],{"href":178942,"rel":178943},"https://spring.io/blog/2023/02/23/a-bootiful-podcast-its-glenn-renfro-listen-to-this-episode-if-you-want-to",[816],"Glenn Renfro",[812,178946,178949],{"href":178947,"rel":178948},"https://spring.io/blog/2023/03/02/a-bootiful-podcast-spring-cloud-stream-and-spring-cloud-function-lead-oleg",[816],"Oleg Zhurakousky",", two amazing members of the Spring Team.",[5909,178952,164971],{"id":157591},[651,178954,177905,178955,178960],{},[812,178956,178959],{"href":178957,"rel":178958},"https://www.youtube.com/watch?v=uy6iN0d6J8E",[816],"How to Best Use Java Records as DTOs in Spring Boot 3"," from Yugabyte. I don’t think I ever knew that you could use a Query and cast the result to a class so I learned something new this weekend!",[5909,178962,178573],{"id":95961},[651,178964,178965],{},"I was excited to see a tweet from Mark Sailes about the AWS Lambda team launching a preview of the Java 17 base container image. Although it's not yet production-ready, this development brings us one step closer to using Java 17 on AWS Lambda, and that makes me very happy!",[651,178967,178968],{},[812,178969,178970],{"href":178970,"rel":178971},"https://twitter.com/MarkSailes3/status/1630654777276215296",[816],[4542,178973,157704],{"id":157703},[651,178975,178368,178976,166271],{},[812,178977,41499],{"href":44086,"rel":178978},[816],[651,178980,41105,178981,69920,178983,178985,178987,178989],{},[41107,178982],{},[41107,178984],{},[812,178986,161560],{"href":161111},[41107,178988],{},[812,178990,53869],{"href":53869,"rel":178991},[816],{"title":674,"searchDepth":790,"depth":790,"links":178993},[178994,178995,178996,178997,179003],{"id":169216,"depth":790,"text":169217},{"id":178889,"depth":790,"text":178890},{"id":169519,"depth":790,"text":169520},{"id":157573,"depth":790,"text":177889,"children":178998},[178999,179000,179001,179002],{"id":69848,"depth":892,"text":164959},{"id":39439,"depth":892,"text":164983},{"id":157591,"depth":892,"text":164971},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":179005,"date":179006},"spring-shell","2023-03-06T07:30:00.000Z","/newsletter/2023/03/06/spring-shell",{"title":178817,"description":178822},"newsletter/2023/03/06/spring-shell","QHL0Du_VNKLQOiIQbId4Jh4VfTXK9zx9jxOzZA1rEI8",{"id":179012,"title":179013,"body":179014,"description":179018,"extension":793,"meta":179245,"navigation":797,"path":179248,"seo":179249,"stem":179250,"__hash__":179251},"content/newsletter/2023/03/20/chat-gpt-4.md","Chat GPT-4, Spring News, and Nuxt + Notion",{"type":648,"value":179015,"toc":179229},[179016,179019,179022,179030,179036,179039,179043,179046,179049,179055,179062,179066,179091,179094,179098,179116,179120,179123,179126,179135,179138,179142,179151,179153,179155,179162,179170,179173,179180,179189,179191,179196,179198,179201,179207,179209,179216],[651,179017,179018],{},"Welcome to another edition of \"Dan tells you what he's been up to lately\"! This week's big news was the release of GPT-4, and I was excited to explore the new features of this tool, which plays a significant role in my daily workflow.",[651,179020,179021],{},"GPT-4 is a state-of-the-art language model developed by OpenAI. It is an advanced version of its predecessor, GPT-3, designed to improve performance in natural language understanding and generation tasks. Key features of GPT-4 include its large scale, better training data, and improvements in model architecture. These enhancements contribute to its superior capabilities for generating human-like text, answering questions, and completing a wide range of tasks.",[651,179023,179024,179025,179029],{},"In case you were wondering I asked Chat GPT-4 to summarize ",[812,179026,109096],{"href":179027,"rel":179028},"https://openai.com/research/gpt-4",[816]," and it gave me the previous paragraph. If you’re a ChatGPT Plus user you will be able to select GPT-4 and it’s currently capped at 50 messages every 4 hours.",[651,179031,179032],{},[660,179033],{"alt":179034,"src":179035},"chat-gpt-4.png","/images/newsletter/2023/03/20/chat-gpt-4.png",[651,179037,179038],{},"In my opinion, the biggest highlights of this model are that it now supports multiple inputs and can process images, which opens up a whole new set of possibilities. Additionally, it can handle larger datasets as input, and although it runs slightly slower than GPT-3, I have found the results to be more accurate. The model training data is also supposed to be better, but a downside is that it still only goes up until September 2021.",[4542,179040,179042],{"id":179041},"what-im-working-on","What I’m working on…",[5909,179044,179045],{"id":102858},"Spring Security Lambda DSL",[651,179047,179048],{},"This week I worked on YouTube Tutorial that explained what the Spring Security Lambda DSL was. I would often post Spring Security configuration that looked like the screenshot below and I would get questions about the syntax of that configuration.",[651,179050,179051],{},[660,179052],{"alt":179053,"src":179054},"lambda-dsl-example.png","/images/newsletter/2023/03/20/lambda-dsl-example.png",[651,179056,179057,179058,664],{},"If you’re confused about what this syntax is or why it was introduced please check out ",[812,179059,85142],{"href":179060,"rel":179061},"https://youtu.be/PWnEZh_t0WI",[816],[5909,179063,179065],{"id":179064},"spring-boot-304","Spring Boot 3.0.4",[651,179067,52282,179068,179072,179073,179076,179077,179081,179082,179084,179085,179087,179088,179090],{},[812,179069,97119],{"href":179070,"rel":179071},"https://youtube.com/live/eRMbHR5LnOc?feature=share",[816]," of ",[812,179074,97345],{"href":178465,"rel":179075},[816]," DaShaun and I took a look at the recently released ",[812,179078,179065],{"href":179079,"rel":179080},"https://spring.io/blog/2023/03/03/spring-boot-3-0-4-available-now",[816],". The reason we wanted to highlight this release is because of a regression that was found. This issue caused problems with Spring applications located in directories with spaces. In these cases, the creation of all ",[676,179083,6139],{}," classes and ",[676,179086,12329],{},"s, along with other ",[676,179089,6151],{},"based beans, may be skipped.",[5988,179092],{"id":179093},"eRMbHR5LnOc",[5909,179095,179097],{"id":179096},"spring-beans-component-vs-bean","Spring Beans - @Component vs @Bean",[651,179099,179100,179101,179105,179106,23212,179108,179110,179111,179115],{},"I am revisiting my blog and updating posts that I think could use some improvement. Recently, I published a ",[812,179102,179104],{"href":23032,"rel":179103},[816],"Spring Crash Course"," where I briefly discussed the concept of Spring Beans and the use of ",[676,179107,12292],{},[676,179109,16857],{},". However, I realized that this topic deserves a more detailed tutorial, so I decided to update my ",[812,179112,179114],{"href":96497,"rel":179113},[816],"original blog post"," dedicated to it.",[4542,179117,179119],{"id":179118},"nuxt-3-notion","Nuxt 3 + Notion",[651,179121,179122],{},"If you follow me on Twitter you know that I have been working on a new lifestyle blog for my wife. She is starting a blog called This is my Momoir to share everything she has learned on this beautiful, exhausting, magical journey known as motherhood.",[651,179124,179125],{},"I decided to go with Nuxt 3 on the front end which was the easy decision but I struggled with what I wanted to do for content. After my wife told me she had written 50 blog posts already I knew that I needed to set up some sort of CMS for her so that I didn’t need to convert these to markdown or HTML.",[651,179127,179128,179129,179134],{},"As a big fan of Notion, I decided to use it and its API to pull in content. While its high level of customizability has made ",[812,179130,179133],{"href":179131,"rel":179132},"https://www.danvega.dev/blog/2023/03/12/notion-api-file-expired/",[816],"this project challenging",", it has also been enjoyable to work with. I would like to express my gratitude to the entire Nuxt team and community for creating such a wonderful framework. They have truly provided an answer to every \"How do I do that?\" question.",[651,179136,179137],{},"We plan to launch it in the next couple of weeks and I look forward to sharing that experience with you. If you have any questions about Nuxt 3, Vue 3, Notion API, or Tailwind CSS, please feel free to ask and I will try to address them.",[4542,179139,179141],{"id":179140},"springone-vmware-explore","SpringOne @VMware Explore",[651,179143,179144,179145,179150],{},"This year, SpringOne is teaming up with VMware Explore in Las Vegas from August 21st-24th. The Spring community is essential, and without your voice, there is no Spring. If you are interested in speaking, please ",[812,179146,179149],{"href":179147,"rel":179148},"https://event.vmware.com/flow/vmware/explore2023lv/cfp/cfpHome",[816],"submit a talk"," by March 31st. We would love to hear from you. See you in Vegas!",[4542,179152,177889],{"id":157573},[5909,179154,164959],{"id":69848},[651,179156,178347,179157,179161],{},[812,179158,109096],{"href":179159,"rel":179160},"https://rieckpil.de/spring-boot-testing-mockmvc-vs-webtestclient-vs-testresttemplate/",[816]," by Philip Riecks on Spring Boot Testing. In this article, he breaks down when you should use Mock MVC, Web Test Client, or Test Rest Template.",[651,179163,179164,179165,664],{},"If you haven’t had a check out Testcontainers yet you’re missing out. I enjoyed reading this guide on ",[812,179166,179169],{"href":179167,"rel":179168},"https://testcontainers.com/guides/testing-spring-boot-rest-api-using-testcontainers/",[816],"Getting Started with Testcontainers in a Java Spring Boot Project",[5909,179171,179172],{"id":133422},"💻 Projects",[651,179174,179175,179179],{},[812,179176,117624],{"href":179177,"rel":179178},"https://openjdk.org/jeps/8303683",[816]," have been submitted to be finalized in JDK 21 🤩 This is exciting and also means that this should be a part of Spring Framework 6.1 in November. This hasn’t finalized on our side yet but if things stay this way you will be able to use virtual threads in your Spring applications this year!",[651,179181,179182,179183,179188],{},"I was just talking about how much I have been enjoying working with Nuxt. Speaking of my friends at Nuxt they just released ",[812,179184,179187],{"href":179185,"rel":179186},"https://nuxt.com/blog/v3-3",[816],"version 3.3",". There are a few exciting enhancements and one that caught my eye was better logging support for browser dev tools.",[5909,179190,165017],{"id":165016},[1004,179192,179193],{},[651,179194,179195],{},"Attitude is the 'little' thing that makes a big difference. - Winston Churchill",[5909,179197,178573],{"id":95961},[651,179199,179200],{},"Added some blue to the background this week. What do you think?",[651,179202,179203],{},[812,179204,179205],{"href":179205,"rel":179206},"https://twitter.com/therealdanvega/status/1636379016956616704",[816],[4542,179208,157704],{"id":157703},[651,179210,179211,179212,166271],{},"I hope you enjoyed this newsletter installment, and I will talk to you in the next one. If you have any links you would like me to include please get ",[812,179213,179215],{"href":44086,"rel":179214},[816],"in touch with me",[651,179217,41105,179218,69920,179220,179222,179224,179226],{},[41107,179219],{},[41107,179221],{},[812,179223,161560],{"href":161111},[41107,179225],{},[812,179227,53869],{"href":53869,"rel":179228},[816],{"title":674,"searchDepth":790,"depth":790,"links":179230},[179231,179236,179237,179238,179244],{"id":179041,"depth":790,"text":179042,"children":179232},[179233,179234,179235],{"id":102858,"depth":892,"text":179045},{"id":179064,"depth":892,"text":179065},{"id":179096,"depth":892,"text":179097},{"id":179118,"depth":790,"text":179119},{"id":179140,"depth":790,"text":179141},{"id":157573,"depth":790,"text":177889,"children":179239},[179240,179241,179242,179243],{"id":69848,"depth":892,"text":164959},{"id":133422,"depth":892,"text":179172},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":179246,"date":179247},"chat-gpt-4","2023-03-20T07:30:00.000Z","/newsletter/2023/03/20/chat-gpt-4",{"title":179013,"description":179018},"newsletter/2023/03/20/chat-gpt-4","qpxlK0dQ72Hdeq-_ZJ8ObvkAPVG_Ww6YZKPXjL3CDfY",{"id":179253,"title":179254,"body":179255,"description":179421,"extension":793,"meta":179422,"navigation":797,"path":179425,"seo":179426,"stem":179427,"__hash__":179428},"content/newsletter/2023/03/27/project-loom.md","Project Loom, GraphQL Mutations, and a very special Birthday!",{"type":648,"value":179256,"toc":179408},[179257,179264,179267,179271,179274,179282,179288,179291,179293,179296,179299,179302,179306,179315,179321,179323,179325,179331,179333,179335,179343,179345,179354,179356,179363,179377,179379,179382,179388,179390,179395],[651,179258,179259,179260,179263],{},"Happy Monday and welcome to another edition of the newsletter. If you're enjoying these conversations, please consider sharing ",[812,179261,97431],{"href":82778,"rel":179262},[816]," with a friend.",[651,179265,179266],{},"In this edition of \"Dan Rambles About What He's Been Up To,\" I want to talk about Project Loom, GraphQL Mutations, and a very special birthday.",[4542,179268,179270],{"id":179269},"project-loom-virtual-threads","Project Loom & Virtual Threads",[651,179272,179273],{},"Last week, I spent a lot of time catching up on Project Loom and Virtual Threads. I wanted to give them a test drive in a Spring Boot application for a demo I am putting together. Before discussing the benefits of embracing virtual threads in a Spring application, it's important to provide some context about what they are and why they are needed.",[651,179275,179276,179277,179281],{},"I shared a graphic on ",[812,179278,51474],{"href":179279,"rel":179280},"https://twitter.com/therealdanvega/status/1638646601295044608",[816]," which, as of writing this newsletter, has over 55,000 views. This is amazing and indicates a high level of interest in this topic. Based on feedback, many people have questions about what Project Loom is and what problems it will solve for Spring developers. I hope to answer these and other questions in the near future.",[651,179283,179284],{},[660,179285],{"alt":179286,"src":179287},"Embracing Virtual Threads in Spring Applications","/images/newsletter/2023/03/27/embracing-virtual-threads.png",[651,179289,179290],{},"If you have any questions about this, please feel free to reach out and let me know.",[4542,179292,104529],{"id":104395},[651,179294,179295],{},"If you follow me, you know I am a huge fan of GraphQL. If you're interested in getting started with GraphQL, I have a ton of resources on my blog and YouTube channel. However, I don't have much content on mutations in GraphQL, so I decided to change that.",[651,179297,179298],{},"If you aren't aware, there are three main operation types in GraphQL: query, mutation, and subscription. A mutation is used any time you want to change data, such as creating, updating, or deleting a record. In this tutorial, I will show you a few examples of how to create a new resource using a mutation. I will walk you through using simple scalar types, such as strings, a complex object type, and a way to bulk create using a List. I hope you enjoy this tutorial and if you had questions about mutations I hope I answered them.",[5988,179300],{"id":179301},"u3FFRq3-0CM",[4542,179303,179305],{"id":179304},"happy-birthday-spring","Happy Birthday Spring",[651,179307,179308,179309,179314],{},"It’s kind of hard to believe but The Spring Framework celebrated it’s 19th birthday last week. If you want you can read the blog post announcing ",[812,179310,179313],{"href":179311,"rel":179312},"https://spring.io/blog/2004/03/24/spring-framework-1-0-final-released",[816],"Spring Framework 1.0"," back on March 24, 2004.",[651,179316,179317],{},[660,179318],{"alt":179319,"src":179320},"Happy 19th Birthday Spring!","/images/newsletter/2023/03/27/happy-birthday-spring.jpeg",[651,179322,179319],{},[4542,179324,179141],{"id":179140},[651,179326,179144,179327,179330],{},[812,179328,179149],{"href":179147,"rel":179329},[816]," by April 14th. We would love to hear from you. See you in Vegas!",[4542,179332,177889],{"id":157573},[5909,179334,164959],{"id":69848},[651,179336,179337,179342],{},[812,179338,179341],{"href":179339,"rel":179340},"https://spring.io/blog/2023/03/23/spring-boot-3-0-5-available-now",[816],"Spring 3.0.5"," was just released and with it comes Java 20 support.",[5909,179344,164971],{"id":157591},[651,179346,179347,179348,179353],{},"I had the pleasure of hosting a new episode of The Golden Path to SpringOne. In ",[812,179349,179352],{"href":179350,"rel":179351},"https://www.youtube.com/watch?v=VgfNCrIVll8",[816],"this episode",", I sat down with Maciej Walkowiak, a great member of our community and an all-around good guy. He spoke about the open-source project he runs, called Spring Cloud AWS, and its newest release, 3.0.",[5909,179355,164983],{"id":39439},[651,179357,63025,179358,179362],{},[812,179359,179352],{"href":179360,"rel":179361},"https://www.youtube.com/watch?v=L_Guz73e6fw",[816]," of the Lex Friedman Podcast Lex sits down with Sam Altman. If you don’t know Sam is the CEO of OpenAI and they talk about GPT-4, Chat GPT, and the future of AI.",[651,179364,179365,179366,2797,179371,179376],{},"In this episode of the ",[812,179367,179370],{"href":179368,"rel":179369},"https://inside.java/2023/03/23/newscast-44/",[816],"Inside Java Newscast",[812,179372,179375],{"href":179373,"rel":179374},"https://inside.java/u/BillyKorando",[816],"Billy Korando"," walks us through an unboxing of Java 20. That’s right Java 20 was just released and if you want to find out what’s new this is a great video to get caught up on.",[5909,179378,178573],{"id":95961},[651,179380,179381],{},"I was able to unbox the BenQ ScreenBar plus last week and so far I’m very impressed.",[651,179383,179384],{},[812,179385,179386],{"href":179386,"rel":179387},"https://twitter.com/therealdanvega/status/1638993071898611731",[816],[4542,179389,157704],{"id":157703},[651,179391,178368,179392,166271],{},[812,179393,41499],{"href":44086,"rel":179394},[816],[651,179396,41105,179397,69920,179399,179401,179403,179405],{},[41107,179398],{},[41107,179400],{},[812,179402,161560],{"href":161111},[41107,179404],{},[812,179406,53869],{"href":53869,"rel":179407},[816],{"title":674,"searchDepth":790,"depth":790,"links":179409},[179410,179411,179412,179413,179414,179420],{"id":179269,"depth":790,"text":179270},{"id":104395,"depth":790,"text":104529},{"id":179304,"depth":790,"text":179305},{"id":179140,"depth":790,"text":179141},{"id":157573,"depth":790,"text":177889,"children":179415},[179416,179417,179418,179419],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},"Happy Monday and welcome to another edition of the newsletter. If you're enjoying these conversations, please consider sharing my newsletter with a friend.",{"slug":179423,"date":179424},"project-loom","2023-03-27T07:30:00.000Z","/newsletter/2023/03/27/project-loom",{"title":179254,"description":179421},"newsletter/2023/03/27/project-loom","zsqFazy01avGWoh2jIyKBj8_4WnJTe5hqrIvK_SzK5U",{"id":179430,"title":179431,"body":179432,"description":179608,"extension":793,"meta":179609,"navigation":797,"path":179611,"seo":179612,"stem":179613,"__hash__":179614},"content/newsletter/2023/04/03/videotap.md","Heading to Devnexus, VideoTap and SpringOne CFP extended",{"type":648,"value":179433,"toc":179596},[179434,179443,179446,179449,179453,179456,179459,179462,179470,179476,179482,179485,179507,179515,179521,179523,179526,179528,179530,179539,179541,179550,179552,179560,179562,179565,179567,179570,179576,179578,179583],[651,179435,179436,179437,179442],{},"Happy Monday and welcome to another edition of the newsletter. As I send this out, I am heading to the airport to fly down to Atlanta for ",[812,179438,179441],{"href":179439,"rel":179440},"https://devnexus.com/",[816],"Devnexus",". If you're attending the conference, please let me know and be sure to say hi! 👋🏻",[651,179444,179445],{},"This is my first time attending this conference, and everyone has told me how amazing it is. Needless to say, I am beyond excited for this week. My friend Nate and I will be giving a talk on Spring Recipes, and we are scheduled right after the keynote on Wednesday, so you have no excuses.",[651,179447,179448],{},"After the talk, I am really looking forward to catching up with friends, learning new things, and enjoying a good meal or two. Additionally, I will be working at the VMware booth, so feel free to stop by and catch a demo while saying hello.",[4542,179450,179452],{"id":179451},"video-tap","Video Tap",[651,179454,179455],{},"I want to start this week by introducing a new service that I'm really excited about. But before I dive into the details of the service, let me explain the problem it solves for me.",[651,179457,179458],{},"As a content creator, I have several mediums through which I can produce content. I write articles for my blog, create content for this newsletter, and produce articles and guides for the Tanzu Developer Center. I also enjoy creating short and long-form videos, as well as entire video courses.",[651,179460,179461],{},"The problem for me is that there is only 1 of me and I have to be very focused on where I spend my time. A big goal of mine is to grow my YouTube channel and so that is where I have been focusing my efforts. I’m not sure many people understand the time that goes into creating a single video but there is a lot that goes into it.",[651,179463,179464,179465,664],{},"By the time I publish that video I am ready to move on to the next one and don’t have the energy to write a full blog post to go along with the video. I know the argument to that and what some creators do is create the blog post first and use that to drive the video. That sounds great but again I don’t have the time for that and If I did it I think it would take away from ",[812,179466,179469],{"href":179467,"rel":179468},"http://www.youtube.com/@danvega",[816],"my YouTube channel",[651,179471,179472,179475],{},[812,179473,97413],{"href":104412,"rel":179474},[816]," is a service that transcribes and converts videos into blog posts. They recently increased their video length limit to 25 minutes, which is ideal for most videos. What sets VideoTap apart is the quality of their blog post output. Instead of simply dumping the transcription, they use GPT-4 to generate high-quality blog posts that include code blocks, making them much more compelling articles. Here is a screenshot of a blog post I am currently working on.",[651,179477,179478],{},[660,179479],{"alt":179480,"src":179481},"VideoTap - Convert Videos to Blog Posts with ease","/images/newsletter/2023/04/03/videotap.png",[651,179483,179484],{},"If you want to read through a few blog posts that I have recently created using VideoTap you can check out these:",[5316,179486,179487,179492,179497,179502],{},[5332,179488,179489],{},[812,179490,104529],{"href":104527,"rel":179491},[816],[5332,179493,179494],{},[812,179495,333],{"href":95535,"rel":179496},[816],[5332,179498,179499],{},[812,179500,104541],{"href":104539,"rel":179501},[816],[5332,179503,179504],{},[812,179505,104548],{"href":104546,"rel":179506},[816],[651,179508,179509,179510,179514],{},"The service costs $1 per minute of video and this is reasonably priced. I was thinking about hiring someone to do this and I know this would have cost me 3-4x as much and I’m not sure how they would have turned out. If you’re interested they also have a professional service for $3.00 a minute where they will take all of your videos, convert them and send you back the markdown. They also have a free ",[812,179511,179513],{"href":104558,"rel":179512},[816],"YouTube chapters generator"," which I have found really useful.",[651,179516,179517,179520],{},[812,179518,97413],{"href":104412,"rel":179519},[816]," is one of those services that have come along at the perfect time. I can only imagine this service getting better and I’m excited about using it more in my creator workflow.",[4542,179522,179141],{"id":179140},[651,179524,179525],{},"This year, SpringOne is teaming up with VMware Explore in Las Vegas from August 21st-24th. The Spring community is essential, and without your voice, there is no Spring. If you didn’t hear the CFP deadline for VMware Explore has been extend until April 14th. If you’re interested in speaking please submit a talk, we would love to hear from you. See you in Vegas!",[4542,179527,177889],{"id":157573},[5909,179529,164959],{"id":69848},[651,179531,179532,179533,179538],{},"I’m a big fan of Nuxt and I enjoyed this article introducing ",[812,179534,179537],{"href":179535,"rel":179536},"https://nuxt.com/blog/introducing-nuxt-devtools",[816],"Nuxt DevTools",". Nuxt really does a great job of putting the developer experience first and this is just another example of that.",[5909,179540,164971],{"id":157591},[651,179542,179543,179544,179549],{},"I really enjoyed this panel discussion about ",[812,179545,179548],{"href":179546,"rel":179547},"https://www.youtube.com/watch?v=G1tR2bF1Geg",[816],"Continuous Learning as a Developer",". This is a subject that is near and dear to me as someone who considers himself a life long learner. I thought there panel was great and Layla did a great job running the show.",[5909,179551,164983],{"id":39439},[651,179553,179554,179555,179559],{},"I’m a big fan of the podcast My First Million. In ",[812,179556,179352],{"href":179557,"rel":179558},"https://www.youtube.com/watch?v=gbDl28Hx9TA",[816]," the guys talk with Hubspot Co-founder and CTO Dharmesh Shah about Chat GPT and AI. Dharmesh shares his excitement around AI and says that its the most disruptive technology since the internet became mainstream. I share his enthusiasm for what AI is going to do and how it will amplify us as developers.",[5909,179561,165017],{"id":165016},[651,179563,179564],{},"“Comparison is the thief of joy” - Teddy Roosevelt",[5909,179566,178573],{"id":95961},[651,179568,179569],{},"If you weren’t aware you can now use Java 20 in your Spring Boot projects 🤩",[651,179571,179572],{},[812,179573,179574],{"href":179574,"rel":179575},"https://twitter.com/therealdanvega/status/1640346550378414080",[816],[4542,179577,157704],{"id":157703},[651,179579,178368,179580,166271],{},[812,179581,41499],{"href":44086,"rel":179582},[816],[651,179584,41105,179585,69920,179587,179589,179591,179593],{},[41107,179586],{},[41107,179588],{},[812,179590,161560],{"href":161111},[41107,179592],{},[812,179594,53869],{"href":53869,"rel":179595},[816],{"title":674,"searchDepth":790,"depth":790,"links":179597},[179598,179599,179600,179607],{"id":179451,"depth":790,"text":179452},{"id":179140,"depth":790,"text":179141},{"id":157573,"depth":790,"text":177889,"children":179601},[179602,179603,179604,179605,179606],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},"Happy Monday and welcome to another edition of the newsletter. As I send this out, I am heading to the airport to fly down to Atlanta for Devnexus. If you're attending the conference, please let me know and be sure to say hi! 👋🏻",{"slug":104601,"date":179610},"2023-04-03T07:30:00.000Z","/newsletter/2023/04/03/videotap",{"title":179431,"description":179608},"newsletter/2023/04/03/videotap","-RWCUZae6c-xbyhTQnz3LAPmhXeyfcPpJiq1IEmM3GU",{"id":179616,"title":177669,"body":179617,"description":179621,"extension":793,"meta":179795,"navigation":797,"path":179797,"seo":179798,"stem":179799,"__hash__":179800},"content/newsletter/2023/04/11/devnexus-2023.md",{"type":648,"value":179618,"toc":179786},[179619,179622,179625,179639,179642,179648,179651,179657,179665,179668,179674,179678,179681,179687,179690,179696,179702,179705,179711,179714,179720,179724,179727,179733,179742,179748,179751,179757,179759,179762,179764,179766,179768,179773],[651,179620,179621],{},"Happy Tuesday and welcome to another edition of the newsletter. In this episode I want to tell you all about my trip to Atlanta for Devnexus 2023. Spring is now in full swing here in Northeast Ohio and I couldn’t be happier. It’s amazing how much getting out for a run or a trip to the park with the family can help your mental well being.",[4542,179623,177669],{"id":179624},"devnexus-2023",[651,179626,179627,179628,179633,179634,179638],{},"I attended Devnexus for the first time last week and wow what an incredible week it was. I got in Monday afternoon and started the trip off right with a run with my friend DaShaun. This would be the first of 3 runs on the trip all focused around the beautiful ",[812,179629,179632],{"href":179630,"rel":179631},"https://www.gwcca.org/centennial-olympic-park",[816],"Centennial Olympic Park"," in downtown Atlanta. Monday night I had some dinner with friends and then we went to meet a group at a local pub. This was my first chance to meet so many people including ",[812,179635,83204],{"href":179636,"rel":179637},"https://twitter.com/Sharat_Chander",[816]," who is one of the great people and leaders in our community.",[651,179640,179641],{},"Tuesday afternoon we checked in for the conference at the Georgia World Congress Center and were immediately met by Pratik, and Glenn who are 2 of the amazing organizers who help make this conference happen.",[651,179643,179644],{},[660,179645],{"alt":179646,"src":179647},"80D9E858-6FDA-41F8-A4FC-3F4C5C043A18.jpeg","/images/newsletter/2023/04/11/80D9E858-6FDA-41F8-A4FC-3F4C5C043A18.jpeg",[651,179649,179650],{},"I got a really cool bag as a speaker gift and whole bunch of cool swag by the time the conference was over.",[651,179652,179653],{},[660,179654],{"alt":179655,"src":179656},"2B260C5C-98A9-47A2-8D59-AAAB99E2D2D2.jpeg","/images/newsletter/2023/04/11/2B260C5C-98A9-47A2-8D59-AAAB99E2D2D2.jpeg",[651,179658,179659,179660,179664],{},"After we were checked in DaShaun and I got setup in one of the presentation rooms and held a ",[812,179661,97345],{"href":179662,"rel":179663},"https://youtube.com/live/kkq-FdLzXQk?feature=share",[816]," live from Devnexus. We ended up using a tripod, iPhone and a USB microphone and I thought the live stream was pretty good quality. I believe this is the first time we have ever done Office Hours live so that was pretty cool.",[651,179666,179667],{},"Tuesday night was the speaker dinner and it was a great way to kick off the conference. I had the opportunity to meet so many people I have talked to over the years. I really can’t describe just how cool it was to see so many people I have looked up to over the years in the same room. I called it an early night because the 1st day of the conference would be a busy one for me.",[651,179669,179670],{},[660,179671],{"alt":179672,"src":179673},"0A367485-629D-4C24-9174-6EE36AF03994.jpeg","/images/newsletter/2023/04/11/0A367485-629D-4C24-9174-6EE36AF03994.jpeg",[5909,179675,179677],{"id":179676},"day-1","Day 1",[651,179679,179680],{},"I was up early for day 1 like a kid on Christmas morning. DaShaun and I walked over early to the conference early to grab some breakfast. We ate in the speaker room with my good friend Todd Sharp. The 3 of us went into the keynote early so we could get front row seats 🤓",[651,179682,179683],{},[660,179684],{"alt":179685,"src":179686},"F1C7E511-FD9D-4DB6-94A5-0F6620011D8B.jpeg","/images/newsletter/2023/04/11/F1C7E511-FD9D-4DB6-94A5-0F6620011D8B.jpeg",[651,179688,179689],{},"The keynote was given by Arun Gupta from IBM and it would be my favorite presentation of the entire conference. Arun talked about the importance of soft skills and 5 in particular. I’m still not sure if the presentations will be available online to everyone but if they are this is a must watch in my books. Thank you Arun for putting together such a thoughtful and impactful presentation!",[651,179691,179692],{},[812,179693,179694],{"href":179694,"rel":179695},"https://twitter.com/arungupta/status/1643663149675970580",[816],[651,179697,179698],{},[660,179699],{"alt":179700,"src":179701},"474A0F8B-BA2D-45F9-975B-C4F8686D045C.jpeg","/images/newsletter/2023/04/11/474A0F8B-BA2D-45F9-975B-C4F8686D045C.jpeg",[651,179703,179704],{},"I had to dip out of the keynote a few minutes early and get setup for my presentation. My friend Nate Schutta and I presented on Spring Recipes, common sense solutions with Spring. The idea is that there are a vast number of projects in the ecosystem and you might not realize that Spring can do that. We presented a problem and then a way to solve it using Spring. Overall I thought the presentation went well and I’m very appreciative of everyone who came out to watch us.",[651,179706,179707],{},[660,179708],{"alt":179709,"src":179710},"D80D9F1E-46B2-4029-AFD2-A4C76E403487.jpeg","/images/newsletter/2023/04/11/D80D9F1E-46B2-4029-AFD2-A4C76E403487.jpeg",[651,179712,179713],{},"It was also really great to see and catch up with my friend Ken Kousen who was in the room for our presentation 🤩",[651,179715,179716],{},[660,179717],{"alt":179718,"src":179719},"338A27FA-EE43-4F55-B1F3-CF07C9445871.jpeg","/images/newsletter/2023/04/11/338A27FA-EE43-4F55-B1F3-CF07C9445871.jpeg",[4542,179721,179723],{"id":179722},"day-2","Day 2",[651,179725,179726],{},"Day 1 was a wonderful experience but it was also a really long day. I decided to sleep in a little, got up and went for an amazing morning run! After I got ready and checked out of my room I went over to the conference to catch a little bit of DaShaun’s talk.",[651,179728,179729],{},[660,179730],{"alt":179731,"src":179732},"IMG_6372.HEIC","/images/newsletter/2023/04/11/IMG_6372.jpeg",[651,179734,179735,179736,179741],{},"After that I was on booth duty and it was my first time working a booth so I didn’t know what to expect. This was a chance for me to tell everyone about this amazing new product we have called ",[812,179737,179740],{"href":179738,"rel":179739},"http://spring.academy",[816],"spring.academy",". I honestly had a blast talking to so many developers and learning about where they work and what they use Spring for.",[651,179743,179744],{},[660,179745],{"alt":179746,"src":179747},"IMG_6426.JPG","/images/newsletter/2023/04/11/IMG_6426.jpg",[651,179749,179750],{},"After booth duties I had to head to the airpot and head back to Cleveland.",[651,179752,179753],{},[812,179754,179755],{"href":179755,"rel":179756},"https://twitter.com/therealdanvega/status/1644040591020310528",[816],[4542,179758,9042],{"id":9041},[651,179760,179761],{},"As with every conference my favorite track was the hallway track. I had the opportunity to talk with so many people and I honestly came away from the conference with a huge sense of pride. I had so many people come up to me and tell me how much my content has helped them over the years. It’s one thing to see a comment on the internet from someone you don’t know, its another level when someone tells you that in person. If my producing content tank was a little empty heading into Devnexus, it’s now filled up! I love this community and I’m thankful for the opportunity to be a part of it. 🙏",[4542,179763,179141],{"id":179140},[651,179765,179525],{},[4542,179767,157704],{"id":157703},[651,179769,178368,179770,166271],{},[812,179771,41499],{"href":44086,"rel":179772},[816],[651,179774,41105,179775,69920,179777,179779,179781,179783],{},[41107,179776],{},[41107,179778],{},[812,179780,161560],{"href":161111},[41107,179782],{},[812,179784,53869],{"href":53869,"rel":179785},[816],{"title":674,"searchDepth":790,"depth":790,"links":179787},[179788,179791,179792,179793,179794],{"id":179624,"depth":790,"text":177669,"children":179789},[179790],{"id":179676,"depth":892,"text":179677},{"id":179722,"depth":790,"text":179723},{"id":9041,"depth":790,"text":9042},{"id":179140,"depth":790,"text":179141},{"id":157703,"depth":790,"text":157704},{"slug":179624,"date":179796},"2023-04-11T10:30:00.000Z","/newsletter/2023/04/11/devnexus-2023",{"title":177669,"description":179621},"newsletter/2023/04/11/devnexus-2023","4rDspgrs6d7jErLmOQ9Z-tuv25gN8jWhYI4lI0_ObDc",{"id":179802,"title":179803,"body":179804,"description":179808,"extension":793,"meta":179990,"navigation":797,"path":179992,"seo":179993,"stem":179994,"__hash__":179995},"content/newsletter/2023/04/17/virtual-threads-spring.md","Virtual Threads in Spring & Launching a New Blog!",{"type":648,"value":179805,"toc":179975},[179806,179809,179812,179814,179817,179823,179825,179833,179836,179839,179842,179845,179847,179850,179866,179870,179873,179876,179881,179884,179891,179894,179897,179900,179909,179911,179913,179920,179923,179935,179939,179947,179949,179955,179957,179962],[651,179807,179808],{},"Happy Monday and welcome to another edition of the newsletter. The weather in Northeast Ohio was amazing last week, just what my mental health needed. After spending a week in Atlanta for Devnexus, the continuation of good weather was a welcome change. I was able to run outside and it instantly put me in a better mood.",[651,179810,179811],{},"In this edition, I will discuss my latest video on Virtual Threads in Spring and introduce a new website I launched using Nuxt 3 + Notion.",[4542,179813,82720],{"id":47833},[5909,179815,117],{"id":179816},"virtual-threads-in-spring-boot",[651,179818,179819,179820,104631],{},"It's been announced that ",[812,179821,104630],{"href":104628,"rel":179822},[816],[651,179824,104622],{},[651,179826,179827,179828,179832],{},"I wrote a ",[812,179829,24879],{"href":179830,"rel":179831},"https://www.danvega.dev/blog/2023/04/12/virtual-threads-spring/",[816]," and created a video that walks through the thread-per-request model and how Virtual Threads are going to improve performance in our Spring MVC applications.",[5988,179834],{"id":179835},"Is5HXJhC3jE",[5909,179837,179838],{"id":105614},"GraphQL Client",[651,179840,179841],{},"I have been talking a lot lately about how to build GraphQL APIs but what happens when you need to call a GraphQL API within your Spring applications? It turns out that the Spring for GraphQL gives us an easy to use client based on the Web Client. You can use the appropriate version for whatever server transport you are using such as HTTP, WebSocket, RSocket.",[5988,179843],{"id":179844},"BuPItqaVeGo",[5909,179846,165117],{"id":165116},[651,179848,179849],{},"I love creating YouTube shorts because they are a different format and because they come in under 60 seconds they take much less time to produce. I’m still playing with different formats. Last week I created 2 shorts around some basic concepts in Java.",[5316,179851,179852,179859],{},[5332,179853,179854],{},[812,179855,179858],{"href":179856,"rel":179857},"https://youtube.com/shorts/WtWbR6UrT8E?feature=share",[816],"How to convert a String into an Integer in Java",[5332,179860,179861],{},[812,179862,179865],{"href":179863,"rel":179864},"https://studio.youtube.com/video/HuGWdsHGcm4/edit",[816],"Master Java String Comparison: Unlock the Power of the Equals Method in Under 60 Seconds!",[4542,179867,179869],{"id":179868},"nuxt-notion","Nuxt + Notion",[651,179871,179872],{},"Before I dive into the specifics I want to give you a little back story on this new project. When my wife and I had our first daughter we decided early on that she was going to stay home to take care of her. We feel very fortunate to be a position to have done this but my wife did need to leave her career behind. She graduated college as an english major and has always had a passion for writing.",[651,179874,179875],{},"This past Christmas I bought her a laptop and a book on writing and encouraged her to pursue her passion. I introduced her to Notion and all of the amazing features you get out of the box and showed her how to organize her content. She began writing about what she is passionate about and she happens to pretty amazing at all of them as well. She wanted to document her time being a Mom and everything that goes into that. With that she came up with the name Momoir (A memoir in Mom form) and This is My Momoir was born.",[651,179877,179878],{},[660,179879],{"alt":101356,"src":179880},"/images/newsletter/2023/04/17/momoir.png",[651,179882,179883],{},"Initially, I expected her to write a few blog posts before I started building the site. However, a couple of months passed, and she managed to write 50 blog posts! 🤯 She is incredibly productive, which made me rethink how I should approach building the site.",[651,179885,179886,179887,179890],{},"All the posts were already in Notion, so it didn't make sense to export them to another CMS. Additionally, I wanted her to be able to publish new content without me being a bottleneck. Therefore, I decided to use the ",[812,179888,101347],{"href":101345,"rel":179889},[816]," and make Notion the CMS for the site.",[651,179892,179893],{},"I chose Nuxt 3 because of my previous experience using it and because I find it to be a fantastic tool. The Nuxt team has thought of everything, and the developer experience is excellent.",[651,179895,179896],{},"The biggest challenge I faced was with Notion being so customizable. Everything in the API is returned as \"blocks\", which meant that for everything from paragraphs to images and videos, I had to write a custom component to render that block.",[651,179898,179899],{},"I have a lot to share with you about this project, so if you're interested in hearing the details, please let me know.",[651,179901,179902,179903,179908],{},"Recently, my wife celebrated her birthday and decided that it was a great time to launch the site. If you want to read more about it, you can visit her \"",[812,179904,179907],{"href":179905,"rel":179906},"https://www.thisismymomoir.com/blog/its-our-birthday",[816],"It's Our Birthday","\" post. I just want to publicly say how proud I am of you, Jen (I know you're reading this). It takes a lot to put yourself out there, and I absolutely love what you did with this project. I can't wait to see where it takes you!",[4542,179910,177889],{"id":157573},[5909,179912,164971],{"id":157591},[651,179914,178347,179915,179919],{},[812,179916,85142],{"href":179917,"rel":179918},"https://www.youtube.com/watch?v=oKYeGfZnznk",[816]," by Sean Cannel with Think Media where he discusses the painful truths about YouTube Short.",[5909,179921,179922],{"id":133422},"👨🏼💻 Projects",[651,179924,179925,179926,179931,179932,88808],{},"I thought ",[812,179927,179930],{"href":179928,"rel":179929},"https://github.com/maciej-scratches/spring-boot-3.1-service-connection-demo",[816],"this repository"," from Maciej was really interesting. This repository shows multiple ways how to set up and use Testcontainers in a Spring Boot application, including the new ",[676,179933,179934],{},"@ServiceConnection",[5909,179936,179938],{"id":179937},"️podcasts","🎙️ Podcasts",[651,179940,178347,179941,179946],{},[812,179942,179945],{"href":179943,"rel":179944},"https://www.youtube.com/watch?v=aMD0zdlrH-o",[816],"this podcast"," from Greg Turnquist on the Spring Boot Learning channel. In this episode Greg walks us through 5 of his pro tips for professional coders in 2023. Greg is a pro and it was great to hear his insight.",[5909,179948,178573],{"id":95961},[651,179950,179951],{},[812,179952,179953],{"href":179953,"rel":179954},"https://twitter.com/therealdanvega/status/1646867149766443008",[816],[4542,179956,157704],{"id":157703},[651,179958,178368,179959,166271],{},[812,179960,41499],{"href":44086,"rel":179961},[816],[651,179963,41105,179964,69920,179966,179968,179970,179972],{},[41107,179965],{},[41107,179967],{},[812,179969,161560],{"href":161111},[41107,179971],{},[812,179973,53869],{"href":53869,"rel":179974},[816],{"title":674,"searchDepth":790,"depth":790,"links":179976},[179977,179982,179983,179989],{"id":47833,"depth":790,"text":82720,"children":179978},[179979,179980,179981],{"id":179816,"depth":892,"text":117},{"id":105614,"depth":892,"text":179838},{"id":165116,"depth":892,"text":165117},{"id":179868,"depth":790,"text":179869},{"id":157573,"depth":790,"text":177889,"children":179984},[179985,179986,179987,179988],{"id":157591,"depth":892,"text":164971},{"id":133422,"depth":892,"text":179922},{"id":179937,"depth":892,"text":179938},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":104989,"date":179991},"2023-04-17T10:30:00.000Z","/newsletter/2023/04/17/virtual-threads-spring",{"title":179803,"description":179808},"newsletter/2023/04/17/virtual-threads-spring","2FDaxkCUmsJTs5yyykxVoI_n048oA3CVRO5HnAHGy3s",{"id":179997,"title":179998,"body":179999,"description":180003,"extension":793,"meta":180199,"navigation":797,"path":180201,"seo":180202,"stem":180203,"__hash__":180204},"content/newsletter/2023/04/24/spring-boot-3-1-first-look.md","🔥 What’s new in Spring Boot 3.1",{"type":648,"value":180000,"toc":180185},[180001,180004,180010,180013,180017,180025,180067,180080,180084,180087,180090,180097,180100,180103,180106,180109,180112,180115,180131,180133,180135,180144,180146,180154,180156,180159,180165,180167,180172],[651,180002,180003],{},"Happy Monday and welcome to the start of another work week. I hope you had a wonderful weekend. Mine was great - this past weekend I had my first daddy-daughter dance with my oldest daughter, Bella (5). It was an amazing night and she was so excited to get “fancied up”, as she says. We had a delicious meal with friends, an ice cream bar, hula hoops, and of course, we danced the night away.",[651,180005,180006],{},[660,180007],{"alt":180008,"src":180009},"Daddy Daughter Dance 2023","/images/newsletter/2023/04/17/daddy-daughter-dance.png",[651,180011,180012],{},"This week, I want to talk to you about the newly released Spring Boot 3.1 RC1. Additionally, I will discuss what I worked on last week, which includes Spring Beans, Spring Security, and YouTube Shorts.",[4542,180014,180016],{"id":180015},"spring-boot-310-rc-1","Spring Boot 3.1.0 RC 1",[651,180018,180019,180020,180024],{},"Spring Boot 3.1.0 RC 1 has just been released. If you want to learn more about it, you can check out the ",[812,180021,95704],{"href":180022,"rel":180023},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.1.0-RC1-Release-Notes",[816],". I will highlight some of the new features in this release.",[5316,180026,180027,180032,180044,180056,180062],{},[5332,180028,180029,180031],{},[2939,180030,171725],{}," - I’m a big fan of using Testcontainers in my integration tests to provide a lightweight throwaway database or any other container that I need. In Spring Boot 3.1 it’s also possible to use them at development time.",[5332,180033,180034,180037,180038,180040,180041,664],{},[2939,180035,180036],{},"Docker Compose"," - A new module ",[676,180039,107346],{}," provides integration with Docker Compose. When your app is starting up, the Docker Compose integration will look for a configuration file in the current working directory. No more starting your application only to see an error because you forgot to run ",[676,180042,180043],{},"docker compose up",[5332,180045,180046,180049,180050,180052,180053,180055],{},[2939,180047,180048],{},"SSL Configuration"," - SSL trust material such as Java KeyStores and PEM-encoded certificates can now be configured using properties and applied to connections of various types such as embedded web servers, data services, ",[676,180051,23551],{},"\n and ",[676,180054,109920],{},"\n in a more consistent manner.",[5332,180057,180058,180061],{},[2939,180059,180060],{},"Docker Images"," - There are some nice improvements to customizing the Docker Image using Maven or Gradle.",[5332,180063,180064,180066],{},[2939,180065,169686],{}," - Spring Boot 3.1.0-RC1 moves to new versions of several Spring projects",[651,180068,180069,180070,180075,180076,180079],{},"We plan to discuss this topic in more depth during ",[812,180071,180074],{"href":180072,"rel":180073},"https://youtube.com/live/lLykB3GI1Cs?feature=share",[816],"Spring Office Hours this week",". Please join us live or watch the replay. I also plan to create one or two videos for my ",[812,180077,101297],{"href":101295,"rel":180078},[816],". If you haven't subscribed yet, what are you waiting for?",[4542,180081,180083],{"id":180082},"content-creation-machine","Content Creation Machine",[651,180085,180086],{},"Here are the highlights of the content I worked on last week.",[5909,180088,177607],{"id":180089},"spring-beans",[651,180091,180092,180093,180096],{},"In a recent video, I discussed Spring Beans and how to create them using @Component vs @Bean annotations. Have you ever come across a term that seemed confusing, only to realize later that it was quite simple? For me, Spring Beans was one of those terms when I first started out. In this video, I delve deeper into the topic. I also covered Spring Beans in my ",[812,180094,23034],{"href":120237,"rel":180095},[816],", but that is a four-hour long video.",[5988,180098],{"id":180099},"CWEQ-1vff1o",[5909,180101,180102],{"id":106811},"Multiple Spring Security Configs",[651,180104,180105],{},"Recently, I have discussed how to configure Spring Security using the new component model. This video covers a couple of scenarios where you may need multiple security configurations and how to create them. In previous versions of Spring Security, this was difficult, but it is now much easier.",[5988,180107],{"id":180108},"PczgM2L3w60",[5909,180110,178538],{"id":180111},"shorts",[651,180113,180114],{},"I love making YouTube shorts because they allow me to produce videos in a shorter time cycle. They also provide an opportunity for me to try out new ideas and editing techniques. Last week, I produced a short video on how to pronounce \"char\" in Java, and it was so much fun. I was literally laughing out loud in between takes. Moreover, they have been receiving a good number of views, which is encouraging. So, be on the lookout for more videos this week!",[5316,180116,180117,180124],{},[5332,180118,180119],{},[812,180120,180123],{"href":180121,"rel":180122},"https://youtube.com/shorts/YTcoGEIuRUQ?feature=share",[816],"How to pronounce char in Java",[5332,180125,180126],{},[812,180127,180130],{"href":180128,"rel":180129},"https://youtube.com/shorts/nSYpFhlUFzM?feature=share",[816],"Java String .contains() method",[4542,180132,177889],{"id":157573},[5909,180134,164971],{"id":157591},[651,180136,180137,180138,180143],{},"I really enjoyed this 60 Minutes piece on ",[812,180139,180142],{"href":180140,"rel":180141},"https://www.youtube.com/watch?v=880TBXMuzmk",[816],"The AI revolution",". Google's CEO, Sundar Pichai, believes AI's impact will depend on human nature and that the AI revolution is coming quickly.",[5909,180145,179922],{"id":133422},[651,180147,180148,180149,180153],{},"As a huge fan of Vue and Nuxt, it was great to see the Nuxt team release another really impressive minor update. Check out this ",[812,180150,24879],{"href":180151,"rel":180152},"https://nuxt.com/blog/v3-4",[816]," to learn about the new features in Nuxt 3.4.",[5909,180155,178573],{"id":95961},[651,180157,180158],{},"I’m really enjoying this BenQ ScreenBar Plus. I thought it would just be for nighttime productivity but ever since I added blackout curtains to my office It comes in handy at all times of the day!",[651,180160,180161],{},[812,180162,180163],{"href":180163,"rel":180164},"https://twitter.com/therealdanvega/status/1650292153535324160",[816],[4542,180166,157704],{"id":157703},[651,180168,178368,180169,166271],{},[812,180170,41499],{"href":44086,"rel":180171},[816],[651,180173,41105,180174,69920,180176,180178,180180,180182],{},[41107,180175],{},[41107,180177],{},[812,180179,161560],{"href":161111},[41107,180181],{},[812,180183,53869],{"href":53869,"rel":180184},[816],{"title":674,"searchDepth":790,"depth":790,"links":180186},[180187,180188,180193,180198],{"id":180015,"depth":790,"text":180016},{"id":180082,"depth":790,"text":180083,"children":180189},[180190,180191,180192],{"id":180089,"depth":892,"text":177607},{"id":106811,"depth":892,"text":180102},{"id":180111,"depth":892,"text":178538},{"id":157573,"depth":790,"text":177889,"children":180194},[180195,180196,180197],{"id":157591,"depth":892,"text":164971},{"id":133422,"depth":892,"text":179922},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":180200,"date":179991},"spring-boot-3-1-first-look","/newsletter/2023/04/24/spring-boot-3-1-first-look",{"title":179998,"description":180003},"newsletter/2023/04/24/spring-boot-3-1-first-look","de-4T6KNJyhQJwyLeasT6iyLT8MwE-ue7vjiycm3qM0",{"id":180206,"title":180207,"body":180208,"description":180212,"extension":793,"meta":180372,"navigation":797,"path":180374,"seo":180375,"stem":180376,"__hash__":180377},"content/newsletter/2023/05/01/spring-office-hours-podcast.md","Spring Office Hours Podcast, Spring Boot Docker Compose & More!",{"type":648,"value":180209,"toc":180358},[180210,180213,180216,180220,180223,180226,180235,180241,180247,180250,180252,180254,180258,180261,180264,180267,180270,180273,180276,180279,180281,180285,180288,180291,180293,180295,180314,180316,180325,180327,180330,180332,180338,180340,180345],[651,180211,180212],{},"Happy Monday and welcome to another edition of the newsletter. I just want to let you know that there will be no newsletter next week as I will be on vacation in Hilton Head SC with my family. We are really looking forward to some fun in the sun and I am even going to get some golf in. I haven’t golfed in 20 years so I’m just hoping I don’t embarrass myself 🤦♂️",[651,180214,180215],{},"In this weeks edition I want to tell you about the Spring Office Hours Podcast and the content I worked on last week.",[4542,180217,180219],{"id":180218},"spring-office-hours-podcast","Spring Office Hours Podcast",[651,180221,180222],{},"DaShaun and I created the Spring Office Hours live stream a little over a year ago as a way to help answer questions from the community. We began talking about converting this over to a podcast at the end of last year but never got around to doing it.",[651,180224,180225],{},"I think my reservations were the same we all experience when we want to start something new. We want everything to be perfect and if we are honest with ourselves, they never will be. I decided to that this weeks show would be our first “Podcast” and we can always iterate and improve on it each week.",[651,180227,180228,180229,180234],{},"With that we recorded our livestream and tried to keep our audio only friends in mind. From there I signed up for a service called ",[812,180230,180233],{"href":180231,"rel":180232},"http://Transistor.fm",[816],"Transistor.fm"," which helps you manage and distribute podcasts. I extracted the audio from the livestream using Adobe Premiere Pro and uploaded it to Transistor. They handle distributing your podcasts to all of the networks and at the time of this writing we are on all of the major providers.",[651,180236,180237],{},[660,180238],{"alt":180239,"src":180240},"Spotify Podcasts","/images/newsletter/2023/05/01/spotify-podcast.png",[651,180242,180243],{},[660,180244],{"alt":180245,"src":180246},"Apple Podcasts","/images/newsletter/2023/05/01/apple-podcast.png",[651,180248,180249],{},"It would mean the world to me if you would subscribe to our podcast wherever you listen. I absolutely love podcasts and I’m already trying to think of the next one I could do 😜",[4542,180251,180083],{"id":180082},[651,180253,180086],{},[4542,180255,180257],{"id":180256},"youtube-cli","YouTube CLI",[651,180259,180260],{},"I absolutely love being a software developer because it gives me this superpower ability to solve my own problems. Part of my job requires me track the content that I am working on. This is for reporting purposes but also helps me figure out what content is doing well over a longer period of time.",[651,180262,180263],{},"Last year I tracked all of my YouTube videos in a very manual process. Once I month I would go into a spreadsheet and manually enter each video along with some details about it including number of views. This doesn’t scale very well because it’s such a manual process and I’m not updating the number of views so it’s not very accurate either.",[651,180265,180266],{},"I decided to sit down and write a command-line-interface in Java that I could run on my local machine and get these stats. I could have written this using something like bash but to be honest I don’t use it enough and it would have taken me forever to Google (or Chat GPT) everything. I decided to write this using Spring Shell so that I could be in a familiar environment and with Spring Boot 3 I could compile this down to a native executable.",[651,180268,180269],{},"Not only did I have a lot of fun building out this project for myself but I feel like you might get a lot out of it as well. I hope you enjoy this and I would love to hear your thoughts on it.",[5988,180271],{"id":180272},"Oi8JeTswYVI",[4542,180274,180275],{"id":107346},"Spring Boot Docker Compose",[651,180277,180278],{},"Spring Boot 3.1 RC 1 was released a little over a week ago. I had a chance to dig through the release notes and was really excited about a few of the new features. In this video I took a look at one of those features which was the Spring Boot Docker Compose Module.",[5988,180280],{"id":120177},[4542,180282,180284],{"id":180283},"spring-security-social-login","Spring Security Social Login",[651,180286,180287],{},"Last but not least I have received a few requests for this one. In this tutorial I look at how you can provide a social login for your Spring Boot applications using Spring Security. In the example that I worked on you can login via form login with a username / password or Google & GitHub.",[5988,180289],{"id":180290},"us0VjFiHogo",[4542,180292,177889],{"id":157573},[5909,180294,164959],{"id":69848},[5316,180296,180297,180306],{},[5332,180298,180299,180300,180305],{},"I can’t even begin to tell you how excited I was to find out that ",[812,180301,180304],{"href":180302,"rel":180303},"https://aws.amazon.com/blogs/compute/java-17-runtime-now-available-on-aws-lambda/",[816],"AWS Lambda now supports Java 17",". In this article my friend Mark Sailes explains what this means for Lambda developers. I can tell you that I have been waiting for this and you can expect some content around this in the near future!",[5332,180307,177894,180308,180313],{},[812,180309,180312],{"href":180310,"rel":180311},"https://foojay.io/today/creating-scalable-openai-gpt-applications-in-java/",[816],"Creating Scalable OpenAI GPT applications in Java"," by Denis Magda. This article walks you through how to send prompts to GPT and how to scale your application by storing data in YugabyteDB.",[5909,180315,164971],{"id":157591},[651,180317,180318,180319,180324],{},"I enjoyed watching ",[812,180320,180323],{"href":180321,"rel":180322},"https://www.youtube.com/watch?v=--TsEhxgRfg",[816],"this presentation"," by Alina Yurenko at Voxxed days Bucharest. Alina is a Developer Advocate for GraalVM and walks us through how to Supercharge your Native Image Applications using GraalVM.",[5909,180326,165017],{"id":165016},[651,180328,180329],{},"“The best time to plant a tree was 20 years ago. The second best time is now” - Chinese Proverb",[5909,180331,178573],{"id":95961},[651,180333,180334],{},[812,180335,180336],{"href":180336,"rel":180337},"https://twitter.com/therealdanvega/status/1651717016473018370",[816],[4542,180339,157704],{"id":157703},[651,180341,178368,180342,166271],{},[812,180343,41499],{"href":44086,"rel":180344},[816],[651,180346,41105,180347,69920,180349,180351,180353,180355],{},[41107,180348],{},[41107,180350],{},[812,180352,161560],{"href":161111},[41107,180354],{},[812,180356,53869],{"href":53869,"rel":180357},[816],{"title":674,"searchDepth":790,"depth":790,"links":180359},[180360,180361,180362,180363,180364,180365,180371],{"id":180218,"depth":790,"text":180219},{"id":180082,"depth":790,"text":180083},{"id":180256,"depth":790,"text":180257},{"id":107346,"depth":790,"text":180275},{"id":180283,"depth":790,"text":180284},{"id":157573,"depth":790,"text":177889,"children":180366},[180367,180368,180369,180370],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":180218,"date":180373},"2023-05-01T07:30:00.000Z","/newsletter/2023/05/01/spring-office-hours-podcast",{"title":180207,"description":180212},"newsletter/2023/05/01/spring-office-hours-podcast","2Ko7bk3LBNz76N8WvyNODC-3eIowddw9uPvZF5OleXs",{"id":180379,"title":180380,"body":180381,"description":180385,"extension":793,"meta":180626,"navigation":797,"path":180629,"seo":180630,"stem":180631,"__hash__":180632},"content/newsletter/2023/05/22/spring-boot-3-1.md","Spring Boot 3.1, 30K Subscribers, and AWS Lambda now supports Java 17!",{"type":648,"value":180382,"toc":180611},[180383,180386,180392,180395,180401,180404,180408,180416,180436,180439,180441,180443,180446,180450,180453,180456,180459,180461,180464,180470,180474,180483,180520,180524,180527,180532,180534,180536,180562],[651,180384,180385],{},"Happy Monday and welcome to another edition of the newsletter. It’s been a couple of weeks since my last update but it was for a good reason. I was able to take a week off and spend some fun in the sun with the family as we headed down to Hilton Head SC which is becoming a yearly tradition for us. I absolutely love Hilton Head, it’s just so calm and relaxing and fun for the whole family.",[651,180387,180388],{},[660,180389],{"alt":180390,"src":180391},"Hilton Head Family","/images/newsletter/2023/05/22/hilton-head-family.jpeg",[651,180393,180394],{},"I played 18 holes at the Old South Golf course, and it was beautiful. This was the first round of golf I played since I was probably 17. Needless to say, I wasn't very good, but I had fun. I am looking forward to playing more golf this summer and improving my game.",[651,180396,180397],{},[660,180398],{"alt":180399,"src":180400},"Golf in Hilton Head","/images/newsletter/2023/05/22/hilton-head-golf.jpeg",[651,180402,180403],{},"Last week, I traveled to Dallas for a customer meeting. Although it was a short trip, it went really well. I had the opportunity to talk to a great group about building GraphQL APIs in Java & Spring. The group was interactive, and there were a ton of really great questions. I have more of these shorter, more focused trips coming up next month, and I'm really excited for them.",[4542,180405,180407],{"id":180406},"spring-boot-31-released","Spring Boot 3.1 released",[651,180409,180410,180411,180415],{},"Spring Boot 3.1 was released and it came packed with some exciting features. If you want to learn more about I would suggest diving into the ",[812,180412,95704],{"href":180413,"rel":180414},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.1-Release-Notes",[816],". The new and noteworthy items were:",[5316,180417,180418,180421,180424,180427,180430,180433],{},[5332,180419,180420],{},"Using Testcontainers at Development Time",[5332,180422,180423],{},"Spring Boot Docker Compose Module",[5332,180425,180426],{},"Unified SSL Configuration",[5332,180428,180429],{},"Auto-configuration for Spring Authorization Server",[5332,180431,180432],{},"Docker Image Building Additions",[5332,180434,180435],{},"Spring for GraphQL Improvements",[651,180437,180438],{},"If you missed it I did a video on the new Spring Boot Docker Compose module",[5988,180440],{"id":120177},[4542,180442,15432],{"id":61224},[651,180444,180445],{},"While I was on vacation, I reached a nice milestone of 30,000 subscribers on my YouTube channel. The channel is gaining traction, and my plan is to continue producing the same content. I am also looking for sponsors for the channel. If you're interested in partnering with me, please reply to this message with an email.",[5909,180447,180449],{"id":180448},"aws-lambda-meet-java-17","AWS Lambda 🤝 Meet Java 17",[651,180451,180452],{},"AWS recently announced support for Java 17 on Lambda. This is exciting because if you are authoring plain Java functions, you can take advantage of all the new language features from Java 11 to 17. If you are a Spring Developer, this means that you can now write your functions in Spring Boot 3 + Spring Cloud Function.",[651,180454,180455],{},"I was thrilled when I heard the news, and I created a tutorial covering these two scenarios.",[5988,180457],{"id":180458},"bxK4GscuVgs",[4542,180460,180219],{"id":180218},[651,180462,180463],{},"After a week off we were back to the podcast last week where discussed all things GraphQL. This was the 3rd episode that we released as a podcast and we are continuing to iterate and improve on it. I think the next big step is to get a hook and an outro and those are on my list for this week.",[651,180465,180466],{},[812,180467,180468],{"href":180468,"rel":180469},"https://share.transistor.fm/e/1c16ee76",[816],[4542,180471,180473],{"id":180472},"devnexus-recordings-available","Devnexus Recordings Available",[651,180475,180476,180477,180482],{},"If you weren’t able to catch ",[812,180478,180481],{"href":180479,"rel":180480},"https://www.youtube.com/watch?v=_jTBKo1qxoA&t=274s",[816],"my talk"," at Devnexus the recordings are now available. I talked about this before but I really enjoyed this conference. I didn’t get a chance to catch a ton of talks though so I was excited to see the recordings made available. I would probably just watch all of them if I were you but if you want a list of a few my favorites here they are:",[5316,180484,180485,180492,180499,180506,180513],{},[5332,180486,180487],{},[812,180488,180491],{"href":180489,"rel":180490},"https://www.youtube.com/watch?v=0GXmTfNoSlY",[816],"Devnexus 2023 - Keynote: Five Skills to Force Multiply Your Technical Talent - Arun Gupta",[5332,180493,180494],{},[812,180495,180498],{"href":180496,"rel":180497},"https://www.youtube.com/watch?v=EZBHAwuQl_U",[816],"Devnexus 2023 - Kontain Your Spring - Craig Walls",[5332,180500,180501],{},[812,180502,180505],{"href":180503,"rel":180504},"https://www.youtube.com/watch?v=h7RhUVRxqYY",[816],"Devnexus 2023 - My Children Will Never Deploy Active-Passive - DaShaun Carter",[5332,180507,180508],{},[812,180509,180512],{"href":180510,"rel":180511},"https://www.youtube.com/watch?v=98cwiZOhoOM",[816],"Devnexus 2023 - Bootiful Spring Boot 3 - Josh Long",[5332,180514,180515],{},[812,180516,180519],{"href":180517,"rel":180518},"https://www.youtube.com/watch?v=zoAG1oatOS0",[816],"Devnexus 2023 - Live Diagramming of Knative - Core Concepts - Whitney Lee",[4542,180521,180523],{"id":180522},"springone-at-vmware-explore","SpringOne at VMware Explore",[651,180525,180526],{},"Registration is now open for SpringOne at VMware explore. I’’m hoping you join me at one of the best conferences of the year as we talk everything Spring. I am going to be a busy Spring bee there and I can’t wait to tell you more about what I’ll be doing as we get closer.",[651,180528,180529],{},[812,180530,82111],{"href":82111,"rel":180531},[816],[4542,180533,177889],{"id":157573},[5909,180535,164959],{"id":69848},[5316,180537,180538,180547,180560],{},[5332,180539,180540,180541,180546],{},"This is a really great introduction to ",[812,180542,180545],{"href":180543,"rel":180544},"https://www.atomicjar.com/2023/05/spring-boot-3-1-0-testcontainers-for-testing-and-local-development/",[816],"Spring Boot Application Testing and Development with Testcontainers",". If you didn’t hear Spring Boot 3.1 was released and in that release we adde support for using Testcontainers at development. This article does a great job of diving into this new feature.",[5332,180548,180549,180550,180554,180555],{},"Vue 3.3 has been released and in ",[812,180551,118932],{"href":180552,"rel":180553},"https://blog.vuejs.org/posts/vue-3-3",[816]," you will learn all about it.This release is focused on developer experience improvements - in particular, SFC ",[2939,180556,180557,180559],{},[47668,180558],{"setup":674}," usage with TypeScript.",[2939,180561],{},[2939,180563,180564,180566,180576,180578,180585,180591,180593,180598],{},[5909,180565,164971],{"id":157591},[5316,180567,180568],{},[5332,180569,178347,180570,180575],{},[812,180571,180574],{"href":180572,"rel":180573},"https://www.youtube.com/watch?v=jnNQEiPs5r0",[816],"this video by Colin and Samir"," where they interviewed the Deep fake Tom Cruise. I thought it was really great insight into who Miles Fisher is and I honestly can’t remember walking away from an interview pulling for a person more than I have here with Miles. He is funny, creative, super talented and I can’t wait to see what he does next.",[5909,180577,178573],{"id":95961},[651,180579,180580,180581,180584],{},"I’m getting really excited for KCDC next month! If you haven’t registered yet you can do so using the code ",[2939,180582,180583],{},"FriendOfDanVega"," for an extra 10% off. I hope to see you there!",[651,180586,180587],{},[812,180588,180589],{"href":180589,"rel":180590},"https://twitter.com/therealdanvega/status/1659182193762082817",[816],[4542,180592,157704],{"id":157703},[651,180594,178368,180595,166271],{},[812,180596,41499],{"href":44086,"rel":180597},[816],[651,180599,41105,180600,69920,180602,180604,180606,180608],{},[41107,180601],{},[41107,180603],{},[812,180605,161560],{"href":161111},[41107,180607],{},[812,180609,53869],{"href":53869,"rel":180610},[816],{"title":674,"searchDepth":790,"depth":790,"links":180612},[180613,180614,180617,180618,180619,180620,180625],{"id":180406,"depth":790,"text":180407},{"id":61224,"depth":790,"text":15432,"children":180615},[180616],{"id":180448,"depth":892,"text":180449},{"id":180218,"depth":790,"text":180219},{"id":180472,"depth":790,"text":180473},{"id":180522,"depth":790,"text":180523},{"id":157573,"depth":790,"text":177889,"children":180621},[180622,180623,180624],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":180627,"date":180628},"spring-boot-3-1","2023-05-22T08:00:00.000Z","/newsletter/2023/05/22/spring-boot-3-1",{"title":180380,"description":180385},"newsletter/2023/05/22/spring-boot-3-1","kl8tTvnApITlwA3UEHKaYlZE2q3_J6j9hHSAP0udYNI",{"id":180634,"title":180635,"body":180636,"description":180640,"extension":793,"meta":180827,"navigation":797,"path":180829,"seo":180830,"stem":180831,"__hash__":180832},"content/newsletter/2023/06/12/pnc-developer-days.md","Virtual Threads, Tales from the JAR Side and a quick trip to Pittsburgh",{"type":648,"value":180637,"toc":180811},[180638,180641,180644,180650,180653,180656,180659,180661,180665,180668,180671,180674,180678,180681,180684,180688,180691,180694,180700,180706,180711,180713,180716,180721,180725,180728,180730,180732,180754,180756,180772,180774,180780,180782,180785,180791,180793,180798],[651,180639,180640],{},"Happy Monday and welcome to another edition of the newsletter. It’s been a few weeks since we last connected and I apologize for that but I have been a busy bee 🐝 June is going to be a pretty busy travel month for me and I hope I get the chance to see some of you on the road this year.",[651,180642,180643],{},"In a previous newsletter I told you that I had taken up golf and my first round ever was played at a beautiful course in Hilton Head SC. Since then I have played 2 more rounds of 18 here in Ohio. I absolutely love the game and love being outside and I’m having fun picking it up. Here is a quick picture my wife took of me heading to the range to try out my new clubs.",[651,180645,180646],{},[660,180647],{"alt":180648,"src":180649},"Dan's new clubs","/images/newsletter/2023/06/12/dan-new-golf-clubs.jpeg",[4542,180651,180652],{"id":179269},"Project Loom / Virtual Threads",[651,180654,180655],{},"Virtual Threads have been finalized for JDK 21, and there is a lot of excitement building for this long-anticipated feature. Last week, I gave a virtual presentation titled \"Embracing Virtual Threads in Spring Applications\". It was a short, high-level overview, but it was a lot of fun. I’m also spending some time on the subject in a bunch of talks around Spring Boot 3 and Beyond!",[651,180657,180658],{},"I'm really excited about JDK 21 and Spring Framework 6.1 because I think they will bring some really nice improvements to certain types of applications with little to no code changes. If you're interested in learning more, I did a similar video on this back in April.",[5988,180660],{"id":179835},[4542,180662,180664],{"id":180663},"tales-from-the-jar-side","Tales from the JAR Side",[651,180666,180667],{},"I recently went on a live stream with my friend Ken Kousen. If you're part of the Java ecosystem, you probably already know Ken. However, in case you don't, he's an author, speaker, trainer, Java Champion, and an all-around great guy. During our conversation, we talked about all things Java, Spring, YouTube, and whatever else came up.",[651,180669,180670],{},"If you have the chance, I highly recommend watching the stream. Additionally, please do me a favor and subscribe to Ken's YouTube channel. He's doing an excellent job creating content, and you won't want to miss out on any more of it.",[5988,180672],{"id":180673},"UhCIW3PQHcI",[4542,180675,180677],{"id":180676},"hello-pittsburgh","Hello, Pittsburgh!",[651,180679,180680],{},"Last week I traveled to Pittsburgh on what would be a really short trip. I wish I had more time to spend there and I was really hoping to get in a run or catch a Pirates game but sadly I just didn’t have the time, maybe next time.",[651,180682,180683],{},"First up I gave a presentation titled Spring Boot 3 and Beyond to over 250 developers at FedEx Ground headquarters. This was exciting for me because I spent some time working for FedEx (Express) in a consultant role as an Enterprise Architect. I was familiar with the company and how much they have embraced Java and Spring.",[5909,180685,180687],{"id":180686},"pnc-developer-days","PNC Developer Days",[651,180689,180690],{},"Next up was PNC Bank’s Developer days. I absolutely love it when a company puts this much thought and work into putting together a conference for their developers. There were a ton of great sessions and demos where employees showed off new features and tools that they could use in their day to day workflows.",[651,180692,180693],{},"I had a chance to work our booth that was there where we helping to spread the word of VMware Tanzu and how our suite of products help modern application development workflows. PNC is a big user of Java and Spring so I had the opportunity to talk to everyone from interns to experienced developers and answered a bunch of really great questions. A+ job PNC and I hope to back for another developer day.",[651,180695,180696],{},[660,180697],{"alt":180698,"src":180699},"PNC Balloons","/images/newsletter/2023/06/12/pnc-balloons.jpeg",[651,180701,180702],{},[660,180703],{"alt":180704,"src":180705},"VMware Booth","/images/newsletter/2023/06/12/vmware-booth-01.png",[651,180707,180708],{},[660,180709],{"alt":180704,"src":180710},"/images/newsletter/2023/06/12/vmware-booth-02.png",[4542,180712,180523],{"id":180522},[651,180714,180715],{},"Registration is now open for SpringOne at VMware explore. I invite you to join me at one of the best conferences of the year, where we'll discuss everything Spring. We have some exciting plans for this year's event, and I'm getting really excited about it. If you're curious about the agenda, it has been posted and you can check it out now. I will be giving a talk on GraphQL and Spring Recipes with my friend Nate Schutta.",[651,180717,180718],{},[812,180719,82111],{"href":82111,"rel":180720},[816],[4542,180722,180724],{"id":180723},"up-next","Up Next",[651,180726,180727],{},"Someone asked me recently if I had quit YouTube. It's only been two weeks, so please give me a little more space! 😅 I have a good queue of videos that I want to record, and my plan is to get two of them done this week. I'm also preparing for KCDC next week, where I will be giving a workshop on Getting Started with Spring and a breakout session on Spring Boot 3.",[4542,180729,177889],{"id":157573},[5909,180731,164959],{"id":69848},[5316,180733,180734,180741,180748],{},[5332,180735,180736],{},[812,180737,180740],{"href":180738,"rel":180739},"https://dev.to/jacobandrewsky/improving-web-performance-with-lazy-pattern-3eia",[816],"Improve performance with Lazy Pattern",[5332,180742,180743],{},[812,180744,180747],{"href":180745,"rel":180746},"https://spring.io/blog/2023/05/24/spring-authorization-server-is-on-spring-initializr",[816],"Spring Authorization Server is now on Spring Initializr",[5332,180749,180750],{},[812,180751,180752],{"href":180752,"rel":180753},"https://blog.jetbrains.com/idea/2023/06/intellij-idea-2023-2-eap-3/",[816],[5909,180755,164971],{"id":157591},[5316,180757,180758,180765],{},[5332,180759,180760],{},[812,180761,180764],{"href":180762,"rel":180763},"https://www.youtube.com/watch?v=J8nbBiAnI6A",[816],"Bootiful Spring Boot 3 by Josh Long",[5332,180766,180767],{},[812,180768,180771],{"href":180769,"rel":180770},"https://www.youtube.com/watch?v=IgmeFeTU1a4",[816],"Spring I/O 2023 Keynote",[5909,180773,165017],{"id":165016},[651,180775,180776,180777,664],{},"“Success isn't always about greatness. It's about consistency. Consistent hard work leads to success. Greatness will come.” – ",[2939,180778,180779],{},"Dwayne Johnson",[5909,180781,178573],{"id":95961},[651,180783,180784],{},"I have been working on something that should be coming to a monitor near you very soon!",[651,180786,180787],{},[812,180788,180789],{"href":180789,"rel":180790},"https://twitter.com/therealdanvega/status/1667235956850565153",[816],[4542,180792,157704],{"id":157703},[651,180794,178368,180795,166271],{},[812,180796,41499],{"href":44086,"rel":180797},[816],[651,180799,41105,180800,69920,180802,180804,180806,180808],{},[41107,180801],{},[41107,180803],{},[812,180805,161560],{"href":161111},[41107,180807],{},[812,180809,53869],{"href":53869,"rel":180810},[816],{"title":674,"searchDepth":790,"depth":790,"links":180812},[180813,180814,180815,180818,180819,180820,180826],{"id":179269,"depth":790,"text":180652},{"id":180663,"depth":790,"text":180664},{"id":180676,"depth":790,"text":180677,"children":180816},[180817],{"id":180686,"depth":892,"text":180687},{"id":180522,"depth":790,"text":180523},{"id":180723,"depth":790,"text":180724},{"id":157573,"depth":790,"text":177889,"children":180821},[180822,180823,180824,180825],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":180686,"date":180828},"2023-06-12T08:00:00.000Z","/newsletter/2023/06/12/pnc-developer-days",{"title":180635,"description":180640},"newsletter/2023/06/12/pnc-developer-days","1X_U0sumeP7dxHywNkJUliz9gNnGRtzcNkW2cO1vIfs",{"id":180834,"title":180835,"body":180836,"description":180840,"extension":793,"meta":180977,"navigation":797,"path":180980,"seo":180981,"stem":180982,"__hash__":180983},"content/newsletter/2023/06/19/http-interfaces.md","HTTP Interfaces, Building a portfolio with Nuxt 3 and KCDC!",{"type":648,"value":180837,"toc":180962},[180838,180841,180844,180847,180850,180853,180856,180859,180863,180866,180869,180871,180874,180877,180879,180881,180886,180888,180890,180897,180899,180908,180910,180919,180921,180930,180932,180934,180937,180942,180944,180949],[651,180839,180840],{},"Happy Monday and welcome to another edition of the newsletter. Today is Monday, June 19th, also known as Juneteenth, a day to celebrate freedom!",[651,180842,180843],{},"Tomorrow, I will be heading to KCDC, where I will deliver a 4-hour workshop on Getting Started with Spring and a breakout session on Spring Boot 3 and Beyond. If you're attending the conference, please say hello 👋🏻.",[4542,180845,180846],{"id":119297},"HTTP Interface Clients",[651,180848,180849],{},"In last week's newsletter, I mentioned that I recently gave a presentation on Spring Boot 3. During that talk, I received some excellent questions about HTTP interfaces in Spring. This prompted me to do a deeper dive and answer those questions. From this effort, I was able to record, edit, and publish two videos on the topic last week.",[651,180851,180852],{},"Since then, I have received a significant number of questions, which tells me that you are very interested in the topic and how it works. I will do my best to answer them as they come in, but for now, enjoy these tutorials.",[5988,180854],{"id":180855},"4U0hUyktpvg",[5988,180857],{"id":180858},"AOJzm7yFOl0",[4542,180860,180862],{"id":180861},"netninja-series","NetNinja Series",[651,180864,180865],{},"I recently recorded a new series on how to build a portfolio site with Nuxt 3. I partnered with the NetNinja who has a really great YouTube channel and you can find the first video in that series below. This was an exciting project because you get to build a really cool portfolio using a variety of technologies like Nuxt 3, Vue 3, Tailwind CSS, GraphQL and Nuxt Content. I hope you enjoy this and I would love to hear what you think of it.",[5988,180867],{"id":180868},"b6b2yZZNG6Y",[4542,180870,97345],{"id":97344},[651,180872,180873],{},"In this week's Spring Office Hours Podcast, DaShaun and I discussed a variety of topics, including a great discussion about virtual threads. I’m starting to get really excited about JDK 21 and Spring Framework 6.1 both of which will be released later this year.",[5988,180875],{"id":180876},"6QeYgSpntpg",[4542,180878,180523],{"id":180522},[651,180880,180715],{},[651,180882,180883],{},[812,180884,82111],{"href":82111,"rel":180885},[816],[4542,180887,177889],{"id":157573},[5909,180889,164959],{"id":69848},[651,180891,178347,180892,180896],{},[812,180893,109096],{"href":180894,"rel":180895},"https://spring.io/blog/2023/06/19/spring-boot-31-connectiondetails-abstraction",[816]," on Spring Boot 3.1’s ConnectionDetails abstraction. In this article Moritz Halbritter breaks down this new feature and how its powering the new Docker Compose and Testcontainers Dev support in Spring Boot 3.1.",[5909,180898,164971],{"id":157591},[651,180900,180901,180902,180907],{},"I had a lot of fun listening to 2 of my friends chop it up on ",[812,180903,180906],{"href":180904,"rel":180905},"https://www.youtube.com/watch?v=wQJjRbS1pKI",[816],"Tales from the JAR Side with Nate Schutta",". I really enjoyed Ken digging into Nate’s story on speaking how he came up with his style of presentations.",[5909,180909,164983],{"id":39439},[651,180911,180912,180913,180918],{},"I’m a big fan of the Working code podcast and not just because I happen to know all of the hosts 🤩 In this episode Ben details his process as he is ",[812,180914,180917],{"href":180915,"rel":180916},"https://workingcode.dev/episodes/131-starting-from-scratch/",[816],"starting from scratch"," when it comes to building out his new fitness application.",[5909,180920,167862],{"id":160019},[5316,180922,180923],{},[5332,180924,180925],{},[812,180926,180929],{"href":180927,"rel":180928},"https://spring.io/blog/2023/06/13/this-week-in-spring-june-13th-2023",[816],"This Week in Spring - June 13th, 2023",[5909,180931,165017],{"id":165016},[5909,180933,178573],{"id":95961},[651,180935,180936],{},"“Unfortunately, there seems to be far more opportunity out there than ability.... We should remember that good fortune often happens when opportunity meets with preparation.”",[651,180938,180939],{},[2939,180940,180941],{},"Thomas A. Edison",[4542,180943,157704],{"id":157703},[651,180945,178368,180946,166271],{},[812,180947,41499],{"href":44086,"rel":180948},[816],[651,180950,41105,180951,69920,180953,180955,180957,180959],{},[41107,180952],{},[41107,180954],{},[812,180956,161560],{"href":161111},[41107,180958],{},[812,180960,53869],{"href":53869,"rel":180961},[816],{"title":674,"searchDepth":790,"depth":790,"links":180963},[180964,180965,180966,180967,180968,180976],{"id":119297,"depth":790,"text":180846},{"id":180861,"depth":790,"text":180862},{"id":97344,"depth":790,"text":97345},{"id":180522,"depth":790,"text":180523},{"id":157573,"depth":790,"text":177889,"children":180969},[180970,180971,180972,180973,180974,180975],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":160019,"depth":892,"text":167862},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":180978,"date":180979},"http-interfaces","2023-06-19T14:00:00.000Z","/newsletter/2023/06/19/http-interfaces",{"title":180835,"description":180840},"newsletter/2023/06/19/http-interfaces","UCJlgQegD2SRtwVnS_rttIW0XlODSaSB5xWN-Pqr8U4",{"id":180985,"title":180986,"body":180987,"description":180991,"extension":793,"meta":181165,"navigation":797,"path":181168,"seo":181169,"stem":181170,"__hash__":181171},"content/newsletter/2023/06/26/kcdc-2023.md","KCDC 2023 Recap!",{"type":648,"value":180988,"toc":181156},[180989,180992,180998,181006,181012,181015,181021,181027,181030,181036,181042,181048,181051,181057,181059,181062,181065,181067,181069,181074,181076,181078,181081,181125,181127,181130,181136,181138,181143],[651,180990,180991],{},"Happy Monday and welcome to another edition of the newsletter. I spent last week in Kansas City for the Kansas City Developers Conference (KCDC) and wow was it a great week. First off I want to thank all of the conference organizers for putting on another amazing conference. They do an amazing job of putting together a really great schedule of presentations and I was honored to have 2 talks there. The swag from the conference was really good my only wish was that the sponsors had more t-shirts because selfishly I would like them for streams / videos.",[651,180993,180994],{},[660,180995],{"alt":180996,"src":180997},"EAF0E12D-511C-41F9-B665-8217A8F4BFBE.jpeg","/images/newsletter/2023/06/26/EAF0E12D-511C-41F9-B665-8217A8F4BFBE.jpeg",[651,180999,181000,181001,181005],{},"First up on Wednesday was a 4 hour Intro to Spring Workshop and this was the 2nd time DaShaun and i gave this one. We learned a lot from the first iteration and I think we made some really nice improvements. I think there were some things we can definitely improve on for the next one but overall I was really happy with how this one turned out. We had a really engaged audience and they asked some really great questions. One attendee (Kevin, up front) even said he just started watching my ",[812,181002,181004],{"href":120237,"rel":181003},[816],"4 hour intro to Spring Crash Course"," on YouTube and was excited to find out I was teaching this workshop.",[651,181007,181008],{},[660,181009],{"alt":181010,"src":181011},"032C876B-E4BF-413D-B63F-E8A1A3F4F1E8.jpeg","/images/newsletter/2023/06/26/032C876B-E4BF-413D-B63F-E8A1A3F4F1E8.jpeg",[651,181013,181014],{},"Wednesday night was the speaker dinner and it was great to see so many friendly and familiar faces. The venue was really cool spanning 3 levels and on the top level was some delicious BBQ.",[651,181016,181017],{},[660,181018],{"alt":181019,"src":181020},"F42D198E-0EC4-451D-9A14-3EBEFDFC07DD.jpeg","/images/newsletter/2023/06/26/F42D198E-0EC4-451D-9A14-3EBEFDFC07DD.jpeg",[651,181022,181023],{},[660,181024],{"alt":181025,"src":181026},"F2F89EE3-FF7F-42B7-80AA-2C710CE5FF8A.jpeg","/images/newsletter/2023/06/26/F2F89EE3-FF7F-42B7-80AA-2C710CE5FF8A.jpeg",[651,181028,181029],{},"On Thursday I gave my Spring Boot 3 and Beyond talk. In this presentation I covered Spring Boot 3.0, 3.1 and a bonus discussion on Virtual Threads support in Spring Boot 3.2. The room was packed and again a very engaged audience. I was brave and decided on live coding for about 20 minutes to show off some cool features and the demo gods were smiling down on me as everything just worked. Speaking of live coding at conferences what is with the podiums that they give us that have us slouching over with arms at a weird angle to write code with 🤦♂️ I found a drink table outside of the room and ended up bringing it in so that I had something usable to code on.",[651,181031,181032],{},[660,181033],{"alt":181034,"src":181035},"44928EAB-EDD8-44C7-8C06-01FD8B1031A4.jpeg","/images/newsletter/2023/06/26/44928EAB-EDD8-44C7-8C06-01FD8B1031A4.jpeg",[651,181037,181038],{},[660,181039],{"alt":181040,"src":181041},"83C6F292-D692-49F7-B601-2A182129AB3D.jpeg","/images/newsletter/2023/06/26/83C6F292-D692-49F7-B601-2A182129AB3D.jpeg",[651,181043,181044],{},[660,181045],{"alt":181046,"src":181047},"8A51099A-CA18-438A-BAD9-6EAB655C9564.jpeg","/images/newsletter/2023/06/26/8A51099A-CA18-438A-BAD9-6EAB655C9564.jpeg",[651,181049,181050],{},"Finally I want to point out that the Kansas City Airport (MCI) was brand new and It is really nice. It is so great that when I got off the plane I had a moment where I questioned if I was in the right city or not. Thanks for having me KCDC and I hope to back next year!",[651,181052,181053],{},[660,181054],{"alt":181055,"src":181056},"885C16CA-E7D5-4742-8C4D-EBDBEE2FB64A.jpeg","KCDC%20430907aa9497432f86fa37a60f5593e5/885C16CA-E7D5-4742-8C4D-EBDBEE2FB64A.jpeg",[4542,181058,180664],{"id":180663},[651,181060,181061],{},"My friend Ken Kousen created a video about the videos I created on HTTP Interfaces. I thought this was an awesome video and Ken had fun with it and added some value on top of what I did. If you haven’t subscribed to Ken’s YouTube channel yet, what are you waiting for?",[5988,181063],{"id":181064},"6ayPzBknMpA",[4542,181066,180523],{"id":180522},[651,181068,180715],{},[651,181070,181071],{},[812,181072,82111],{"href":82111,"rel":181073},[816],[4542,181075,177889],{"id":157573},[5909,181077,164971],{"id":157591},[651,181079,181080],{},"The videos for my series on the NetNinja channel on building a portfolio site with Nuxt / Tailwind CSS / GraphQL have all been released. I really had a lot of fun putting this together and I hope you enjoy it",[5316,181082,181083,181090,181097,181104,181111,181118],{},[5332,181084,181085],{},[812,181086,181089],{"href":181087,"rel":181088},"https://www.youtube.com/watch?v=b6b2yZZNG6Y&t=1s",[816],"Portfolio Build with Nuxt Content & GraphQL - Part 1",[5332,181091,181092],{},[812,181093,181096],{"href":181094,"rel":181095},"https://www.youtube.com/watch?v=eXSVyfBLtnY&t=1s",[816],"Portfolio Build with Nuxt Content & GraphQL - Part 2",[5332,181098,181099],{},[812,181100,181103],{"href":181101,"rel":181102},"https://www.youtube.com/watch?v=oh2KQqtJLhA&t=136s",[816],"Portfolio Build with Nuxt Content & GraphQL - Part 3",[5332,181105,181106],{},[812,181107,181110],{"href":181108,"rel":181109},"https://www.youtube.com/watch?v=GXgWQN_NJnA&t=259s",[816],"Portfolio Build with Nuxt Content & GraphQL - Part 4",[5332,181112,181113],{},[812,181114,181117],{"href":181115,"rel":181116},"https://www.youtube.com/watch?v=Vh357OVNSlo&t=373s",[816],"Portfolio Build with Nuxt Content & GraphQL - Part 5",[5332,181119,181120],{},[812,181121,181124],{"href":181122,"rel":181123},"https://www.youtube.com/watch?v=R0hAI0qUvmk&t=9s",[816],"Portfolio Build with Nuxt Content & GraphQL - Part 6",[5909,181126,178573],{"id":95961},[651,181128,181129],{},"I can’t believe Google Domains is being sold off to Squarespace 🤦♂️",[651,181131,181132],{},[812,181133,181134],{"href":181134,"rel":181135},"https://twitter.com/therealdanvega/status/1669493112794759169",[816],[4542,181137,157704],{"id":157703},[651,181139,178368,181140,166271],{},[812,181141,41499],{"href":44086,"rel":181142},[816],[651,181144,41105,181145,69920,181147,181149,181151,181153],{},[41107,181146],{},[41107,181148],{},[812,181150,161560],{"href":161111},[41107,181152],{},[812,181154,53869],{"href":53869,"rel":181155},[816],{"title":674,"searchDepth":790,"depth":790,"links":181157},[181158,181159,181160,181164],{"id":180663,"depth":790,"text":180664},{"id":180522,"depth":790,"text":180523},{"id":157573,"depth":790,"text":177889,"children":181161},[181162,181163],{"id":157591,"depth":892,"text":164971},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":181166,"date":181167},"kcdc-2023","2023-06-26T08:00:00.000Z","/newsletter/2023/06/26/kcdc-2023",{"title":180986,"description":180991},"newsletter/2023/06/26/kcdc-2023","X-tGt3mb4loiTS52m5VPA2kscmnVfrWllCXlxqLWiNM",{"id":181173,"title":181174,"body":181175,"description":181179,"extension":793,"meta":181332,"navigation":797,"path":181335,"seo":181336,"stem":181337,"__hash__":181338},"content/newsletter/2023/07/03/spring-boot-3-aws-lambda.md","Spring Boot 3 on AWS Lambda, Spring Cloud Gateway and Oracle GraalVM",{"type":648,"value":181176,"toc":181320},[181177,181180,181184,181187,181190,181193,181202,181205,181209,181212,181221,181224,181228,181246,181251,181253,181255,181271,181273,181284,181286,181289,181291,181294,181300,181302,181307],[651,181178,181179],{},"Happy Monday and welcome to another edition of the newsletter. Tomorrow is the 4th of July, also known as Independence Day, which is a federal holiday in the United States commemorating the Declaration of Independence. This happens to be a family favorite holiday in our household that kicks with a kids bike parade behind a fire truck in our neighborhood. If you’re celebrating I hope you have an amazing holiday spent with family and friends. In this edition of the newsletter I want to talk to you about deploying Spring Boot 3 applications on AWS Lambda, A quick trip to Detroit and Oracle GraalVM.",[4542,181181,181183],{"id":181182},"spring-boot-3-on-aws-lambda","Spring Boot 3 on AWS Lambda",[651,181185,181186],{},"When it comes to deploying frontend applications, there are many great options available, whether you're working on a personal project or just trying to get something up and running. While Java is the 800 lb gorilla in the room and solves problems for some of the biggest enterprises in the world, it may not be the best choice for personal or weekend hacking projects. Recently, I've been exploring AWS Lambda as a solution to this problem, and it turns out to be a really great one.",[651,181188,181189],{},"When you manage your own server, you have to think about things like patching, security, and scaling. When you deploy an application to a server, you have to consider how much traffic your application will receive. If your server is running at 100% utilization, you might need to scale up to handle the load. If it's running at 10% utilization, you might want to scale down to save money.",[651,181191,181192],{},"AWS Lambda is a serverless compute service that lets you run code without provisioning or managing servers. You pay only for the compute time you consume. This means that my personal project, which won't receive a ton of traffic, is an ideal candidate for AWS Lambda. Any applications with variable traffic or that can scale to zero are great candidates for this use case.",[651,181194,181195,181196,181201],{},"I have looked at the concept for deploying functions as a service (FaaS) but in this tutorial we are going to deploy an entire Spring Boot REST API. If you look at the ",[812,181197,181200],{"href":181198,"rel":181199},"https://aws.amazon.com/lambda/pricing/",[816],"AWS Pricing Page"," you will see that The AWS Lambda free tier includes one million free requests per month and 400,000 GB-seconds of compute time per month. This means that for most of the side projects I am working on I can deploy them to AWS Lambda and not have to pay anything.",[5988,181203],{"id":181204},"GGPPkUcHleQ",[4542,181206,181208],{"id":181207},"spring-cloud-gateway","Spring Cloud Gateway",[651,181210,181211],{},"Last week I had a quick trip to Detroit for a SpringOne Tour stop. This was my first time staying in Downtown Detroit and I have to say that I really enjoyed my stay. I love running in new cities and usually part of my judgement on your city is how friendly of a walking/running area you have. I took in some great sights like all 3 stadiums downtown and the river walk where I was able to wave hi 👋🏻 to our friends in Canada.",[651,181213,181214,181215,181220],{},"After day 1 of presentations DaShaun and and sat down to record a new episode of the Spring Office Hours Podcast. We were lucky enough to have ",[812,181216,181219],{"href":181217,"rel":181218},"https://twitter.com/spencerbgibb",[816],"Spencer Gibb"," in Detroit so we decided to sit down and talk to him about all things Spring Cloud + Spring Cloud Gateway. I thought this was a really great level setting conversation on Spring Cloud and some of the use cases for an API Gateway. I want to create some tutorials around Spring Cloud Gateway but would love to hear from you. Is that something you would like to see? What problems are you facing?",[5988,181222],{"id":181223},"eq1aO9IpEIM",[4542,181225,181227],{"id":181226},"oracle-graalvm","Oracle GraalVM",[651,181229,181230,181231,181236,181237,51389,181242,664],{},"If you haven't heard, GraalVM is now Oracle GraalVM, and there are a few exciting things that come with ",[812,181232,181235],{"href":181233,"rel":181234},"https://medium.com/graalvm/a-new-graalvm-release-and-new-free-license-4aab483692f5",[816],"this announcement",". The first thing to note is that Oracle GraalVM now supports JDK 17 and 20, and the best part is that it's free. Another update is that if you're using it to generate native images, you no longer need to install a separate tool for that, as it now comes with it. If you want to download it you can do from the ",[812,181238,181241],{"href":181239,"rel":181240},"https://www.graalvm.org/downloads/",[816],"Oracle GraalVM site",[812,181243,181245],{"href":121585,"rel":181244},[816],"SDK Man",[651,181247,181248],{},[660,181249],{"alt":181227,"src":181250},"/images/newsletter/2023/07/03/oracle_graalvm.png",[4542,181252,177889],{"id":157573},[5909,181254,164959],{"id":69848},[5316,181256,181257],{},[5332,181258,181259,181260,181265,181266,664],{},"There were a lot of exciting announcements from YouTube last week during VidCon. The first was a ",[812,181261,181264],{"href":181262,"rel":181263},"https://techcrunch.com/2023/06/23/youtube-integrates-ai-powered-dubbing-tool/",[816],"new tool"," that could dub your videos into other languages using AI. The next tool was something that creators have been asking for for as long as I can remember. YouTube is now beta testing a tool to ",[812,181267,181270],{"href":181268,"rel":181269},"https://www.theverge.com/2023/6/23/23771045/youtube-test-and-compare-a-b-testing-thumbnails-feature",[816],"A/B test your thumbnails",[5909,181272,164971],{"id":157591},[5316,181274,181275],{},[5332,181276,181277,181278,181283],{},"While we are talking about Spring Cloud Gateway I really enjoyed this video from Spring I/O 2023 by Iván López on ",[812,181279,181282],{"href":181280,"rel":181281},"https://www.youtube.com/watch?v=NjqgXzCSu7M",[816],"Dynamic OpenAPIs with Spring Cloud Gateway",". I thought he did a really good job of introducing us to API gateways and some of the use cases.",[5909,181285,165017],{"id":165016},[651,181287,181288],{},"“We are what we repeatedly do. Excellence, then, is not an act, but a habit.”",[5909,181290,178573],{"id":95961},[651,181292,181293],{},"Marcus Hellberg had an interesting tweet about some of the interesting trends in the Java Ecosystem. No surprise to see some of my favorite topics on there like Spring Boot, Serverless and GraalVM.",[651,181295,181296],{},[812,181297,181298],{"href":181298,"rel":181299},"https://twitter.com/marcushellberg/status/1674458944255959041",[816],[4542,181301,157704],{"id":157703},[651,181303,178368,181304,166271],{},[812,181305,41499],{"href":44086,"rel":181306},[816],[651,181308,41105,181309,69920,181311,181313,181315,181317],{},[41107,181310],{},[41107,181312],{},[812,181314,161560],{"href":161111},[41107,181316],{},[812,181318,53869],{"href":53869,"rel":181319},[816],{"title":674,"searchDepth":790,"depth":790,"links":181321},[181322,181323,181324,181325,181331],{"id":181182,"depth":790,"text":181183},{"id":181207,"depth":790,"text":181208},{"id":181226,"depth":790,"text":181227},{"id":157573,"depth":790,"text":177889,"children":181326},[181327,181328,181329,181330],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":181333,"date":181334},"spring-boot-3-aws-lambda","2023-07-03T08:00:00.000Z","/newsletter/2023/07/03/spring-boot-3-aws-lambda",{"title":181174,"description":181179},"newsletter/2023/07/03/spring-boot-3-aws-lambda","92jXJFPCspJUZVIm-6Tq0jHYqyROy6hQn7rvc-ib3Wk",{"id":181340,"title":181341,"body":181342,"description":181346,"extension":793,"meta":181476,"navigation":797,"path":181479,"seo":181480,"stem":181481,"__hash__":181482},"content/newsletter/2023/07/17/spring-one-tour-austin.md","Hello Austin, Spring for GraphQL and the schedule for SpringOne is now live!",{"type":648,"value":181343,"toc":181465},[181344,181347,181350,181353,181359,181365,181367,181370,181373,181380,181383,181392,181395,181398,181403,181405,181407,181417,181419,181428,181430,181433,181435,181438,181444,181446,181451],[651,181345,181346],{},"Happy Monday and welcome to another edition of the newsletter! In this issue, I'll share my quick trip to Austin, new features in Spring for GraphQL, and updates about SpringOne at VMware Explore.",[651,181348,181349],{},"Last week, I visited Austin, TX for a SpringOne Tour stop at a customer's location. As I started packing for the trip, I realized it was going to be 100 degrees all week. Though I packed some clothes to run with, it was still too hot even at 6 AM 🥵.",[651,181351,181352],{},"We had a great team dinner at Vince Young's Steakhouse downtown, which was about a 20-minute drive from where I was staying in the Domain. The Domain is a really cool area with all kinds of shops, and I saw a lot of big tech companies in the area. I would love to go back there one day and spend some time with my family. I was also really close to Top Golf and spent some time hanging out with my coworkers.",[651,181354,181355],{},[660,181356],{"alt":181357,"src":181358},"Top Golf Austin","/images/newsletter/2023/07/17/top-golf.jpeg",[651,181360,181361],{},[660,181362],{"alt":181363,"src":181364},"SpringOne Tour Austin","/images/newsletter/2023/07/17/spring-one-tour-austin.jpeg",[4542,181366,86347],{"id":86782},[651,181368,181369],{},"Last week, I spent some time exploring the new features in Spring for GraphQL. I began by examining the new schema mapping inspection report, which can help you identify mapping issues between your schema and data fetchers. I recorded and published a video on this topic, which you can watch below.",[5988,181371],{"id":181372},"YBPG0JbHvpY",[651,181374,181375,181376,181379],{},"During the week, I invested a lot of time in exploring the new paging and sorting features added in Spring for GraphQL 1.2. Thanks to this feature, we now have a standard way of doing paging in GraphQL using the Cursor connection specification. I concluded the week by examining the observation support, which provides us with metrics and insights into the performance of our API. I recorded several videos about GraphQL and will be releasing at least two of them this week, so stay tuned. By the way, if you haven't subscribed to my ",[812,181377,101297],{"href":101295,"rel":181378},[816],", what are you waiting for?",[4542,181381,181382],{"id":180522},"SpringOne at VMWare Explore",[651,181384,181385,181386,181391],{},"SpringOne at VMware Explore is coming up and I couldn't be more excited for this conference! The ",[812,181387,181390],{"href":181388,"rel":181389},"https://springone.io/schedule",[816],"schedule"," was just released, and I can finally discuss everything that I am working on for it.",[651,181393,181394],{},"I have three talks scheduled, and I will also be working the booth throughout the week. Make sure to stop by and say hi! On day one, I will be giving a talk on Spring for GraphQL, and later that day, I'll be giving another talk with my good friend Nate Schutta on Spring Recipes. On day four, I will also be joining Nate to discuss Spring for Architects.",[651,181396,181397],{},"We are also planning a 5k run, so if you're interested in running with us, stay tuned for more information. Additionally, we are working on a live session of Spring Office Hours. It's going to be an exciting conference, and I can't wait to see all of you there. If you haven't registered yet, please do so now and join me and all of my friends for what is sure to be the conference of the year!",[651,181399,181400],{},[812,181401,82111],{"href":82111,"rel":181402},[816],[4542,181404,177889],{"id":157573},[5909,181406,164959],{"id":69848},[5316,181408,181409],{},[5332,181410,181411,181412,181416],{},"I don’t think there is much more to say than Bravo Oracle 👏🏻 Check out ",[812,181413,109096],{"href":181414,"rel":181415},"https://www.oracle.com/news/announcement/blog/keep-linux-open-and-free-2023-07-10/",[816]," about keeping Linux open and free.",[5909,181418,164971],{"id":157591},[5316,181420,181421],{},[5332,181422,180318,181423,181427],{},[812,181424,85142],{"href":181425,"rel":181426},"https://www.youtube.com/watch?v=ccj2-FsfnzE",[816]," by Mark Pollack, which showcases the features of the new Spring CLI. This experimental project is designed to help you create new Spring-based projects and add functionality after the application is already up and running.",[5909,181429,165017],{"id":165016},[651,181431,181432],{},"“The only person you are destined to become is the person you decide to be.” - Ralph Waldo Emerson",[5909,181434,178573],{"id":95961},[651,181436,181437],{},"I wrote a quick appreciation post about where I am in my career while I was finishing this newsletter last night and I thought I would share it with you.",[651,181439,181440],{},[812,181441,181442],{"href":181442,"rel":181443},"https://twitter.com/therealdanvega/status/1680762913651322880",[816],[4542,181445,157704],{"id":157703},[651,181447,178368,181448,166271],{},[812,181449,41499],{"href":44086,"rel":181450},[816],[651,181452,41105,181453,69920,181455,181457,181459,181461],{},[41107,181454],{},[41107,181456],{},[812,181458,161560],{"href":161111},[41107,181460],{},[812,181462,181463],{"href":181463,"rel":181464},"http://www.danvega.dev",[816],{"title":674,"searchDepth":790,"depth":790,"links":181466},[181467,181468,181469,181475],{"id":86782,"depth":790,"text":86347},{"id":180522,"depth":790,"text":181382},{"id":157573,"depth":790,"text":177889,"children":181470},[181471,181472,181473,181474],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":181477,"date":181478},"spring-one-tour-austin","2023-07-17T08:00:00.000Z","/newsletter/2023/07/17/spring-one-tour-austin",{"title":181341,"description":181346},"newsletter/2023/07/17/spring-one-tour-austin","Khx0aAvrfPmB_67CrnIYtogIS2HaPvbSF8luBqE3c7w",{"id":181484,"title":181485,"body":181486,"description":181490,"extension":793,"meta":181811,"navigation":797,"path":181814,"seo":181815,"stem":181816,"__hash__":181817},"content/newsletter/2023/07/24/content-creation-cycle.md","The weekly content cycle of stress and Spring Recipes on the O’Reilly Learning Platform",{"type":648,"value":181487,"toc":181793},[181488,181491,181500,181506,181510,181513,181516,181519,181522,181553,181556,181560,181569,181572,181575,181607,181612,181617,181620,181628,181631,181635,181640,181643,181646,181654,181658,181661,181664,181667,181670,181674,181677,181679,181682,181685,181687,181690,181694,181697,181700,181704,181707,181710,181714,181722,181728,181730,181735,181737,181739,181744,181746,181748,181758,181760,181768,181770,181773,181775,181780],[651,181489,181490],{},"Happy Monday and welcome to another edition of the newsletter. This week I want to talk to you about the current state of content creation for me, a couple of videos I worked on last week and a big announcement about SpringOne at VMware Explore!",[651,181492,181493,181494,181499],{},"Last week I received the photos that were taken at KCDC and wow were these some great action shots. Thanks to the talented ",[812,181495,181498],{"href":181496,"rel":181497},"https://tiffanybuckley.com/aboutme",[816],"Tiffany Buckley"," for the pictures and capturing my session on Spring Boot 3 and Beyond!",[651,181501,181502],{},[660,181503],{"alt":181504,"src":181505},"KCDC Photos","/images/newsletter/2023/07/24/kcdc.png",[4542,181507,181509],{"id":181508},"the-content-creation-machine","The Content Creation Machine",[651,181511,181512],{},"I want to take some time this week to discuss the current state of my content creation machine. I'm very fortunate to be where I am in my career and to love what I do every day. However, I also want to be open about the other side of content creation.",[651,181514,181515],{},"People often say that postal workers or couriers have stressful lives because their job never truly ends. Even if they work hard every week, the work just keeps piling up. While they may be rewarded for their hard work, that feeling of never being \"done\" must take a toll.",[651,181517,181518],{},"I have certainly felt this weight of never being done before, and it has been weighing on me even more lately. I'm a big believer in task lists and organizing both my personal and work life in lists. I use Notion for work to plan out my year, quarter, week, and days. I also use it to plan out what content I want to work on and use it to move from idea to production.",[651,181520,181521],{},"When it comes to content creation, there are several things I could be working on at any time:",[5316,181523,181524,181535,181537,181540,181543,181551],{},[5332,181525,181526,181527],{},"Code Demos (GitHub Repo)\n",[5316,181528,181529,181532],{},[5332,181530,181531],{},"A topic I'm learning about",[5332,181533,181534],{},"A README to explain what this repo is for",[5332,181536,67442],{},[5332,181538,181539],{},"Video",[5332,181541,181542],{},"Blog Post",[5332,181544,181545,181546],{},"Presentation\n",[5316,181547,181548],{},[5332,181549,181550],{},"Customer / Conference / Training",[5332,181552,120483],{},[651,181554,181555],{},"With so many mediums to work on, I need to establish a process for reusing the content I'm working on. Without such a process, I become scatterbrained and can't stop thinking about which of these mediums I'm neglecting and how to resolve that.",[5909,181557,181559],{"id":181558},"content-reuse","Content Reuse",[651,181561,181562,181563,181568],{},"I realize that one of the reasons I get so stressed out is because I have too many projects and mediums to focus on. If I can simplify and focus on a single piece of content, it might take away some of my stress. Let's take the example of a project I plan to work on soon: creating content around the new and exciting Spring CLI project that ",[812,181564,181567],{"href":181565,"rel":181566},"https://tanzu.vmware.com/developer/team/mark-pollack/",[816],"Mark Pollack"," is working on.",[651,181570,181571],{},"To start, I need to create a demo and understand the topic well enough to explain it to others. Once I have the code ready, I can create a GitHub repository to serve as a reference for this topic.",[651,181573,181574],{},"The repository's README is a great place to describe the tutorial and explain its purpose to anyone who comes across it. The README should include:",[5316,181576,181577,181595],{},[5332,181578,181579,181580],{},"About\n",[5316,181581,181582,181584],{},[5332,181583,55297],{},[5332,181585,181586,181587],{},"Description\n",[5316,181588,181589,181592],{},[5332,181590,181591],{},"Used for Blog Post Meta Data (Description)",[5332,181593,181594],{},"Used for YouTube Description",[5332,181596,181597,181598],{},"Links to (future)\n",[5316,181599,181600,181603,181605],{},[5332,181601,181602],{},"Blog post",[5332,181604,181539],{},[5332,181606,67442],{},[651,181608,181609],{},[2939,181610,181611],{},"YouTube Tutorial",[651,181613,181614],{},[660,181615],{"alt":181616,"src":181616},"https://images.unsplash.com/photo-1594394489098-74ac04c0fc2e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb",[651,181618,181619],{},"Now that I have code and copy for the tutorial, I can proceed to create a video tutorial. Although I do not script my videos, I like to write out an agenda. Here are the items I plan to cover in the intro, tutorial, and outro. I want to be clear about what viewers will gain from this tutorial so they have a clear understanding of the outcome.",[651,181621,181622,181623,664],{},"I hit the record button and begin capturing my screen and A-roll footage to teach you about the topic at hand. In an effort to grab your attention in the thumbnail, I also record myself in silly poses. Once the footage is captured, it's time to move to the editing floor and create the best video I can with the time I have. In a perfect world, I would have the money to hire someone with more time and skill to work on these videos. Lately, I've had the feeling that my videos have been stale and even ",[812,181624,181627],{"href":181625,"rel":181626},"https://twitter.com/therealdanvega/status/1681850663125303297",[816],"put out a tweet about it this week",[651,181629,181630],{},"After recording the video, I create a thumbnail and publish it on YouTube. Reusing the title and description we thought about earlier comes in handy, as I do not have to rewrite the same content.",[651,181632,181633],{},[2939,181634,181542],{},[651,181636,181637],{},[660,181638],{"alt":181639,"src":181639},"https://images.unsplash.com/photo-1499750310107-5fef28a66643?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb",[651,181641,181642],{},"When I first started blogging, it was easy to write for the blog since it was the only medium I had to focus on. However, with so many places to post content these days, it has become almost impossible for me to focus on writing new articles. Nevertheless, I have finally come to terms with this. I can express myself in written form through the newsletter, so writing articles is no longer an itch I need to scratch.",[651,181644,181645],{},"That being said, I still want to post articles. When I'm working on a new tutorial, like the Spring CLI one I'm currently working on, I still want to provide it in written form. Specifically, I want an article that gets picked up by search engines when people search for answers to their questions. When someone lands on this search result, I want to provide the video I have already worked on and include some form of the video in written form.",[651,181647,181648,181649,181653],{},"I previously tried posting the transcript from the video on the blog, but it did not work out well. Therefore, I now use an amazing service called ",[812,181650,97413],{"href":181651,"rel":181652},"https://www.danvega.dev/blog/2023/03/31/videotap/",[816]," that helps me turn a video into a blog post, and it works really well.",[651,181655,181656],{},[2939,181657,67442],{},[651,181659,181660],{},"Every week, I put together a newsletter that allows me to write without worrying about analytics. I don't care if it shows up in search rankings or how many views it gets. I write because I enjoy it.",[651,181662,181663],{},"At least, that's what I told myself when I started. These days, however, I have to wonder if I'm spending my time wisely. While I add notes to my newsletter throughout the week, I also end up spending a few hours on my only free weekend day, Sunday, to finish it, as I'm doing right now.",[651,181665,181666],{},"Now that I've put pressure on myself to deliver this newsletter each week, I want to see a reward for my hard work. Although my open rate is okay and there are ways to increase subscribers, I have to question if it's worth my time and effort to focus on these things.",[651,181668,181669],{},"The other part of this process is something that has been bothering me for awhile. Currently I write this newsletter in Notion and then export it to markdown. I pull that into my website and then fix up images, YouTube videos and more to fit within the framework of my site. After that has been pushed to production I need to export the markdown from Notion to ConvertKit which is the tool I use to send out this newsletter. I really need to find a way streamline this process and I think that will make it feel less like a chore.",[651,181671,181672],{},[2939,181673,177849],{},[651,181675,181676],{},"The combination of all the content above are the building blocks for my presentations. These are talks that I turn into for customers, conferences and workshops.",[5909,181678,9042],{"id":9041},[651,181680,181681],{},"I'm not sure if anyone will find this useful, but I wanted to get it all out. I love what I do every single day, but that doesn't mean it's stress-free. Without a plan, things will continue to be overwhelming. So, I'm trying to make some adjustments. I'll treat each new topic as a project, and each project will have a number of content pieces to create. Once I finish a project, I can move on to a new one instead of being stuck in a never-ending cycle of stressful content creation.",[651,181683,181684],{},"I also realize that I am a problem solver, and I need to spend some time solving the problems in my workflow. Doing so will save me time and reduce stress.",[4542,181686,171236],{"id":171235},[651,181688,181689],{},"Speaking of content here are a couple of videos I worked on last week:",[5909,181691,181693],{"id":181692},"pgadmin-spring-boot-docker-compose-module","pgAdmin Spring Boot Docker Compose Module",[651,181695,181696],{},"Are you tired of dealing with complex development environments? Look no further than this beginner-friendly tutorial on streamlining your development process with Spring Boot, PostgreSQL, and pgAdmin in Docker. I'll walk you through the steps to set up your environment using Docker Compose, so you can focus on writing code instead of worrying about infrastructure. Join me and discover how to build and deploy faster with this efficient and easy-to-use setup.",[5988,181698],{"id":181699},"XDlgWyVfSMA",[5909,181701,181703],{"id":181702},"graphql-pagination-support-in-spring-for-graphql","GraphQL Pagination Support in Spring for GraphQL",[651,181705,181706],{},"In this tutorial you are going to learn how to perform pagination in Spring for GraphQL using the cursor connection specification. You will start by building out a GraphQL API and then we will look at when you might want to introduce paging.",[5988,181708],{"id":181709},"3YTSh8vJ8eY",[4542,181711,181713],{"id":181712},"spring-recipes-on-the-oreilly-learning-platform","Spring Recipes on the O’Reilly Learning Platform",[651,181715,181716,181717,181721],{},"I’m excited to announce that on August 17th my friend and coworker Nate Schutta and I will be presenting ",[812,181718,181713],{"href":181719,"rel":181720},"https://learning.oreilly.com/live-events/spring-recipes/0636920095810/",[816],". If you’re on the platform I hope you can sign up and join us for a morning full of learning Spring!",[651,181723,181724],{},[660,181725],{"alt":181726,"src":181727},"SPring Recipes","/images/newsletter/2023/07/24/spring-recipes-olp.png",[4542,181729,181382],{"id":180522},[651,181731,181385,181732,181391],{},[812,181733,181390],{"href":181388,"rel":181734},[816],[651,181736,181394],{},[651,181738,181397],{},[651,181740,181741],{},[812,181742,82111],{"href":82111,"rel":181743},[816],[4542,181745,177889],{"id":157573},[5909,181747,164959],{"id":69848},[651,181749,181750,181751,181754,181755,664],{},"I really enjoyed this article by Brian Vermeer on How to do password hashing in Java applications the right way. There was a nice example of using the ",[676,181752,181753],{},"spring-security-crypto"," dependency and using the ",[676,181756,181757],{},"Argon2PasswordEncoder",[5909,181759,179922],{"id":133422},[651,181761,181762,181767],{},[812,181763,181766],{"href":181764,"rel":181765},"https://in.relation.to/2023/07/20/orm-63cr1/",[816],"Hibernate 6.3 CR1"," now supports query methods.",[5909,181769,165017],{"id":165016},[651,181771,181772],{},"Peace is the result of retraining your mind to process life as it is rather than as you think it should be. – Dr. Wayne W. Dyer",[4542,181774,157704],{"id":157703},[651,181776,178368,181777,166271],{},[812,181778,41499],{"href":44086,"rel":181779},[816],[651,181781,41105,181782,69920,181784,181786,181788,181790,4505],{},[41107,181783],{},[41107,181785],{},[812,181787,161560],{"href":161111},[41107,181789],{},[812,181791,53869],{"href":53869,"rel":181792},[816],{"title":674,"searchDepth":790,"depth":790,"links":181794},[181795,181799,181803,181804,181805,181810],{"id":181508,"depth":790,"text":181509,"children":181796},[181797,181798],{"id":181558,"depth":892,"text":181559},{"id":9041,"depth":892,"text":9042},{"id":171235,"depth":790,"text":171236,"children":181800},[181801,181802],{"id":181692,"depth":892,"text":181693},{"id":181702,"depth":892,"text":181703},{"id":181712,"depth":790,"text":181713},{"id":180522,"depth":790,"text":181382},{"id":157573,"depth":790,"text":177889,"children":181806},[181807,181808,181809],{"id":69848,"depth":892,"text":164959},{"id":133422,"depth":892,"text":179922},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":181812,"date":181813},"content-creation-cycle","2023-07-24T08:00:00.000Z","/newsletter/2023/07/24/content-creation-cycle",{"title":181485,"description":181490},"newsletter/2023/07/24/content-creation-cycle","mA_mwz5nVbK9Wmh7LjgrFND_ilEir62gStBNUwrSAU8",{"id":181819,"title":181820,"body":181821,"description":181825,"extension":793,"meta":181965,"navigation":797,"path":181968,"seo":181969,"stem":181970,"__hash__":181971},"content/newsletter/2023/07/31/spring-one-announcement.md","What’s new in Spring, Getting Started with GraphQL and a HUGE announcement",{"type":648,"value":181822,"toc":181953},[181823,181826,181834,181837,181856,181859,181862,181864,181867,181873,181877,181886,181892,181894,181897,181913,181915,181917,181922,181924,181927,181933,181935,181940],[651,181824,181825],{},"Happy Monday and welcome to another edition of the newsletter! This will be my last week in the office before my family and I head off to San Destin, FL for a little vacation. I absolutely love the white water and amazing beaches there. I’m really looking forward to some rest and relaxation, as well as some golf!",[651,181827,181828,181829,181833],{},"This week, I want to catch up on what’s new in Spring, based on a conversation I had with DaShaun during ",[812,181830,97345],{"href":181831,"rel":181832},"https://springofficehours.io/",[816],". Additionally, I spent all of last week rewriting my slide deck for my GraphQL talk at SpringOne this year, so I’ll touch on some things that are top of mind with that. Finally, be sure to stick around until the end for a huge announcement.",[4542,181835,181836],{"id":39730},"What’s new in Spring",[651,181838,181839,181840,23212,181845,181850,181851,181855],{},"DaShaun and I were finally both home to record a new episode of Spring Office Hours. We noted at the beginning of the episode that we have both been traveling a lot for work lately. With some time between episodes, we thought it would be a good opportunity to catch up on all the news in the Spring Ecosystem. There were some big releases in ",[812,181841,181844],{"href":181842,"rel":181843},"https://spring.io/blog/2023/06/15/spring-framework-6-1-m1-released",[816],"Spring Framework 6.1 M1",[812,181846,181849],{"href":181847,"rel":181848},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2.0-M1-Release-Notes",[816],"Spring Boot 3.2 M1",". There has also been a lot of talk about the new ",[812,181852,113232],{"href":181853,"rel":181854},"https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient",[816]," and Spring Cloud Gateway's support for MVC coming later this year.",[651,181857,181858],{},"It's an exciting time to be a Spring Developer! We have three more shows before we are in Vegas for SpringOne, and we plan on doing an episode live there. This is just a reminder that you can now find the podcast on all your favorite podcast networks. If you're enjoying the show, please do us a favor and leave a review.",[5988,181860],{"id":181861},"eUKqjRV1zEE",[4542,181863,180523],{"id":180522},[651,181865,181866],{},"Last week, I spent most of my time working on my slide deck for my GraphQL talk. Although I have given a similar talk about 20 times before, I felt that I had learned a lot over the past year. Therefore, I decided to start from scratch and incorporate what I had learned. I am pleased with the result and feel that the presentation flows well. Despite only having 40 minutes, I was able to pack a substantial amount of content into it.",[651,181868,181869],{},[660,181870],{"alt":181871,"src":181872},"spring-one-rest-presentation.png","/images/newsletter/2023/07/31/spring-one-rest-presentation.png",[5909,181874,181876],{"id":181875},"huge-announcement","Huge Announcement",[651,181878,181879,181880,181885],{},"I can’t believe this is actually happening but yours truly will be on the main stage during this years keynote! I have a small part but couldn’t be more excited to join so many of my amazing colleagues on stage for what should be an amazing presentation. If you are going to SpringOne make sure you take advantage of the ",[812,181881,181884],{"href":181882,"rel":181883},"https://event.vmware.com/flow/vmware/explore2023lv/content/page/catalog?search.sessiontype=1684866685619001w4UD&src=WWW_us_MRC_dqgsrvt1ho7td&cid=7012H000000wtgaQAA",[816],"scheduler"," and sign up for the talks you want to attend.",[651,181887,181888],{},[660,181889],{"alt":181890,"src":181891},"SpringOne Keynote","/images/newsletter/2023/07/31/spring-one-keynote.png",[4542,181893,176059],{"id":176058},[651,181895,181896],{},"I have a list of videos I want to work on but we all have priorities and right now that’s SpringOne. If I have anytime I will try and get something out but it might be slow until the conference ends.",[5316,181898,181899,181908],{},[5332,181900,181901,181902,181907],{},"I’ll be joining ",[812,181903,181906],{"href":181904,"rel":181905},"https://twitter.com/MarcoBehler/status/1684858548235198464",[816],"Marco Behler"," on his YouTube channel this week for a live stream. We are going have a good conversation and build something in Spring and I am really looking forward to this.",[5332,181909,181716,181910,181721],{},[812,181911,181713],{"href":181719,"rel":181912},[816],[4542,181914,177889],{"id":157573},[5909,181916,164959],{"id":69848},[5316,181918,181919],{},[5332,181920,181921],{},"I really enjoyed this article by my colleague Nick Kuhn on extended Spring Runtime Support. If you’re running Spring Boot 2.7.x in production open source support will end this November. With VMware Spring Runtime support you can get peace of mind through our extended support.",[5909,181923,178573],{"id":95961},[651,181925,181926],{},"This is a public service announcement to stop repeating patterns that make no sense.",[651,181928,181929],{},[812,181930,181931],{"href":181931,"rel":181932},"https://twitter.com/therealdanvega/status/1684951821444435969",[816],[4542,181934,157704],{"id":157703},[651,181936,178368,181937,166271],{},[812,181938,41499],{"href":44086,"rel":181939},[816],[651,181941,41105,181942,69920,181944,181946,181948,181950],{},[41107,181943],{},[41107,181945],{},[812,181947,161560],{"href":161111},[41107,181949],{},[812,181951,53869],{"href":53869,"rel":181952},[816],{"title":674,"searchDepth":790,"depth":790,"links":181954},[181955,181956,181959,181960,181964],{"id":39730,"depth":790,"text":181836},{"id":180522,"depth":790,"text":180523,"children":181957},[181958],{"id":181875,"depth":892,"text":181876},{"id":176058,"depth":790,"text":176059},{"id":157573,"depth":790,"text":177889,"children":181961},[181962,181963],{"id":69848,"depth":892,"text":164959},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":181966,"date":181967},"spring-one-announcement","2023-07-31T07:00:00.000Z","/newsletter/2023/07/31/spring-one-announcement",{"title":181820,"description":181825},"newsletter/2023/07/31/spring-one-announcement","ZLAMcBV-3ArP5iel3gWCJ3S2ukDbIK-y8uJbEfYBx44",{"id":181973,"title":181974,"body":181975,"description":181979,"extension":793,"meta":182359,"navigation":797,"path":182362,"seo":182363,"stem":182364,"__hash__":182365},"content/newsletter/2023/08/28/spring-one-2023-recap.md","SpringOne 2023 Recap",{"type":648,"value":181976,"toc":182334},[181977,181980,181983,181989,181992,181995,181998,182000,182003,182006,182009,182012,182017,182021,182024,182030,182038,182042,182045,182052,182055,182061,182065,182068,182074,182078,182081,182087,182091,182094,182097,182101,182104,182108,182111,182116,182119,182122,182126,182129,182132,182143,182149,182156,182160,182163,182168,182170,182173,182176,182181,182184,182190,182194,182197,182203,182207,182210,182216,182220,182223,182229,182233,182236,182238,182241,182246,182249,182253,182256,182262,182268,182272,182275,182281,182285,182288,182294,182297,182299,182302,182308,182311,182314,182316,182321],[651,181978,181979],{},"Welcome to another edition of the newsletter! This is a special edition in which I will be recapping SpringOne and VMware Explore. If you've been following this newsletter, Twitter, or Spring Office Hours, you'll know that I had a busy few months leading up to SpringOne. As I write this on Sunday night, I can say that while it was one of the most amazing conferences I've ever had the opportunity to be a part of, it was also exhausting.",[651,181981,181982],{},"This is the first year that we've combined SpringOne and VMware Explore, and the first time we've been in person since 2019. VMware Explore was in person last year at the Moscone Center, and this year it moved to the Venetian Expo Center in Las Vegas. I'm a big fan of Vegas, and I thought it was a perfect setting for the conference, and the Venetian was an excellent host.",[651,181984,181985],{},[660,181986],{"alt":181987,"src":181988},"VMware Registration Desk","/images/newsletter/2023/08/28/registration.jpeg",[651,181990,181991],{},"I arrived late on Saturday night because I was originally going to be part of the keynote speech, and we had a dress rehearsal scheduled for Sunday morning. However, my flight was delayed, and I didn't arrive in Vegas until 1 AM local time. By the time I got to bed, it was already a little after 2 AM. I don't sleep well on planes, so I only managed to get around 5 hours of sleep.",[651,181993,181994],{},"Despite knowing that my part had been cut from the keynote, I still wanted to see everyone and support them in any way I could. I went down to meet the team for rehearsal. When I got registered for the conference, I was in awe of the check-in process. The sheer size of the conference began to sink in; with over 10,000 registrants, it was by far the biggest conference I had ever attended, let alone spoken at. Later that day, I had a chance to play golf with my friends Nate and Sharat at Top Golf Vegas, which was absolutely beautiful.",[651,181996,181997],{},"I went to bed early, feeling like a kid on Christmas Eve, excited for the day that was coming my way!",[4542,181999,82113],{"id":82579},[651,182001,182002],{},"Due to being on East Coast time, I woke up a little after 5:00 AM, ready for the day. After having breakfast sent to my room, I did one last walkthrough of my two talks and prepared for the long day ahead.",[651,182004,182005],{},"The keynote started at 9:00 AM, so I arrived around 8:00 AM, 30 minutes before the doors would open. It was an amazing opportunity to finally meet in person so many people I work with but had never met before.",[651,182007,182008],{},"The keynote room held about 1,000 people, and at one point, I saw people standing on the sides and back of the room. The layout was awesome, with two big screens on the side of the stage and some amazing graphics lining the back wall.",[651,182010,182011],{},"The keynote kicked off with the emcees DaShaun and Tasha, who brought a ton of energy through the keynote. There were so many highlights of the keynote, and I didn't take a lot of notes, so this will be off of memory, which means if I left anything out, I apologize.",[651,182013,182014],{},[660,182015],{"alt":181890,"src":182016},"/images/newsletter/2023/08/28/springone-keynote.jpeg",[5909,182018,182020],{"id":182019},"springs-20-years-of-innovation","Spring’s 20 years of Innovation",[651,182022,182023],{},"The first speaker was Purnima Padmanabhan, who discussed Spring's 20 years of innovation. She emphasized how great the community is and how it has been the driving force behind Spring's growth. The impact of Spring is not just about technology, but also about improving human lives. Purnima provided examples of companies such as Netflix, FedEx, and Blue Cross Blue Shield that rely on Spring to power their applications.",[651,182025,182026],{},[660,182027],{"alt":182028,"src":182029},"Tasha and DaShaun introducing Purnima","/images/newsletter/2023/08/28/tasha_dashaun_purnima.png",[651,182031,182032,182033,664],{},"During her presentation, Purnima announced a new set of Spring Solutions, including Spring Consulting. This new service can help companies transition to the latest and greatest version for all of their Spring applications. For more information, see the ",[812,182034,182037],{"href":182035,"rel":182036},"https://tanzu.vmware.com/content/blog/springone-2023-announcements",[816],"official blog post",[5909,182039,182041],{"id":182040},"history-of-spring","History of Spring",[651,182043,182044],{},"I really enjoyed Josh Long and Dave Syer's presentation on the history of Spring. They walked us through how Spring has made application development easier over the years.",[651,182046,182047,182048,664],{},"They began with an application that used an old version of Spring and was based on XML. They then resurrected an old version of the Spring Initializr, which can be found at ",[812,182049,182050],{"href":182050,"rel":182051},"https://start100.spring.io/",[816],[651,182053,182054],{},"They proceeded to show how Spring has simplified application development over the years. Spring Boot provides a way to bootstrap our applications and simplifies everything by removing XML. It also offers powerful features like dependency management and auto-configuration.",[651,182056,182057],{},[660,182058],{"alt":182059,"src":182060},"Dave Syer and Josh Long","/images/newsletter/2023/08/28/josh_dave.png",[5909,182062,182064],{"id":182063},"spring-community-video","Spring Community Video",[651,182066,182067],{},"Several individuals from the Spring community shared amazing videos praising Spring for its impact on their careers. It was inspiring to hear from so many people, and I will share the video with the community as soon as I can get my hands on it.",[651,182069,182070],{},[660,182071],{"alt":182072,"src":182073},"Matt Raible","/images/newsletter/2023/08/28/matt_raible.png",[5909,182075,182077],{"id":182076},"spring-virtual-threads","Spring + Virtual Threads",[651,182079,182080],{},"During the keynote, my coworker Cora discussed virtual threads in the context of Spring applications. I found her presentation to be an excellent representation of how virtual threads in JDK 21 can enhance performance for blocking applications. What's exciting is that you can easily take advantage of this feature by simply adjusting the settings in Spring Boot 3.2, which will be released later this year.",[651,182082,182083],{},[660,182084],{"alt":182085,"src":182086},"Cora’s Virtual Threads Presentation","/images/newsletter/2023/08/28/cora.png",[5909,182088,182090],{"id":182089},"scale-your-controllers-to-zero-with-serverless","Scale your @Controllers to Zero with Serverless",[651,182092,182093],{},"Oleg Zhurakousky and Mark Sailes gave a wonderful presentation on building Spring applications for AWS Lambda. In the past, you could build serverless functions in both Java and Spring using Spring Cloud Function. They reminded us about SnapStart, which can increase the performance of your Java functions up to 10 times.",[651,182095,182096],{},"Now, thanks to the collaboration between VMware and AWS, you can deploy your existing Spring MVC applications onto AWS Lambda. There are several scenarios where this makes sense, and they did a great job of explaining how it works.",[5909,182098,182100],{"id":182099},"scale-to-zero-with-checkpoint-restore","Scale to Zero with Checkpoint Restore",[651,182102,182103],{},"Violeta Georgieva presented using Project CraC in Spring Applications during the session. This new approach enables scaling applications to zero, which reduces cloud spending and improves scalability for high availability under high loads. She did an excellent job of explaining the Checkpoint Restore process and demonstrated how it would work in Spring through a demo that showcased its performance benefits.",[5909,182105,182107],{"id":182106},"leyla-seka","Leyla Seka",[651,182109,182110],{},"It's difficult to summarize Layla's talk in just a couple of sentences. I highly recommend taking 10 minutes to watch her presentation. During the talk, she shared how her parents instilled in her the importance of using her life to help others from an early age.",[651,182112,182113],{},[660,182114],{"alt":182107,"src":182115},"/images/newsletter/2023/08/28/leyla_seka.png",[651,182117,182118],{},"She joined Salesforce early on and enjoyed her time on the product team. However, she noticed that the men in the company were being paid more than her and knew that something was wrong. To address this issue, she led an initiative that included an equal pay audit. This helped to drive change throughout the tech industry.",[651,182120,182121],{},"After leaving Salesforce, she started a venture capital firm for women and BLCK VC to bring more black entrepreneurs into the investing ecosystem. Her story is truly inspiring, and I encourage you to watch her presentation. Thank you, Leyla!",[5909,182123,182125],{"id":182124},"spring-unlocks-the-power-of-platforms","Spring unlocks the power of platforms",[651,182127,182128],{},"Adib Saikali and Asir Selvasingh took the stage to talk about platforms and started with the phrase \"You can count on Spring to unlock the power of Platforms\". No application is an island and they depend on many services to run. With so many services available, how do you know which ones to choose and how to combine them all?",[651,182130,182131],{},"There are three app-aware platforms for Spring Applications that you should be aware of:",[5316,182133,182134,182137,182140],{},[5332,182135,182136],{},"VMware Tanzu Application Service",[5332,182138,182139],{},"VMware Tanzu Application Platform",[5332,182141,182142],{},"Azure Spring Apps Enterprise",[651,182144,182145],{},[660,182146],{"alt":182147,"src":182148},"The power of platforms","/images/newsletter/2023/08/28/platforms.png",[651,182150,182151,182152,182155],{},"The presenters walked through an example application and discussed adding AI to it towards the end. If you want to add AI to your Spring-powered applications today, you could start from scratch and develop your own solution. However, this approach is not very efficient. Instead, you can take advantage of a new experimental project called ",[812,182153,123549],{"href":150492,"rel":182154},[816],", which was announced during the presentation. It was impressive to see how easily we could incorporate AI features into our applications.",[5909,182157,182159],{"id":182158},"keynote-video-replay","Keynote Video Replay",[651,182161,182162],{},"If you want to watch a replay of the keynote you can do so by visiting the SpringOne conference website below. After you have had a chance to go through it please reach out and let me know what stood out for you.",[651,182164,182165],{},[812,182166,82111],{"href":82111,"rel":182167},[816],[5909,182169,99135],{"id":66261},[651,182171,182172],{},"That keynote was amazing but I had no time to soak it in because my first talk of the conference was right after it. I quickly made my way to my session and got setup for my talk titled “Does your API need a REST, check out GraphQL”.",[651,182174,182175],{},"The room was packed with standing room only and I was really excited for this talk. I have been talking about GraphQL for the past couple of years but this was an updated talk packed full of content. I only had 40 minutes and tried to pack in about an hours worth of content into this time slot. I thought the talk went really well but my only criticism of the talk was that it was a lot to take in.",[651,182177,182178],{},[660,182179],{"alt":178055,"src":182180},"/images/newsletter/2023/08/28/graphql_talk.png",[651,182182,182183],{},"If you were at the conference there is a way to watch the replay. I don’t know if this is going to be public but if it is I will be sure to share the link. If it isn’t and you’re interested in seeing this let me know and I can record it for my YouTube channel.",[651,182185,182186],{},[660,182187],{"alt":182188,"src":182189},"GraphQL in Action","/images/newsletter/2023/08/28/graphql_in_action.png",[5909,182191,182193],{"id":182192},"spring-recipes","Spring Recipes",[651,182195,182196],{},"Later in the afternoon Nate Schutta and I presented on Spring Recipes. This is our take on raising awareness of the different projects in the Spring ecosystem. We start off by defining a problem and then walk through a solution and how Spring can solve it. This talk was 70 minutes and honestly it was so much fun sharing the stage with my good friend Nate. We had a lot of really good questions from the audience and I think this talk was a big hit.",[651,182198,182199],{},[660,182200],{"alt":182201,"src":182202},"Spring Recipes in Action","/images/newsletter/2023/08/28/spring_recipes_in_action.png",[5909,182204,182206],{"id":182205},"welcome-reception","Welcome Reception",[651,182208,182209],{},"On Monday night, there was a welcome reception in the main expo hall where all the booths were set up. I was working at the Spring portion of the VMware Booth and had the opportunity to talk to many people about the great things we are doing with Spring. To my surprise, our CEO Raghu Raghuram stopped by the booth, and we had a conversation about the morning's keynote and what was on attendees' minds. It was a conversation that I will remember for the rest of my life. Seeing his enthusiasm for what we were doing and being able to share that with him was a great way to cap off Day 1!",[651,182211,182212],{},[660,182213],{"alt":182214,"src":182215},"Raghu Raghuram at VMware Explore","/images/newsletter/2023/08/28/raghu.jpeg",[5909,182217,182219],{"id":182218},"birthday","Birthday🤩",[651,182221,182222],{},"It was my Birthday and all I did was attend a keynote give 2 talks and work the booth, no big deal. Thank you to my friend Sharat for the Birthday shout out on Twitter",[651,182224,182225],{},[812,182226,182227],{"href":182227,"rel":182228},"https://twitter.com/Sharat_Chander/status/1693754169747017991",[816],[4542,182230,182232],{"id":182231},"vmware-exploreconclusion","VMware ExploreConclusion",[651,182234,182235],{},"I will now quickly run through the remaining parts of the conference, as otherwise this newsletter will take me another week to write. Unfortunately I didn’t get much of a chance to attend any of the general sessions because I was working the booth or catching up on other work.",[5909,182237,97345],{"id":97344},[651,182239,182240],{},"We attempted a live version of Spring Office Hours, but unfortunately, we were unsuccessful. However, it was a valuable learning experience that reminded us of the importance of better preparation. The WiFi in the area was unreliable, which was our biggest obstacle. Additionally, we lacked the appropriate equipment. If we attempt this again, we will work on improving our setup to improve the quality of the broadcast.",[651,182242,182243],{},[660,182244],{"alt":121483,"src":182245},"/images/newsletter/2023/08/28/spring_office_hours_live.png",[651,182247,182248],{},"On today's show, we will provide a recap of SpringOne. Please join us live or catch the replay or podcast episode.",[5909,182250,182252],{"id":182251},"springs-20th-anniversary-celebration","Spring’s 20th Anniversary Celebration",[651,182254,182255],{},"On Wednesday night, we had a 20th Anniversary Spring Celebration get-together, which was one of the highlights for me. It was great to be in a room with so many people who have impacted Spring and so many people from the community. Throughout the event, there were video montages of Spring Conference presentations over the last 20 years, which was really cool to see.",[651,182257,182258],{},[660,182259],{"alt":182260,"src":182261},"Spring’s 20 Anniversary Celebration","/images/newsletter/2023/08/28/spring_20_event.jpeg",[651,182263,182264],{},[660,182265],{"alt":182266,"src":182267},"Dan, Purnima and DaShaun","/images/newsletter/2023/08/28/dan_purnima_dashaun.png",[5909,182269,182271],{"id":182270},"spring-for-architects","Spring for Architects",[651,182273,182274],{},"On Thursday afternoon, I had my final talk of the conference with my good friend Nate. The presentation focused on what architects of Spring applications should understand. It was held in the Hub area at the code theater, and I wasn't sure if anyone would attend. To my delight, not only did people attend, but the room was packed. It was a lot of fun, and I appreciate all the positive feedback we received.",[651,182276,182277],{},[660,182278],{"alt":182279,"src":182280},"Spring for Architects in Action","/images/newsletter/2023/08/28/spring_architects_in_action.png",[5909,182282,182284],{"id":182283},"spring-team-meeting-and-dinner","Spring Team Meeting and Dinner",[651,182286,182287],{},"To conclude the conference, I was invited to a Spring Team Meeting and dinner. During the meeting, we heard from our leadership about the current state of Spring and its future direction. While I cannot divulge too much from the meeting, I can say that I am excited and grateful to be part of such an amazing and talented group of people.",[651,182289,182290],{},[660,182291],{"alt":182292,"src":182293},"Spring Team Dinner","/images/newsletter/2023/08/28/spring_team_dinner.jpeg",[651,182295,182296],{},"After this dinner I caught a redeye flight home and spent the entire weekend recovering from the conference.",[4542,182298,9042],{"id":9041},[651,182300,182301],{},"What I will remember most about this conference is the opportunity to hang out with so many amazing people. I had the chance to spend a lot of time with DaShaun, Nate, and Sharat. Sharat, who runs Developer Relations for Java at Oracle, is one of the best people in our community. It was great to see firsthand just how important it is to prioritize people and be kind to everyone. As my friend Sharat likes to say, \"Technology is about people first and technology second.”",[651,182303,182304],{},[660,182305],{"alt":182306,"src":182307},"LOVE","/images/newsletter/2023/08/28/love.jpeg",[651,182309,182310],{},"I want to thank everyone who came up and said hello to me. I got so many great comments about Spring Office Hours or my YouTube channel and it really made me smile and remember why I do what I do.",[651,182312,182313],{},"Now that the conference is over I can get back to focusing on producing content, mainly YouTube. I haven’t posted a video in over a month and I have a long list of videos that I am excited to sit down and record. That is as soon as crawl out of the hole I’m about to get in to try and wrangle this expense report 🤣",[4542,182315,157704],{"id":157703},[651,182317,178368,182318,166271],{},[812,182319,41499],{"href":44086,"rel":182320},[816],[651,182322,41105,182323,69920,182325,182327,182329,182331],{},[41107,182324],{},[41107,182326],{},[812,182328,161560],{"href":161111},[41107,182330],{},[812,182332,53869],{"href":53869,"rel":182333},[816],{"title":674,"searchDepth":790,"depth":790,"links":182335},[182336,182351,182357,182358],{"id":82579,"depth":790,"text":82113,"children":182337},[182338,182339,182340,182341,182342,182343,182344,182345,182346,182347,182348,182349,182350],{"id":182019,"depth":892,"text":182020},{"id":182040,"depth":892,"text":182041},{"id":182063,"depth":892,"text":182064},{"id":182076,"depth":892,"text":182077},{"id":182089,"depth":892,"text":182090},{"id":182099,"depth":892,"text":182100},{"id":182106,"depth":892,"text":182107},{"id":182124,"depth":892,"text":182125},{"id":182158,"depth":892,"text":182159},{"id":66261,"depth":892,"text":99135},{"id":182192,"depth":892,"text":182193},{"id":182205,"depth":892,"text":182206},{"id":182218,"depth":892,"text":182219},{"id":182231,"depth":790,"text":182232,"children":182352},[182353,182354,182355,182356],{"id":97344,"depth":892,"text":97345},{"id":182251,"depth":892,"text":182252},{"id":182270,"depth":892,"text":182271},{"id":182283,"depth":892,"text":182284},{"id":9041,"depth":790,"text":9042},{"id":157703,"depth":790,"text":157704},{"slug":182360,"date":182361},"spring-one-2023-recap","2023-08-28T07:00:00.000Z","/newsletter/2023/08/28/spring-one-2023-recap",{"title":181974,"description":181979},"newsletter/2023/08/28/spring-one-2023-recap","xM8ZXq3QakHAW3yj31SPXR4cW9DOBm2G4bYjhfuzApo",{"id":182367,"title":182368,"body":182369,"description":182373,"extension":793,"meta":182543,"navigation":797,"path":182546,"seo":182547,"stem":182548,"__hash__":182549},"content/newsletter/2023/09/04/working-with-json-spring.md","Working with JSON in Spring & VMware Explore Recordings",{"type":648,"value":182370,"toc":182531},[182371,182374,182377,182381,182384,182387,182390,182393,182397,182406,182422,182427,182430,182432,182435,182441,182450,182452,182454,182468,182470,182489,182491,182500,182502,182511,182513,182518],[651,182372,182373],{},"Happy Monday and welcome to another edition of the newsletter. It's Labor Day here in the United States, which marks the unofficial end of summer. It's always bittersweet for me. On one hand, it means the end of my favorite time here in the Midwest. I love being outside, grilling, having pool parties, and going for morning runs in my neighborhood. While those won't completely go away yet, they are coming to an end. On the other side of the holiday is fall, football, and the kids are back in school. To get me through the end of summer let me know what some of your favorite fall traditions are.",[651,182375,182376],{},"In this week's newsletter, we're going to talk about working with JSON in Java and Spring.",[4542,182378,182380],{"id":182379},"json-in-spring","JSON in Spring",[651,182382,182383],{},"Last week, I spent some time exploring how to work with JSON in Spring applications. I was inspired to dig into this topic by my desire to create a demo GraphQL API for a video project. I wanted to use real-world data for this API and decided to extract it from my personal blog, which uses GraphQL.",[651,182385,182386],{},"I wrote a query to extract the data, which resulted in some JSON data. However, the format of this data was not suitable for importing into my new application. To address this issue, I went down the rabbit hole of reading JSON files in Spring and exploring the various options available for mapping this data to the types used in my application.",[5988,182388],{"id":182389},"EumLbf8WjnY",[5988,182391],{"id":182392},"cw0TfpcUkao",[4542,182394,182396],{"id":182395},"vmware-explore-recordings","VMware Explore Recordings",[651,182398,182399,182400,182405],{},"In last week's newsletter, I talked about my trip to Las Vegas for SpringOne. This week, I'm happy to share the news that all of the breakout sessions have been uploaded to the ",[812,182401,182404],{"href":182402,"rel":182403},"https://www.vmware.com/explore/video-library/video-landing.html",[816],"VMware Explore Library",". To access the sessions, simply create a free account and log in. Two out of the three sessions I gave were recorded, and you can check them out below.",[5316,182407,182408,182415],{},[5332,182409,182410,664],{},[812,182411,182414],{"href":182412,"rel":182413},"https://www.vmware.com/explore/video-library/video-landing.html?sessionid=1681495955926001D6RA&videoId=6335272726112",[816],"Does your API need a REST, checkout GraphQL",[5332,182416,182417],{},[812,182418,182421],{"href":182419,"rel":182420},"https://www.vmware.com/explore/video-library/video-landing.html?sessionid=1680205167648001ZhTy&videoId=6335272438112",[816],"Spring Recipes: Common problems and Solutions",[651,182423,182424],{},[660,182425],{"alt":182404,"src":182426},"/images/newsletter/2023/09/04/vmware_explore_library.png",[651,182428,182429],{},"I am still making my way through the recordings. I can’t believe how many great talks there are from both SpringOne and VMware Explore. If you were in attendance or had a chance to go through the library what are some of your must watch sessions?",[4542,182431,176059],{"id":176058},[651,182433,182434],{},"I have been hard at working on cleaning up my backlog of video ideas and planning out what is next. I have a couple videos already recorded and I’m looking forward editing and publishing those soon. This week my plan is to record videos on the new client in Spring Boot 3.2.",[651,182436,182437],{},[660,182438],{"alt":182439,"src":182440},"Video Backlog","/images/newsletter/2023/09/04/video_backlog.png",[651,182442,182443,182444,182449],{},"It’s hard to believe that I am starting to submit CFPs for next year. I submitted a bunch of talks to Code Mash, JFokus and I am working on my submissions for Devnexus. I spent some time last week updated my abstracts and you can ",[812,182445,182448],{"href":182446,"rel":182447},"https://github.com/danvega/abstracts",[816],"check them out here",". As a reminder you can always reach out to me if you would like me to speak at your user group meeting, lunch and learn or conference.",[4542,182451,177889],{"id":157573},[5909,182453,164959],{"id":69848},[5316,182455,182456,182459],{},[5332,182457,182458],{},"In this article Jens Schauder discusses the beginning of the end of the N+1 problem. This article walks through the problem and then introductions Single Query loading which can help us solve the problem. Just don’t call it SQL 🤣",[5332,182460,182461,182462,182467],{},"In this ",[812,182463,182466],{"href":182464,"rel":182465},"https://piotrminkowski.com/2023/08/29/introduction-to-grpc-with-spring-boot/",[816],"next article",", you will learn how to implement Spring Boot apps that communicate over gRPC. Piotr Minkowski shows us how to create a new project and a third party library that will help us support gRPC.",[5909,182469,164971],{"id":157591},[5316,182471,182472,182481],{},[5332,182473,182474,182475,182480],{},"The 2023 JVM Language Summit ",[812,182476,182479],{"href":182477,"rel":182478},"https://www.youtube.com/watch?v=6nRS6UiN7X0&list=PLX8CzqL3ArzW90jKUCf4H6xCKpStxsOzp",[816],"replay records"," are now hosted on the Java YouTube channel.",[5332,182482,182483,182484,182488],{},"I really enjoyed this video from a former coworker Matt Eland. In ",[812,182485,85142],{"href":182486,"rel":182487},"https://www.youtube.com/watch?v=2ULkQcOW8Y8",[816]," Matt walks us through his first look at GitHub Copilot Chat.",[5909,182490,164983],{"id":39439},[651,182492,182493,182494,182499],{},"I discovered a new Podcast this week called ",[812,182495,182498],{"href":182496,"rel":182497},"https://www.devtools.fm/episode/63",[816],"devtools.f","m. The episode I listened to featured Daniel Roe, Nuxt Core team lead. I really enjoyed this conversation about Daniel’s career and all things Nuxt.",[5909,182501,179922],{"id":133422},[651,182503,182504,182505,182510],{},"It looks like ",[812,182506,182509],{"href":182507,"rel":182508},"https://twitter.com/nuxt_js/status/1696157897607061704",[816],"Nuxt Image is about to hit 1.0"," which is certainly exciting news.",[4542,182512,157704],{"id":157703},[651,182514,178368,182515,166271],{},[812,182516,41499],{"href":44086,"rel":182517},[816],[651,182519,41105,182520,69920,182522,182524,182526,182528],{},[41107,182521],{},[41107,182523],{},[812,182525,161560],{"href":161111},[41107,182527],{},[812,182529,53869],{"href":53869,"rel":182530},[816],{"title":674,"searchDepth":790,"depth":790,"links":182532},[182533,182534,182535,182536,182542],{"id":182379,"depth":790,"text":182380},{"id":182395,"depth":790,"text":182396},{"id":176058,"depth":790,"text":176059},{"id":157573,"depth":790,"text":177889,"children":182537},[182538,182539,182540,182541],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":133422,"depth":892,"text":179922},{"id":157703,"depth":790,"text":157704},{"slug":182544,"date":182545},"working-with-json-spring","2023-09-04T07:00:00.000Z","/newsletter/2023/09/04/working-with-json-spring",{"title":182368,"description":182373},"newsletter/2023/09/04/working-with-json-spring","zgzI101oHnj8QxSiSDRBMEwZRM_gyIUK8e1wdFsoVwc",{"id":182551,"title":182552,"body":182553,"description":182557,"extension":793,"meta":182746,"navigation":797,"path":182749,"seo":182750,"stem":182751,"__hash__":182752},"content/newsletter/2023/09/08/spring-framework-new-rest-client.md","Spring Framework’s new Rest Client, GraphQL in Nuxt and SpringOne videos are now available",{"type":648,"value":182554,"toc":182732},[182555,182558,182561,182564,182568,182576,182578,182581,182588,182611,182615,182618,182621,182625,182628,182631,182633,182642,182657,182661,182669,182671,182673,182681,182683,182690,182692,182701,182703,182706,182712,182714,182719],[651,182556,182557],{},"Happy Monday and welcome to another edition of the newsletter. This week, I'm heading out to Seattle to meet with a customer and discuss GraphQL, one of my favorite topics. I feel grateful for the opportunity to do what I love every day!",[651,182559,182560],{},"Before we dive in, I'm excited to announce that I'll be speaking at ConnectTech on October 24-26 in Atlanta. As a huge fan of frontend technology and building for the web, I can't wait to share my real-world experience of building a Nuxt application powered by Notion. If you're attending, please let me know and come say hi 👋🏻. With an amazing lineup of speakers, I'm just as excited to be an attendee as I am to speak.",[651,182562,182563],{},"In this edition of the newsletter I want to talk to you about the new Rest Client in Spring Boot 3.2, working with GraphQL in Nuxt and SpringOne videos are now available on YouTube.",[4542,182565,182567],{"id":182566},"spring-frameworks-new-rest-client","Spring Framework’s new Rest Client",[651,182569,182570,182571,182575],{},"Last week I worked on a ",[812,182572,24879],{"href":182573,"rel":182574},"https://www.danvega.dev/blog/2023/09/08/rest-client-first-look/",[816]," and a video around Spring Framework’s new Rest Client coming in Spring Boot 3.2. What I really enjoy about this new Rest Client is that it works in a Spring MVC app, the fluent API and the simplicity / readability of the resulting code.",[5988,182577],{"id":119294},[651,182579,182580],{},"I’m also going to be experimenting with creating multiple YouTube shorts for each of my long form videos to hopefully drive more viewers to the tutorial. This didn’t make a lot of sense in the past because there was no way you could connect a short to a long form video. YouTube rolled out a new feature that allows you to set the related video for a short.",[651,182582,182583,182584,182587],{},"I could create these shorts by hand but one of my favorite services around, ",[812,182585,97413],{"href":181651,"rel":182586},[816]," added this in a recent update. It will automatically create shorts for me or I can go in and select the portion of the video I want to clip and then customize it. This is going to save me a ton of time and I am really excited to utilize this feature. Here are the 3 shorts I clipped from the long form tutorial.",[5316,182589,182590,182597,182604],{},[5332,182591,182592],{},[812,182593,182596],{"href":182594,"rel":182595},"https://youtube.com/shorts/owD-bIeOYcY",[816],"Spring Rest Client First Look",[5332,182598,182599],{},[812,182600,182603],{"href":182601,"rel":182602},"https://youtube.com/shorts/jgR9PxeY1mU",[816],"Http Interfaces in Spring using the new Rest Client",[5332,182605,182606],{},[812,182607,182610],{"href":182608,"rel":182609},"https://youtube.com/shorts/yBUskJd0Qww",[816],"What is a “Client” in Spring?",[4542,182612,182614],{"id":182613},"nuxt-graphql-client-spring-for-graphql","Nuxt GraphQL Client + Spring for GraphQL",[651,182616,182617],{},"I work a lot with GraphQL on the server using Spring for GraphQL. I often get questions on how to consume those APIs on the frontend so I thought I would take some time to address this last week. I put together a quick tutorial on using Nuxt + Nuxt GraphQL Client Module to call a GraphQL API written in Java and Spring.",[5988,182619],{"id":182620},"GzB-pL7gJcc",[4542,182622,182624],{"id":182623},"reading-yaml-in-spring-with-jackson","Reading YAML in Spring with Jackson",[651,182626,182627],{},"We all know how great Jackson is for working with JSON data in Java. Did you know that you can also use it to read in YAML? In this tutorial I give you a quick example of how and why you might want to do this.",[5988,182629],{"id":182630},"zy8slvenGxc",[4542,182632,173588],{"id":173914},[651,182634,182635,182636,182641],{},"All of the videos for SpringOne are now on the ",[812,182637,182640],{"href":182638,"rel":182639},"https://www.youtube.com/@SpringSourceDev/",[816],"SpringDeveloper YouTube Channel",". I was at the conference and I didn’t get to catch the sessions I wanted to see because there were so many. I started binging some of the sessions already and they are pure gold. If you want to catch my sessions, 2 of the 3 were recorded and you can find them below",[5316,182643,182644,182650],{},[5332,182645,182646],{},[812,182647,182193],{"href":182648,"rel":182649},"https://www.youtube.com/watch?v=8LCy662fIHA",[816],[5332,182651,182652],{},[812,182653,182656],{"href":182654,"rel":182655},"https://www.youtube.com/watch?v=xprZxH8p0Pg",[816],"Does your API Need a REST? Check out GraphQL",[4542,182658,182660],{"id":182659},"connect-tech","Connect Tech",[651,182662,182663,182664,182668],{},"I’m happy to announce that will be speaking at ",[812,182665,182660],{"href":182666,"rel":182667},"https://2023.connect.tech/",[816]," in Atlanta next month. This is my first time attending or speaking at this conference but I have heard nothing but great things about it. The list of speakers looks amazing as well so while I am excited to speak and equally as excited to be an attendee.",[4542,182670,177889],{"id":157573},[5909,182672,164959],{"id":69848},[651,182674,182675,182676,182680],{},"Java 21 is set to be released next week and it’s packed full of features. In ",[812,182677,109096],{"href":182678,"rel":182679},"https://blogs.oracle.com/javamagazine/post/java-21-sneak-peek?source=:so:tw:or:awr:jav:::&SC=:so:tw:or:awr:jav:::&pcode=",[816]," Mohamed Taman gives a sneak peak at what to expect.",[5909,182682,164971],{"id":157591},[651,182684,63025,182685,182689],{},[812,182686,85142],{"href":182687,"rel":182688},"https://www.youtube.com/watch?v=1g_wuincUdU",[816]," the great Craig Walls talks about the new experimental Spring AI Project. I thought this was a great example of how to get started with the project and how to take advantage of AI capabilities in your Spring Applications.",[5909,182691,164983],{"id":39439},[651,182693,182694,182695,182700],{},"I’m really enjoying the Working Code Podcast with Adam, Tim Carol and Ben. In the ",[812,182696,182699],{"href":182697,"rel":182698},"https://workingcode.dev/episodes/143-moving-on-rewriting-replatforming/",[816],"latest episode"," they discussed moving on, rewriting and replatforming.",[5909,182702,178573],{"id":95961},[651,182704,182705],{},"Victory Monday!",[651,182707,182708],{},[812,182709,182710],{"href":182710,"rel":182711},"https://twitter.com/therealdanvega/status/1700205200919679193",[816],[4542,182713,157704],{"id":157703},[651,182715,178368,182716,166271],{},[812,182717,41499],{"href":44086,"rel":182718},[816],[651,182720,41105,182721,69920,182723,182725,182727,182729],{},[41107,182722],{},[41107,182724],{},[812,182726,161560],{"href":161111},[41107,182728],{},[812,182730,53869],{"href":53869,"rel":182731},[816],{"title":674,"searchDepth":790,"depth":790,"links":182733},[182734,182735,182736,182737,182738,182739,182745],{"id":182566,"depth":790,"text":182567},{"id":182613,"depth":790,"text":182614},{"id":182623,"depth":790,"text":182624},{"id":173914,"depth":790,"text":173588},{"id":182659,"depth":790,"text":182660},{"id":157573,"depth":790,"text":177889,"children":182740},[182741,182742,182743,182744],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":182747,"date":182748},"spring-framework-new-rest-client","2023-09-08T07:00:00.000Z","/newsletter/2023/09/08/spring-framework-new-rest-client",{"title":182552,"description":182557},"newsletter/2023/09/08/spring-framework-new-rest-client","BUtAj_yeRvtSft4gP1SMjqTcF5W3O4IQN5T8QmgBSxk",{"id":182754,"title":182755,"body":182756,"description":182760,"extension":793,"meta":183186,"navigation":797,"path":183189,"seo":183190,"stem":183191,"__hash__":183192},"content/newsletter/2023/09/18/java-21.md","Java 21, The new JDBC Client and the Spring Boot Actuator",{"type":648,"value":182757,"toc":183172},[182758,182761,182767,182770,182773,182777,182786,182982,182990,182993,182995,183007,183009,183012,183035,183037,183040,183043,183050,183053,183056,183079,183083,183086,183089,183097,183099,183101,183104,183112,183114,183123,183125,183131,183133,183136,183141,183143,183146,183152,183154,183159],[651,182759,182760],{},"Happy Monday and welcome to another edition of the newsletter. Last week, I took a quick trip to Seattle to visit with a customer. While there, Josh Long and I presented on Spring Boot 3 and GraphQL, respectively, in person. It was a great trip and I have been looking forward to meeting this customer in person for a long time.",[651,182762,182763],{},[660,182764],{"alt":182765,"src":182766},"Seattle Waterfront","/images/newsletter/2023/09/18/seattle_waterfront.jpeg",[651,182768,182769],{},"I was also pretty excited about the iPhone 15 announcements last week. I am still using a 13 pro and I have been waiting for this one. I am excited about a lot of the new features but none more than the improved camera when it comes to shooting video. This is going to be a big step over my 2 year old phone for shooting video on the road.",[651,182771,182772],{},"In this weeks newsletter I want to talk about the release of Java 21, The JDBC Client and The Spring Boot Actuator.",[4542,182774,182776],{"id":182775},"jdk-21-is-here","JDK 21 is here!",[651,182778,182779,182780,182785],{},"This is a really exciting week as ",[812,182781,182784],{"href":182782,"rel":182783},"https://openjdk.org/projects/jdk/21/",[816],"JDK 21"," is set to be released tomorrow. There are a total of 15 JEPs (Java Enhancement Proposals) included with this release and they are listed below. If you want to learn more about each of them you can click on the links below. The JEP contains a lot of information such as the history, the motivation behind it and the details for the feature.",[1031,182787,182788,182802],{},[1034,182789,182790],{},[1037,182791,182792,182797],{},[1040,182793,182794],{},[2939,182795,182796],{},"JEP",[1040,182798,182799],{},[2939,182800,182801],{},"Feature",[1050,182803,182804,182816,182828,182840,182852,182864,182876,182888,182898,182910,182922,182934,182946,182958,182970],{},[1037,182805,182806,182809],{},[1055,182807,182808],{},"430:",[1055,182810,182811],{},[812,182812,182815],{"href":182813,"rel":182814},"https://openjdk.org/jeps/430",[816],"String Templates (Preview)",[1037,182817,182818,182821],{},[1055,182819,182820],{},"431:",[1055,182822,182823],{},[812,182824,182827],{"href":182825,"rel":182826},"https://openjdk.org/jeps/431",[816],"Sequenced Collections",[1037,182829,182830,182833],{},[1055,182831,182832],{},"439:",[1055,182834,182835],{},[812,182836,182839],{"href":182837,"rel":182838},"https://openjdk.org/jeps/439",[816],"Generational ZGC",[1037,182841,182842,182845],{},[1055,182843,182844],{},"440:",[1055,182846,182847],{},[812,182848,182851],{"href":182849,"rel":182850},"https://openjdk.org/jeps/440",[816],"Record Patterns",[1037,182853,182854,182857],{},[1055,182855,182856],{},"441:",[1055,182858,182859],{},[812,182860,182863],{"href":182861,"rel":182862},"https://openjdk.org/jeps/441",[816],"Pattern Matching for switch",[1037,182865,182866,182869],{},[1055,182867,182868],{},"442:",[1055,182870,182871],{},[812,182872,182875],{"href":182873,"rel":182874},"https://openjdk.org/jeps/442",[816],"Foreign Function and Memory API (Third Preview)",[1037,182877,182878,182881],{},[1055,182879,182880],{},"443:",[1055,182882,182883],{},[812,182884,182887],{"href":182885,"rel":182886},"https://openjdk.org/jeps/443",[816],"Unnamed Patterns and Variables (Preview)",[1037,182889,182890,182893],{},[1055,182891,182892],{},"444:",[1055,182894,182895],{},[812,182896,117624],{"href":104628,"rel":182897},[816],[1037,182899,182900,182903],{},[1055,182901,182902],{},"445:",[1055,182904,182905],{},[812,182906,182909],{"href":182907,"rel":182908},"https://openjdk.org/jeps/445",[816],"Unnamed Classes and Instance Main Methods (Preview)",[1037,182911,182912,182915],{},[1055,182913,182914],{},"446:",[1055,182916,182917],{},[812,182918,182921],{"href":182919,"rel":182920},"https://openjdk.org/jeps/446",[816],"Scoped Values (Preview)",[1037,182923,182924,182927],{},[1055,182925,182926],{},"448:",[1055,182928,182929],{},[812,182930,182933],{"href":182931,"rel":182932},"https://openjdk.org/jeps/448",[816],"Vector API (Sixth Incubator)",[1037,182935,182936,182939],{},[1055,182937,182938],{},"449:",[1055,182940,182941],{},[812,182942,182945],{"href":182943,"rel":182944},"https://openjdk.org/jeps/449",[816],"Deprecate the Windows 32-bit x86 Port for Removal",[1037,182947,182948,182951],{},[1055,182949,182950],{},"451:",[1055,182952,182953],{},[812,182954,182957],{"href":182955,"rel":182956},"https://openjdk.org/jeps/451",[816],"Prepare to Disallow the Dynamic Loading of Agents",[1037,182959,182960,182963],{},[1055,182961,182962],{},"452:",[1055,182964,182965],{},[812,182966,182969],{"href":182967,"rel":182968},"https://openjdk.org/jeps/452",[816],"Key Encapsulation Mechnaism API",[1037,182971,182972,182975],{},[1055,182973,182974],{},"453:",[1055,182976,182977],{},[812,182978,182981],{"href":182979,"rel":182980},"https://openjdk.org/jeps/453",[816],"Structured Concurrency (Preview)",[651,182983,182984,182985,182989],{},"If you're interested in hearing my thoughts on this, we will cover the release on ",[812,182986,97345],{"href":182987,"rel":182988},"https://www.youtube.com/watch?v=TAliLDYe20M",[816]," this week. Additionally, I hope to release a few videos on the YouTube channel over the next few weeks.",[651,182991,182992],{},"This is an exciting development for the Java community. As a Spring Developer, I'm particularly enthusiastic about Virtual Threads and how they offer a new way to scale Spring MVC applications.",[4542,182994,118347],{"id":118516},[651,182996,182997,182998,183001,183002,183006],{},"Last week I created a tutorial on the new JDBC Client in Spring Framework. Spring Boot 3.2 will add auto-configuration for the new client that is being released in Spring Framework 6.1. This is a follow up to my tutorial on the other new client, the ",[812,182999,144990],{"href":182573,"rel":183000},[816],". If you want to learn more about it you can check out this ",[812,183003,24879],{"href":183004,"rel":183005},"https://www.danvega.dev/blog/2023/09/11/spring-jdbc-client/",[816]," or the video below.",[5988,183008],{"id":118936},[651,183010,183011],{},"And here are the 3 shorts I created for the JDBC Client tutorial:",[5316,183013,183014,183021,183028],{},[5332,183015,183016],{},[812,183017,183020],{"href":183018,"rel":183019},"https://youtube.com/shorts/Sv0-b5rsw08?feature=share",[816],"JDBC Template vs JDBC Client",[5332,183022,183023],{},[812,183024,183027],{"href":183025,"rel":183026},"https://youtube.com/shorts/66O-PSw25fU?feature=share",[816],"JDBC Client First Impressions",[5332,183029,183030],{},[812,183031,183034],{"href":183032,"rel":183033},"https://youtube.com/shorts/IYi2hHpO9W8?feature=share",[816],"Working with JDBC in Java and Spring",[4542,183036,117259],{"id":117563},[651,183038,183039],{},"I started off this particular piece of content by asking Spring Boot Developers what the #1 Spring Boot Starter is that should be included in every single application. There really is no right answer and it’s purely an opinion but I believe it is the Spring Boot Actuator.",[651,183041,183042],{},"The Spring Boot Actuator gives us a number of features that help us move our applications to production. In this tutorial I take a look at what you get out of the box with the Actuator and what you can do to customize it. I also take a look at how you can customize the info endpoint and create your own to provide whatever data you need to other applications.",[651,183044,183045,183046,183006],{},"If you’re interested you can check out this ",[812,183047,24879],{"href":183048,"rel":183049},"https://www.danvega.dev/blog/2023/09/17/spring-boot-actuator/",[816],[5988,183051],{"id":183052},"4OVe0MWgZ4k",[651,183054,183055],{},"As I talked about last week I’m trying to create shorts related to each of my long form videos. Here are the 3 shorts I created for the Spring Boot Actuator tutorial:",[5316,183057,183058,183065,183072],{},[5332,183059,183060],{},[812,183061,183064],{"href":183062,"rel":183063},"https://youtube.com/shorts/1otiGkwk76s?feature=share",[816],"The #1 Spring Boot Starter",[5332,183066,183067],{},[812,183068,183071],{"href":183069,"rel":183070},"https://youtube.com/shorts/PLuqBHzciT4?feature=share",[816],"Spring Boot Actuator helps us move to production",[5332,183073,183074],{},[812,183075,183078],{"href":183076,"rel":183077},"https://youtube.com/shorts/LCqIQ5YorgI?feature=share",[816],"Create a new app with Spring Boot Actuator",[4542,183080,183082],{"id":183081},"springone-tour-virtual","SpringOne Tour Virtual",[651,183084,183085],{},"We’re still buzzing from the amazing energy at SpringOne at VMware Explore. If you made it out to Las Vegas, we were thrilled to have you be a part of the vibrant Spring developer community. If you weren’t able to make the event, there are still opportunities to learn from industry experts and connect.",[651,183087,183088],{},"The first stop on our SpringOne Tour is a virtual session where we’ll be featuring the latest advancements, best practices, and tools and techniques that are quickly becoming industry-standard. Our technical advocates, Spring engineers, and application development experts will bring you an in-depth look into the beauty of open source, with Spring Framework, Spring Boot 3, Kubernetes, Progressive Delivery and more, so you can innovate faster.",[651,183090,183091,183092,664],{},"Join us virtually on October 17, 2023 by ",[812,183093,183096],{"href":183094,"rel":183095},"https://springonetour.io/",[816],"registering for free using this link",[4542,183098,177889],{"id":157573},[5909,183100,164959],{"id":69848},[651,183102,183103],{},"I really enjoyed this article by Chris Seaton on the 10 things to do with GraalVM. You have probably heard of by GraalVM by now but maybe you aren’t quite sure what the use cases are. Chris has you covered as he walks through 10 things you can do with GraalVM.",[651,183105,183106,183107,664],{},"Java 21 will be released tomorrow and I can’t think of a better way to catch up on all the new features than by reading ",[812,183108,183111],{"href":183109,"rel":183110},"https://blogs.oracle.com/javamagazine/post/java-inside-21-features",[816],"Nicolai Parlog’s Inside article",[5909,183113,164971],{"id":157591},[651,183115,183116,183117,183122],{},"Speaking of Java 21 there is going to be an ",[812,183118,183121],{"href":183119,"rel":183120},"https://dev.java/community/java-21-launch/",[816],"8 hour live stream release party"," by our friends at Oracle. That is right, 8 hours of everything Java 21 and I can’t wait to check this out. If you want to learn all about Java 21 you need to check this out.",[5909,183124,164983],{"id":39439},[651,183126,183127,183128,664],{},"I really love to learn and one of my main sources for learning is through podcasts. I love to listen to software and business related podcasts but my feed is becoming pretty stale and I need some new podcasts to listen to. If you have some favorites in these areas or any other ones you find interesting please tell me about them. You can reply to this email or reach out to me on ",[812,183129,51474],{"href":51472,"rel":183130},[816],[5909,183132,165017],{"id":165016},[651,183134,183135],{},"“The truth is that stress doesn’t come from your boss, your kids, your spouse, traffic jams, health challenges, or other circumstances. It comes from your thoughts about your circumstances.”",[651,183137,183138],{},[2939,183139,183140],{},"―Andrew Bernstein",[5909,183142,178573],{"id":95961},[651,183144,183145],{},"Java is so cool that the most popular YouTuber on the planet is hiring Java developers.",[651,183147,183148],{},[812,183149,183150],{"href":183150,"rel":183151},"https://twitter.com/therealdanvega/status/1702763200557560217",[816],[4542,183153,157704],{"id":157703},[651,183155,178368,183156,166271],{},[812,183157,41499],{"href":44086,"rel":183158},[816],[651,183160,41105,183161,69920,183163,183165,183167,183169],{},[41107,183162],{},[41107,183164],{},[812,183166,161560],{"href":161111},[41107,183168],{},[812,183170,53869],{"href":53869,"rel":183171},[816],{"title":674,"searchDepth":790,"depth":790,"links":183173},[183174,183175,183176,183177,183178,183185],{"id":182775,"depth":790,"text":182776},{"id":118516,"depth":790,"text":118347},{"id":117563,"depth":790,"text":117259},{"id":183081,"depth":790,"text":183082},{"id":157573,"depth":790,"text":177889,"children":183179},[183180,183181,183182,183183,183184],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":183187,"date":183188},"java-21","2023-09-18T07:00:00.000Z","/newsletter/2023/09/18/java-21",{"title":182755,"description":182760},"newsletter/2023/09/18/java-21","1N-564LLkj9DsHo2qXy-L-F-CRu9kwChcfFkWmNf5qw",{"id":183194,"title":183195,"body":183196,"description":183200,"extension":793,"meta":183367,"navigation":797,"path":183369,"seo":183370,"stem":183371,"__hash__":183372},"content/newsletter/2023/09/25/spring-boot-code-structure.md","Spring Boot Code Structure, Java 21, and a Podcast Interview with Java Brains!",{"type":648,"value":183197,"toc":183353},[183198,183201,183207,183211,183220,183229,183232,183235,183244,183247,183250,183254,183257,183265,183268,183271,183275,183278,183281,183283,183285,183287,183292,183294,183296,183304,183306,183314,183316,183324,183326,183333,183335,183340],[651,183199,183200],{},"Happy Monday and welcome to another edition of the newsletter. Last week was busy as I engaged in activities I love, which I would like to share with all of you. It was an exciting week with the release of Java 21. I spent a lot of time tweeting and discussing this exciting release with all of you. Now that Java 21 is out, we can look forward to the release of Spring Boot 3.2 in November. This will allow us to start using Virtual Threads in production. In the meantime, you can already try out Spring Boot 3.2 Milestones and Java 21 for yourself!",[651,183202,183203],{},[660,183204],{"alt":183205,"src":183206},"Java 21 & Spring Boot 3.2","/images/newsletter/2023/09/25/spring-init-java-21.png",[4542,183208,183210],{"id":183209},"spring-boot-code-structure","Spring Boot Code Structure",[651,183212,183213,183214,183219],{},"This is a tutorial I had in my backlog for a long time and I finally got around to making it. I decided to throw out a ",[812,183215,183218],{"href":183216,"rel":183217},"https://twitter.com/therealdanvega/status/1704867889004564622",[816],"tweet last week"," asking everyone how they structure code in their Spring Boot Applications. With over 120 comments I received a lot of really great feedback.",[651,183221,183222,183223,183228],{},"I created the following tutorial to walk through 2 ways that you can structure your code, Package by Layer and Package by Feature. I’m going to be diving into other architecture choices including tomorrow when I sit down with my friend ",[812,183224,183227],{"href":183225,"rel":183226},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0053/",[816],"Ted Young on Spring Office Hours"," to talk about Hexagonal Architecture.",[5988,183230],{"id":183231},"B1d95I7-zsw",[4542,183233,183234],{"id":183187},"Java 21",[651,183236,183237,183238,183243],{},"Java 21 was released on Tuesday, and I spent most of the day consuming all the content I could. My friends at Oracle had an 8-hour release party on ",[812,183239,183242],{"href":183240,"rel":183241},"http://dev.java/",[816],"dev.java",", and I tried to watch as much as possible. If you missed it, you can check out the recording and catch up on all the exciting news about this release.",[651,183245,183246],{},"I decided to create a quick tutorial on how to get started with Java 21. In this video, I also discuss the JEPs included in this release and how to use preview features. I'm a big fan of Java 21 and plan to create a lot of content about it in the coming months.",[5988,183248],{"id":183249},"aqc5YB7TISM",[4542,183251,183253],{"id":183252},"spring-boot-observability","Spring Boot Observability",[651,183255,183256],{},"I have been giving talks all over about Spring Boot 3 and beyond. One of the topics I cover is the complete revamp of Observability in Spring Boot 3, marking the beginning of a new Observability story.",[651,183258,183259,183260,183264],{},"In the last newsletter, I introduced ",[812,183261,117259],{"href":183262,"rel":183263},"https://youtu.be/4OVe0MWgZ4k",[816],", which sets the stage for this tutorial. In this tutorial, we delve deeper into the Actuator by exploring the metrics endpoint.",[651,183266,183267],{},"Through this tutorial, you will learn how to enable observability in your applications by including the Spring Boot Actuator. Additionally, you will discover how to view traces using a tool like Zipkin, and finally, how to utilize the Observation API and create your own observations.",[5988,183269],{"id":183270},"exRkiVLyPpc",[4542,183272,183274],{"id":183273},"java-brains-podcast","Java Brains Podcast",[651,183276,183277],{},"There are some really great educators in the Java space and I was lucky enough to spend some time last week with one of my favorite content creators Kouth AKA Java Brains. We sat down and recorded an episode of his podcast and wow did I have some fun. I think we ended up talking for close to 90 minutes and probably could have spent the rest of the day talking.",[5988,183279],{"id":183280},"HTS8Yfos-Ns",[4542,183282,183082],{"id":183081},[651,183284,183085],{},[651,183286,183088],{},[651,183288,183091,183289,664],{},[812,183290,183096],{"href":183094,"rel":183291},[816],[4542,183293,177889],{"id":157573},[5909,183295,164959],{"id":69848},[651,183297,183298,183299,183303],{},"Shortly after Java 21 was released GraalVM for JDK 21 was released. I enjoyed ",[812,183300,109096],{"href":183301,"rel":183302},"https://medium.com/graalvm/graalvm-for-jdk-21-is-here-ee01177dd12d",[816]," by Alina Yurenko and was particularly intrigued by the section that talked about the speed of JIT vs AOT.",[5909,183305,164971],{"id":157591},[651,183307,178347,183308,183313],{},[812,183309,183312],{"href":183310,"rel":183311},"https://www.youtube.com/watch?v=U6s2pdxebSo&t=4386s&pp=ygUWdHlwZXNjcmlwdCBkb2N1bWVudGFyeQ%3D%3D",[816],"this documentary on TypeScript",". It was really great to hear behind the scenes stories of how they got started with it Microsoft and how they used it to build VS Code. I’m a big fan of TypeScript and It got me excited to find a way to use it more.",[5909,183315,164983],{"id":39439},[651,183317,178347,183318,183323],{},[812,183319,183322],{"href":183320,"rel":183321},"https://bootifulpodcast.fm/#/episodes/16aff4f7-4a2f-4823-ac21-b9fbf15bd914",[816],"this episode of the Bootiful Podcast"," with Sharat Chander. In this episode 2 legends (and friends of mine) sit down to talk about past, present and future of Java.",[5909,183325,165017],{"id":165016},[651,183327,183328],{},[2939,183329,183330],{},[2939,183331,183332],{},"“Instead of worrying about what you cannot control, shift your energy to what you can create.” – Roy T. Bennett",[4542,183334,157704],{"id":157703},[651,183336,178368,183337,166271],{},[812,183338,41499],{"href":44086,"rel":183339},[816],[651,183341,41105,183342,69920,183344,183346,183348,183350],{},[41107,183343],{},[41107,183345],{},[812,183347,161560],{"href":161111},[41107,183349],{},[812,183351,53869],{"href":53869,"rel":183352},[816],{"title":674,"searchDepth":790,"depth":790,"links":183354},[183355,183356,183357,183358,183359,183360,183366],{"id":183209,"depth":790,"text":183210},{"id":183187,"depth":790,"text":183234},{"id":183252,"depth":790,"text":183253},{"id":183273,"depth":790,"text":183274},{"id":183081,"depth":790,"text":183082},{"id":157573,"depth":790,"text":177889,"children":183361},[183362,183363,183364,183365],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":183209,"date":183368},"2023-09-25T07:00:00.000Z","/newsletter/2023/09/25/spring-boot-code-structure",{"title":183195,"description":183200},"newsletter/2023/09/25/spring-boot-code-structure","TPQoOGCz6zLBHoOAysLeak7it750xCK9987WO6FWR5Q",{"id":183374,"title":183375,"body":183376,"description":183380,"extension":793,"meta":183541,"navigation":797,"path":183544,"seo":183545,"stem":183546,"__hash__":183547},"content/newsletter/2023/10/02/terrific-development-dan.md","Docker Compose, TDD in Spring and Upcoming Speaking Engagements",{"type":648,"value":183377,"toc":183528},[183378,183381,183384,183387,183390,183393,183396,183400,183415,183418,183421,183424,183427,183430,183434,183442,183465,183467,183469,183471,183476,183478,183480,183488,183490,183495,183497,183500,183506,183508,183513],[651,183379,183380],{},"Happy Monday and welcome to another edition of the newsletter. This week will be a short week for me as we have a company day off this Friday. I have mentioned this before, but I truly enjoy working for VMware. They truly embody their company culture and pay attention to the small details.",[651,183382,183383],{},"In this episode, I would like to discuss Docker Compose, TDD in Spring, and some upcoming speaking engagements.",[4542,183385,180036],{"id":183386},"docker-compose",[651,183388,183389],{},"Last week, while putting together a small demo, I encountered an interesting issue. I was using the new Docker Compose module in Spring Boot to connect to a PostgreSQL database. The application was functioning perfectly, but when I attempted to connect to the database using the database tools in IntelliJ, I couldn't establish a connection.",[651,183391,183392],{},"Initially, I considered the possibility that the issue was related to Docker. I wondered if I needed to connect to the internal IP address of the containers instead of using localhost. However, that wasn't the problem either. After some head scratching, I eventually identified the cause of the issue. To help you understand what went wrong and how I resolved it, I have created the following tutorial on connecting to a database inside a Docker container.",[5988,183394],{"id":183395},"NOrwxSI_VIg",[4542,183397,183399],{"id":183398},"spring-boot-testing","Spring Boot Testing",[651,183401,183402,183403,183408,183409,183414],{},"A few weeks ago, I created a tutorial on ",[812,183404,183407],{"href":183405,"rel":183406},"https://youtu.be/B1d95I7-zsw",[816],"how to structure your code"," in a Spring Boot application. Last week, I had the opportunity to continue the conversation on the ",[812,183410,183413],{"href":183411,"rel":183412},"https://tanzu.vmware.com/developer/tv/spring-office-hours",[816],"Podcast"," by inviting my friend Ted Young as a guest.",[651,183416,183417],{},"We started off the podcast by summarizing what I covered in the video, which focused on organizing code by package, either by layer or by feature. I was eager to hear Ted's perspective on this topic, and he provided some valuable insights.",[651,183419,183420],{},"Next, we delved into Hexagonal Architecture, an approach that Ted is a strong advocate for and frequently discusses. Although I haven't personally used this approach in my Spring Boot applications, I can see several benefits, particularly in terms of testing.",[651,183422,183423],{},"Inspired by watching a few videos on Ted's channel, I decided to create some testing videos of my own. The first video in this series, which focuses on Test Driven Development (TDD) in Spring, has already been published.",[5988,183425],{"id":183426},"-H5sud1-K5A",[651,183428,183429],{},"I have already recorded a follow up to this video where we will go through integration testing with Testcontainers and that should be released today!",[4542,183431,183433],{"id":183432},"upcoming-conferences","Upcoming Conferences",[651,183435,183436,183437,183441],{},"This is going to be a busy few months to wrap up the year. Not only do I have everything that’s going on below but I also have bunch of in-person customer meetings that I am really looking forward. I will be ",[812,183438,183440],{"href":120449,"rel":183439},[816],"speaking"," at the following conferences so if you are in attendance please come say hi 👋🏻",[5316,183443,183444,183450,183458],{},[5332,183445,183446,183449],{},[812,183447,182660],{"href":182666,"rel":183448},[816]," - October 24-26 2023",[5332,183451,183452,183457],{},[812,183453,183456],{"href":183454,"rel":183455},"https://www.vmware.com/explore/eu.html",[816],"VMware Barcelona"," - November 6-9 2023",[5332,183459,183460,183464],{},[812,183461,97312],{"href":183462,"rel":183463},"https://codemash.org/",[816]," - January 9-12 2024",[5909,183466,183082],{"id":183081},[651,183468,183085],{},[651,183470,183088],{},[651,183472,183091,183473,664],{},[812,183474,183096],{"href":183094,"rel":183475},[816],[4542,183477,177889],{"id":157573},[5909,183479,164959],{"id":69848},[651,183481,183482,183483,183487],{},"I enjoyed ",[812,183484,109096],{"href":183485,"rel":183486},"https://stuartmarks.wordpress.com/2023/09/22/my-favorite-jdk-21-feature-javadoc-search-url/",[816]," by Stuart Marks where he talks about his favorite feature in JDK 21. The Javadoc Search got an upgrade and it’s own URL which means you can easily interact it with it using your own tools.",[5909,183489,165017],{"id":165016},[1004,183491,183492],{},[651,183493,183494],{},"“The best way to appreciate your job is to imagine yourself without one.” – Oscar Wilde",[5909,183496,178573],{"id":95961},[651,183498,183499],{},"I’m really looking forward to a world where I can stand in a virtual room and teach all of the things I love to those willing to listen 🤩",[651,183501,183502],{},[812,183503,183504],{"href":183504,"rel":183505},"https://twitter.com/therealdanvega/status/1707458829552685275",[816],[4542,183507,157704],{"id":157703},[651,183509,178368,183510,166271],{},[812,183511,41499],{"href":44086,"rel":183512},[816],[651,183514,41105,183515,69920,183517,183519,183521,183523,183526],{},[41107,183516],{},[41107,183518],{},[812,183520,161560],{"href":161111},[41107,183522],{},[812,183524,53869],{"href":53869,"rel":183525},[816],[41107,183527],{},{"title":674,"searchDepth":790,"depth":790,"links":183529},[183530,183531,183532,183535,183540],{"id":183386,"depth":790,"text":180036},{"id":183398,"depth":790,"text":183399},{"id":183432,"depth":790,"text":183433,"children":183533},[183534],{"id":183081,"depth":892,"text":183082},{"id":157573,"depth":790,"text":177889,"children":183536},[183537,183538,183539],{"id":69848,"depth":892,"text":164959},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":183542,"date":183543},"terrific-development-dan","2023-10-02T07:00:00.000Z","/newsletter/2023/10/02/terrific-development-dan",{"title":183375,"description":183380},"newsletter/2023/10/02/terrific-development-dan","gOEELSb6hHJU1X-ZYs2CvUC9zRx_Q028jrHiGP87MJ8",{"id":183549,"title":183550,"body":183551,"description":183555,"extension":793,"meta":183807,"navigation":797,"path":183809,"seo":183810,"stem":183811,"__hash__":183812},"content/newsletter/2023/10/09/testing-spring-boot-applications.md","Testing Spring Boot Applications, Spring Cloud Gateway and Devoxx",{"type":648,"value":183552,"toc":183792},[183553,183556,183559,183563,183571,183581,183584,183587,183590,183592,183595,183598,183601,183606,183609,183668,183670,183679,183685,183693,183695,183700,183717,183719,183721,183723,183728,183730,183732,183749,183751,183754,183756,183761,183763,183766,183772,183774,183779],[651,183554,183555],{},"Happy Monday and welcome to another edition of the newsletter. I’m in the middle of a pretty stressful month some of which I won’t get into here but If I’m being honest I’m struggling a bit but I am doing my best to push forward. I have a bunch of presentations at upcoming events I’m preparing for and I couldn't be more excited about all of them.",[651,183557,183558],{},"In this episode I want to talk to you about testing Spring Boot applications, Spring Cloud Gateway and more.",[4542,183560,183562],{"id":183561},"testing-spring-boot-applications","Testing Spring Boot Applications",[651,183564,183565,183566,183570],{},"I recently had a discussion about testing Spring Boot applications during ",[812,183567,97345],{"href":183568,"rel":183569},"https://www.youtube.com/watch?v=PdXcInQ1SjA",[816]," with DaShaun. One aspect of Spring Boot that I particularly appreciate is that testing is built-in and doesn't require any additional setup.",[651,183572,183573,183574,183577,183578,183580],{},"When you create a new project on ",[812,183575,77478],{"href":20748,"rel":183576},[816],", the ",[676,183579,39625],{}," is automatically included in your application. In the mentioned episode and the video provided below, I explain what is included in this starter. Understanding the components that come with Spring Boot makes it easier to comprehend the purpose of tests and the resources available to you for writing them.",[5988,183582],{"id":183583},"rUbjV3VY1DI",[651,183585,183586],{},"I'm also a big fan of Testcontainers and how it simplifies the process of doing the right thing. We all understand the importance of writing tests, but if integration tests take too long, we are less motivated to write them. In this tutorial, I will guide you on how to get started with Testcontainers in Spring Boot. Additionally, we will explore how the changes in Spring Boot 3.1 make this process even easier.",[5988,183588],{"id":183589},"erp-7MCK5BU",[4542,183591,181208],{"id":181207},[651,183593,183594],{},"Spring Cloud Gateway is a topic I have wanted to create a tutorial on for ahwile now and last week I finally got around to doing it. I’m building out a service called DanSON Placeholder Service (yes, just like JSON Placeholder Service) and I thought this would be a really good way to show off an API Gateway and specifically Spring Cloud Gateway.",[5988,183596],{"id":183597},"EKoq98KqvrI",[651,183599,183600],{},"I put together this graphic and I used it during my tutorial. I thought I would share it here in case you find useful.",[651,183602,183603],{},[660,183604],{"alt":181208,"src":183605},"/images/newsletter/2023/10/09/scg-diagram.png",[651,183607,183608],{},"This was an introduction to Spring Cloud Gateway, but it is certainly not the end of this series. I asked you guys on Twitter if you had any questions about API Gateways or SCG, and you came up with some really great questions. Here is a list of those questions, and we still need to answer the majority of them.",[5316,183610,183611,183614,183617,183620,183623,183626,183629,183632,183635,183638,183641,183644,183647,183650,183653,183656,183659,183662,183665],{},[5332,183612,183613],{},"Does it make sense to have a Gateway when deploying on Kubernetes or any other orchestration with some sort of service discovery?",[5332,183615,183616],{},"The company I work for runs Spring Boot services within a Kubernetes cluster. My question is: Is it better to use Ingress (nginx) or Spring Cloud Gateway to route user requests? Thanks.",[5332,183618,183619],{},"Do Virtual Threads make any difference? Does a non-Project Reactor implementation exist that can use Virtual Threads?",[5332,183621,183622],{},"SCG --> Kafka would be exciting",[5332,183624,183625],{},"Hi Dan, can Spring Cloud Gateway be integrated with Microservices written in languages other than Java? Thx",[5332,183627,183628],{},"Can you compare it with other similar tools like Envoy?",[5332,183630,183631],{},"I would love for you to do a tutorial on this and how it links up to event-driven architecture",[5332,183633,183634],{},"Do you need to scale SCG because of the services behind it? Imagine I need to scale one of the services that has very high throughput, does SCG need to scale as well? Otherwise, would it be a bottleneck?",[5332,183636,183637],{},"I've always wanted to leverage Spring Cloud Gateway to build something that validates against an OpenAPI spec and logs req+res with tags on the operations in the OpenAPI spec.",[5332,183639,183640],{},"What are the differences between Spring Cloud Gateway and Netflix Zuul?",[5332,183642,183643],{},"Is it okay to have Spring Cloud Gateway along with microservices as a single repository and single service?",[5332,183645,183646],{},"Integrate role-based access control with API gateway microservice architecture",[5332,183648,183649],{},"How to implement audit logs in SCG?",[5332,183651,183652],{},"Yes, what would be the best way to document my gateway API with OpenAPI? It becomes complicated to implement this so that it works accepting requests from Swagger and from external clients (Postman).",[5332,183654,183655],{},"SCG is great, but I always had a bit of trouble distinguishing the various OAuth2 flows. Resource Server, M2M, browser flow, etc. Some worked examples would be nice.",[5332,183657,183658],{},"Can you spare some thoughts on Inter Web-Services on Microservices design with a few examples?",[5332,183660,183661],{},"Would be awesome if you can implement JWT using spring security on API gateway.",[5332,183663,183664],{},"Open API",[5332,183666,183667],{},"Should you pass JWT token to a microservice, or you can verify it on Gateway and just made a non-secured request to an endpoint?",[4542,183669,36460],{"id":36459},[651,183671,183672,183673,183678],{},"Devoxx Belgium took place last week, and I experienced some serious FOMO while watching all the buzz surrounding the conference. It was really cool to see the GraalVM Native Image panel discussion, especially since they used one of my tweets as a talking point. I want to express my gratitude to ",[812,183674,183677],{"href":183675,"rel":183676},"https://twitter.com/vitalethomas/status/1709563387267387581/photo/1",[816],"Thomas Vitale"," for bringing this to my attention.",[651,183680,183681],{},[660,183682],{"alt":183683,"src":183684},"GraalVM Tweet","/images/newsletter/2023/10/09/graalvm-tweet.png",[651,183686,183687,183688,183692],{},"I was impressed by the quickness with which they posted the conference recordings on their ",[812,183689,101297],{"href":183690,"rel":183691},"https://www.youtube.com/@DevoxxForever",[816],". There is a lengthy list of talks that I am interested in watching, but I intend to save them for my trip to Barcelona next month.",[4542,183694,183433],{"id":183432},[651,183696,183436,183697,183441],{},[812,183698,183440],{"href":120449,"rel":183699},[816],[5316,183701,183702,183707,183712],{},[5332,183703,183704,183449],{},[812,183705,182660],{"href":182666,"rel":183706},[816],[5332,183708,183709,183457],{},[812,183710,183456],{"href":183454,"rel":183711},[816],[5332,183713,183714,183464],{},[812,183715,97312],{"href":183462,"rel":183716},[816],[5909,183718,183082],{"id":183081},[651,183720,183085],{},[651,183722,183088],{},[651,183724,183091,183725,664],{},[812,183726,183096],{"href":183094,"rel":183727},[816],[4542,183729,177889],{"id":157573},[5909,183731,164959],{"id":69848},[5316,183733,183734,183741],{},[5332,183735,178347,183736,183740],{},[812,183737,109096],{"href":183738,"rel":183739},"https://glaforge.dev/posts/2023/10/02/client-side-consumption-of-a-rate-limited-api-in-java/",[816]," by Guillaume Laforge where he discusses client-side consumption of rate-limited APIs in Java. Guillaume walks us through how rate limiting works and some libraries in Java that can help us with that such as Bucket4J and Resilience4J.",[5332,183742,166728,183743,183748],{},[812,183744,183747],{"href":183745,"rel":183746},"https://auth0.com/blog/how-to-build-a-graphql-api-with-spring-boot/",[816],"good article"," on the Auth0 blog by Jimena Garbarino on How to build a GraphQL API with Spring Boot. This is a complete walkthrough of building a GraphQL API in Spring and consuming it in React with Next.js",[5909,183750,164971],{"id":157591},[651,183752,183753],{},"I really enjoyed this video by my friend and coworker Josh Long. In this tutorial Josh talks about how different it is to get started with Java today as opposed to when he started. He walks through some of the tooling in the Java ecosystem and how to get started.",[5909,183755,165017],{"id":165016},[1004,183757,183758],{},[651,183759,183760],{},"“Work harder on yourself than you do on your job.” – Jim Rohn",[5909,183762,178573],{"id":95961},[651,183764,183765],{},"I had testing on my mind last week and decided to put a quick Twitter poll together to see what Assertion library Java users were using and here are the results.",[651,183767,183768],{},[812,183769,183770],{"href":183770,"rel":183771},"https://twitter.com/therealdanvega/status/1710041316703219883",[816],[4542,183773,157704],{"id":157703},[651,183775,178368,183776,166271],{},[812,183777,41499],{"href":44086,"rel":183778},[816],[651,183780,41105,183781,69920,183783,183785,183787,183789],{},[41107,183782],{},[41107,183784],{},[812,183786,161560],{"href":161111},[41107,183788],{},[812,183790,53869],{"href":53869,"rel":183791},[816],{"title":674,"searchDepth":790,"depth":790,"links":183793},[183794,183795,183796,183797,183800,183806],{"id":183561,"depth":790,"text":183562},{"id":181207,"depth":790,"text":181208},{"id":36459,"depth":790,"text":36460},{"id":183432,"depth":790,"text":183433,"children":183798},[183799],{"id":183081,"depth":892,"text":183082},{"id":157573,"depth":790,"text":177889,"children":183801},[183802,183803,183804,183805],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":183561,"date":183808},"2023-10-09T07:00:00.000Z","/newsletter/2023/10/09/testing-spring-boot-applications",{"title":183550,"description":183555},"newsletter/2023/10/09/testing-spring-boot-applications","-BeUq7X4GjO90lTqlw4MGqpI7NLkLu25ohWsfsOjARU",{"id":183814,"title":183815,"body":183816,"description":183820,"extension":793,"meta":183926,"navigation":797,"path":183929,"seo":183930,"stem":183931,"__hash__":183932},"content/newsletter/2023/10/30/dan-vega-nuxt.md","Website Redesign, VMware Explore Barcelona and Happy Halloween 🎃",{"type":648,"value":183817,"toc":183919},[183818,183821,183824,183830,183833,183836,183840,183853,183860,183866,183872,183879,183883,183886,183889,183891,183897,183899,183904],[651,183819,183820],{},"Happy Monday and welcome to another edition of the newsletter. My apologies for the recent silence; I've had a busy few weeks of traveling and presenting. Two weeks ago, I was in Seattle with my friend DaShaun for a productive customer meeting where I discussed Spring Boot 3 and the upcoming features in Spring Boot 3.2.",[651,183822,183823],{},"Last week, I attended and spoke at Connect Tech in Atlanta, a first-time experience for me. It was really great to see so many familiar faces and learn from some of the best the frontend development community has to offer. I was also lucky enough to grab some dinner and catch up with my friends Jordan and Erik. We had dinner at an amazing restaurant called Blue India thanks to a recommendation from Todd Libby.",[651,183825,183826],{},[660,183827],{"alt":183828,"src":183829},"Blue India","/images/newsletter/2023/10/30/blue-india.jpeg",[651,183831,183832],{},"Finally, I want to thank Pratik Patel and Vincent Myers for putting together another amazing conference. They do a bunch of work on the conference scene and in the development community and we are lucky to have them.",[651,183834,183835],{},"Happy Halloween to everyone who celebrates. 🎃",[4542,183837,183839],{"id":183838},"website-redesign","Website Redesign",[651,183841,183842,183843,183847,183848,183852],{},"I'm excited to announce that I've updated ",[812,183844,183846],{"href":82688,"rel":183845},[816],"my website",". While I've been planning this for some time, I wanted to use Nuxt 3 for the update. It's hard to believe I've been blogging for nearly 20 years now. If you're interested in the origins of this blog, you can read ",[812,183849,119291],{"href":183850,"rel":183851},"https://www.danvega.dev/blog/2019/04/11/dont-call-it-a-comback",[816]," from about four years ago.",[651,183854,183855,183856,183859],{},"For the new blog, I used a template called Spotlight from ",[812,183857,120274],{"href":120272,"rel":183858},[816],". The template was designed for React/Next, so I had to convert it. I'm not familiar with either, which made this project interesting. Now that I understand React at a high level, I have a question for those who use it: are you aware of other options, like Vue?",[651,183861,183862],{},[660,183863],{"alt":183864,"src":183865},"Website Redesign Light Theme","/images/newsletter/2023/10/30/redesign-light.png",[651,183867,183868],{},[660,183869],{"alt":183870,"src":183871},"Website Redesign Dark Theme","/images/newsletter/2023/10/30/redesign-dark.png",[651,183873,183874,183875,183878],{},"I’m really proud with how clean this is and how the initial rollout has been. I put this together pretty quickly and I still have a lot to do but I felt it was good enough to get it out there. I’m going to do a video on this as well where I cover some of the features so stay tuned for that. If there is anything in particular you want to know about you can check out the ",[812,183876,74069],{"href":120278,"rel":183877},[816]," for this site here and as always feel free to ask questions.",[4542,183880,183882],{"id":183881},"vmware-explore-barcelona","VMware Explore Barcelona",[651,183884,183885],{},"Next week, I will be in Barcelona, Spain for VMware Explore. My coworker Cora and I will give a presentation on Accelerating Innovation with Spring. In this presentation, we will explore the newest features of the Spring Framework 6.x and Spring Boot 3.x releases.",[651,183887,183888],{},"I am also presenting on GraphQL at the Code Theater. This presentation is unique because it won't include any slides and will feature live coding for about 20 minutes. This will be my first time visiting Barcelona and I'm extremely excited about the conference. If you’re going to be in attendance please be sure to come by and say hi 👋🏻",[5909,183890,165017],{"id":165016},[651,183892,183893,183894],{},"“Be kind, for everyone you meet is fighting a harder battle.” ― ",[2939,183895,183896],{},"Plato",[4542,183898,157704],{"id":157703},[651,183900,178368,183901,166271],{},[812,183902,41499],{"href":44086,"rel":183903},[816],[651,183905,41105,183906,69920,183908,183910,183912,183914,183917],{},[41107,183907],{},[41107,183909],{},[812,183911,161560],{"href":161111},[41107,183913],{},[812,183915,53869],{"href":53869,"rel":183916},[816],[41107,183918],{},{"title":674,"searchDepth":790,"depth":790,"links":183920},[183921,183922,183925],{"id":183838,"depth":790,"text":183839},{"id":183881,"depth":790,"text":183882,"children":183923},[183924],{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":183927,"date":183928},"dan-vega-nuxt","2023-10-30T07:00:00.000Z","/newsletter/2023/10/30/dan-vega-nuxt",{"title":183815,"description":183820},"newsletter/2023/10/30/dan-vega-nuxt","4WRaMB2UaOrBzKuWE7LDQxI5nr6t2FnrE6nYp_eGyk0",{"id":183934,"title":183935,"body":183936,"description":183940,"extension":793,"meta":184119,"navigation":797,"path":184121,"seo":184122,"stem":184123,"__hash__":184124},"content/newsletter/2023/11/13/vmware-explore-barcelona.md","VMware Explore Barcelona 2023 Recap",{"type":648,"value":183937,"toc":184109},[183938,183941,183949,183952,183959,183962,183964,183967,183971,183974,183979,183982,183989,183993,183999,184002,184005,184008,184019,184023,184032,184035,184041,184045,184048,184054,184062,184071,184077,184079,184084,184087,184089,184091,184096],[651,183939,183940],{},"Happy Monday and welcome to another edition of the newsletter. I apologize for the delay in updates as I have been busy with work and traveling.",[651,183942,183943,183944,183948],{},"In a recent newsletter, I mentioned that I upgraded ",[812,183945,183947],{"href":82688,"rel":183946},[816],"my personal website"," to Nuxt 3. I am really enjoying this upgrade as it has rejuvenated my excitement for my website.",[651,183950,183951],{},"As I started writing this edition of the newsletter, I realized that my speaking page was already outdated because it was static. To address this, I decided to rewrite the page and transfer all the static data into a JSON data file. Instead of doing this manually, I sought the assistance of my good friend, Chat GPT. This is a prime example of how AI can enhance a developer's capabilities.",[651,183953,183954,183955,183958],{},"Anyway, if you visit my ",[812,183956,120451],{"href":120449,"rel":183957},[816],", you will find a list of upcoming and archived talks. I'm really proud of the migration to my new website and would love to put together some content around it if that's something you're interested in learning about. If you are, please reach out to me and let me know that you're interested.",[651,183960,183961],{},"This week, I am going to recap my trip to Barcelona, Spain for VMware Explore Barcelona.",[4542,183963,183882],{"id":183881},[651,183965,183966],{},"Last week, I had the opportunity to visit Barcelona, Spain for the VMware Barcelona event, and it exceeded my expectations. During the conference, I had the chance to give two presentations, both of which I believe went really well.",[5909,183968,183970],{"id":183969},"accelerating-innovation-with-spring","Accelerating Innovation with Spring",[651,183972,183973],{},"The first presentation was co-presented with my coworker, Cora Iberkleid, and it was titled \"Accelerating Innovation with Spring, The Anniversary Edition.\" The idea behind this talk stemmed from the fact that we are celebrating 20 years of Spring and 10 years of Spring Boot this year. We decided to start with a brief history lesson on the origins of Spring and highlight some of the major milestones throughout the years.",[651,183975,183976],{},[660,183977],{"alt":182041,"src":183978},"/images/newsletter/2023/11/13/history-of-spring.png",[651,183980,183981],{},"We then provided an overview of the major themes of Spring Boot 3 and Spring Framework 6. Following that recap, we delved into what to expect in Spring Boot 3.2 and beyond. Our focus was on improving runtime efficiency with Virtual Threads and CRaC, enhancing developer experience with Docker & Testcontainers, and introducing new client abstractions with RestClient and JDBC Client.",[651,183983,183984,183985,664],{},"If you're interested in watching the recording of this session, you can check it out ",[812,183986,18263],{"href":183987,"rel":183988},"https://www.vmware.com/explore/video-library/video-landing.html?sessionid=1693987326111001vbVK&videoId=6340722423112",[816],[5909,183990,183992],{"id":183991},"graphql-code-theater","GraphQL - Code Theater",[651,183994,183995],{},[660,183996],{"alt":183997,"src":183998},"Code Theater","/images/newsletter/2023/11/13/code-theater.jpeg",[651,184000,184001],{},"My next presentation was one that included 0 slides which is always fun. I love to live code but in a long session format it can be quite the challenge. This talk however was only 20 minutes and I had to jam a lot of content into those 20 minutes. After saying a quick prayer to the demo gods, I was off and running.",[651,184003,184004],{},"I had to skip a lot of the intro to GraphQL content and when you might want to use it. If you’re interested in that you can check out the recording from my talk at SpringOne in Las Vegas from earlier this year.",[5988,184006],{"id":184007},"xprZxH8p0Pg",[651,184009,184010,184011,184015,184016,664],{},"I ended up creating a small application that interacted with a database. I used the posts from my blog to demonstrate its functionality. After setting up the posts, we developed a new GraphQL API and discussed the process of building a schema and connecting it to your Spring Controllers. While there is no recording of this talk, you can refer to the ",[812,184012,110300],{"href":184013,"rel":184014},"https://github.com/danvega/vme-blog",[816]," that contains the agenda and code. This will allow you to follow along and create your own API. Since the talk was not recorded, I may consider recording it for ",[812,184017,179469],{"href":101295,"rel":184018},[816],[5909,184020,184022],{"id":184021},"spring-meetup-at-mclaren-barcelona","Spring Meetup at McLaren Barcelona",[651,184024,184025,184026,184031],{},"We recently had a special Spring Meetup at ",[812,184027,184030],{"href":184028,"rel":184029},"https://barcelona.mclaren.com/en",[816],"McLaren in Barcelona",". I had the privilege of co-hosting the meetup with my coworker, Cora. We were given a special tour of the three different floors of cars, which was truly amazing.",[651,184033,184034],{},"During the meetup, Cora and I gave a brief introduction to celebrate 20 years of Spring and 10 years of Spring Boot. We shared its history, enjoyed champagne, and indulged in Spring branded cupcakes. I want to express my gratitude to everyone involved for including me in this special event. It was an honor and privilege to be a part of it. 🤩",[651,184036,184037],{},[660,184038],{"alt":184039,"src":184040},"McLaren Barcelona","/images/newsletter/2023/11/13/mclaren.jpeg",[5909,184042,184044],{"id":184043},"barcelona","Barcelona",[651,184046,184047],{},"During my time in Barcelona, I had the opportunity to have dinner with an amazing group of people on two separate occasions. The picture below was taken on the second night, where they served different steaks that were almost cooked and you finish them off right in front of you.",[651,184049,184050],{},[660,184051],{"alt":184052,"src":184053},"Dinner","/images/newsletter/2023/11/13/dinner.jpeg",[651,184055,184056,184057,184061],{},"I was also thrilled to finally meet Sergi Almar in person. He can be seen in the dinner picture above, but I also had dinner with him the night before. In case you're not familiar with Sergi, he is the mastermind behind ",[812,184058,36441],{"href":184059,"rel":184060},"https://twitter.com/spring_io/status/1722242942184923475",[816],". I hope to return to Barcelona next year for that event and another dinner with Sergi 🥳",[651,184063,184064,184065,184070],{},"Finally, I had the opportunity to be a tourist and visit ",[812,184066,184069],{"href":184067,"rel":184068},"https://www.sagradafamilia-tickets.org/",[816],"La Sagrada Familia",". Construction of this church began in 1882 and is still ongoing today. It was an impressive architectural achievement, and seeing it in person was an absolutely amazing experience. I also took the opportunity to say a prayer for my family.",[651,184072,184073],{},[660,184074],{"alt":184075,"src":184076},"la-sagrada-familia","/images/newsletter/2023/11/13/la-sagrada-familia.jpeg",[5909,184078,165017],{"id":165016},[1004,184080,184081],{},[651,184082,184083],{},"\"Success is the sum of small efforts, repeated day in and day out.\"",[651,184085,184086],{},"Robert Collier",[1004,184088],{},[4542,184090,157704],{"id":157703},[651,184092,178368,184093,166271],{},[812,184094,41499],{"href":44086,"rel":184095},[816],[651,184097,41105,184098,69920,184100,184102,184104,184106],{},[41107,184099],{},[41107,184101],{},[812,184103,161560],{"href":161111},[41107,184105],{},[812,184107,53869],{"href":53869,"rel":184108},[816],{"title":674,"searchDepth":790,"depth":790,"links":184110},[184111,184118],{"id":183881,"depth":790,"text":183882,"children":184112},[184113,184114,184115,184116,184117],{"id":183969,"depth":892,"text":183970},{"id":183991,"depth":892,"text":183992},{"id":184021,"depth":892,"text":184022},{"id":184043,"depth":892,"text":184044},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":183881,"date":184120},"2023-11-13T07:00:00.000Z","/newsletter/2023/11/13/vmware-explore-barcelona",{"title":183935,"description":183940},"newsletter/2023/11/13/vmware-explore-barcelona","H96TBvxVcB3CQiGzRGN9hTN8bAedfcWngB5UTGD97zg",{"id":184126,"title":184127,"body":184128,"description":184132,"extension":793,"meta":184306,"navigation":797,"path":184309,"seo":184310,"stem":184311,"__hash__":184312},"content/newsletter/2023/11/20/spring-framework-6-1.md","Spring Framework 6.1, Spring Guides in the Cloud and a GraphQL",{"type":648,"value":184129,"toc":184294},[184130,184133,184136,184140,184143,184186,184189,184192,184195,184199,184207,184210,184214,184221,184224,184226,184229,184235,184237,184239,184253,184255,184263,184265,184268,184274,184276,184281],[651,184131,184132],{},"Happy Monday and welcome to another edition of the newsletter. It's Thanksgiving here in the United States this week, a time to express gratitude. First and foremost, I'm thankful for my family, and everything I do is for them. I'm also grateful for the opportunity to do what I love every single day. I appreciate all of you who support me, and I don't take my position for granted. For those celebrating this week, I hope you have an amazing day filled with family, friends, and food.",[651,184134,184135],{},"Today, I want to discuss three topics: Spring Framework 6.1, Spring Guides in the Cloud, and my GraphQL presentation at VMware Explore Barcelona.",[4542,184137,184139],{"id":184138},"spring-framework-61","Spring Framework 6.1",[651,184141,184142],{},"It's hard to believe, but Spring Framework 6.1 was released last year and will serve as the foundation for Spring Boot 3.2, which is set to be released this week on Thanksgiving. Spring Framework 6.1 ships with a bunch of exciting new features:",[5316,184144,184145,184150,184155,184161,184167,184172],{},[5332,184146,184147],{},[2939,184148,184149],{},"Embracing JDK 21 LTS",[5332,184151,184152,184154],{},[2939,184153,117624],{}," (Project Loom)",[5332,184156,184157,184160],{},[2939,184158,184159],{},"JVM Checkpoint Restore"," (Project CRaC)",[5332,184162,184163,184166],{},[2939,184164,184165],{},"Resource Lifecycle Management",", revisited",[5332,184168,184169,184166],{},[2939,184170,184171],{},"Data Binding and Validation",[5332,184173,184174],{},[2939,184175,184176,184177,119818,184181,184185],{},"New ",[812,184178,113232],{"href":184179,"rel":184180},"https://docs.spring.io/spring-framework/docs/6.1.x/javadoc-api/org/springframework/web/client/RestClient.html",[816],[812,184182,118531],{"href":184183,"rel":184184},"https://docs.spring.io/spring-framework/docs/6.1.x/javadoc-api/org/springframework/jdbc/core/simple/JdbcClient.html",[816]," APIs",[651,184187,184188],{},"I am a big fan of all the features released in JDK 21 and I am really excited to see Spring adopting JDK 21. Although the baseline remains at JDK 17, it is worth considering upgrading your JDK along with Spring to avoid the need for a major upgrade in a few years.",[651,184190,184191],{},"I am particularly excited about Virtual Threads in JDK 21 and I believe Spring Developers should be too. When it comes to developing traditional blocking web applications, Virtual Threads will offer significant scalability benefits with minimal or no code changes.",[651,184193,184194],{},"I am also a big fan of the new client abstractions. The new RestClient provides a simple and fluent API for making API calls, built on top of the RestTemplate. The JDBC Client offers a more user-friendly and fluent API for interacting with databases. I believe both of these improvements will enhance the developer experience.",[4542,184196,184198],{"id":184197},"spring-guides-in-the-cloud","Spring Guides in the Cloud",[651,184200,184201,184202,184206],{},"Last week on ",[812,184203,97345],{"href":184204,"rel":184205},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0057/",[816]," we had a really great discussion about Spring Academy with one of the engineers on the team, Felipe Gutierrez. During the live stream Felipe showed off a really cool feature where you could run a Spring Guide in the cloud. I thought this was so cool that I decided to sit down and put together a video on it.",[5988,184208],{"id":184209},"kSSStm6b0Zg",[4542,184211,184213],{"id":184212},"graphql-vmware-explore","GraphQL VMware Explore",[651,184215,184216,184217,184220],{},"In last week’s newsletter I mentioned that I did 2 talks at VMware Explore Barcelona. The first talk “Accelerating innovation with Spring” that I did with my coworker Cora was recorded and you can check it out ",[812,184218,18263],{"href":183987,"rel":184219},[816],". The other talk was a live coding session where I did an introduction to building GraphQL APIs in Java and Spring and that talk was not recorded. I put all of that work into that presentation so I decided to sit down and record it and I hope you enjoy it.",[5988,184222],{"id":184223},"1mRK3FeV76I",[4542,184225,161336],{"id":161335},[651,184227,184228],{},"One of the reasons I really love having my own personal website is because I feel like it’s my own little corner of the internet. I recently launched a new website but it was missing that personal touch. I have started collecting images for the home page and now have a few that I really like. My plan is to showcase the events I’m attending and the community I get to interact with. If we have a photo together please send the to me so I can add the to the home page. If we don’t have a photo together let’s try and change that the next time we are in the same place.",[651,184230,184231],{},[660,184232],{"alt":184233,"src":184234},"danvega_dev_home.png","/images/newsletter/2023/11/20/danvega_dev_home.png",[4542,184236,177889],{"id":157573},[5909,184238,164983],{"id":39439},[651,184240,178347,184241,184246,184247,184252],{},[812,184242,184245],{"href":184243,"rel":184244},"https://www.youtube.com/watch?v=rUjLf_8HwLk",[816],"this live stream"," on Coffee and Code where Josh Long had a couple of special guests in Rod Johnson and Heather VanCura. They talked about Heathers new book “",[812,184248,184251],{"href":184249,"rel":184250},"https://amzn.to/3R5XSHH",[816],"Developer Career Masterplan","” and had some really interesting discussions about it.",[5909,184254,170434],{"id":79608},[651,184256,40060,184257,184262],{},[812,184258,184261],{"href":184259,"rel":184260},"https://cloud-builders.tech/",[816],"Cloud Builders Java Conference"," is coming up soon and they have a really great lineup of speakers. This conference is online and free so make sure you register now.",[5909,184264,178573],{"id":95961},[651,184266,184267],{},"I had some fun with the new Make Real feature of tldraw this weekend.",[651,184269,184270],{},[812,184271,184272],{"href":184272,"rel":184273},"https://twitter.com/therealdanvega/status/1725623222207484274",[816],[4542,184275,157704],{"id":157703},[651,184277,178368,184278,166271],{},[812,184279,41499],{"href":44086,"rel":184280},[816],[651,184282,41105,184283,69920,184285,184287,184289,184291],{},[41107,184284],{},[41107,184286],{},[812,184288,161560],{"href":161111},[41107,184290],{},[812,184292,53869],{"href":53869,"rel":184293},[816],{"title":674,"searchDepth":790,"depth":790,"links":184295},[184296,184297,184298,184299,184300,184305],{"id":184138,"depth":790,"text":184139},{"id":184197,"depth":790,"text":184198},{"id":184212,"depth":790,"text":184213},{"id":161335,"depth":790,"text":161336},{"id":157573,"depth":790,"text":177889,"children":184301},[184302,184303,184304],{"id":39439,"depth":892,"text":164983},{"id":79608,"depth":892,"text":170434},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":184307,"date":184308},"spring-framework-6-1","2023-11-20T07:00:00.000Z","/newsletter/2023/11/20/spring-framework-6-1",{"title":184127,"description":184132},"newsletter/2023/11/20/spring-framework-6-1","IGFmYjyTbQURy0Hd79NY_qWnWvXRhGX7CdMD83R7kiU",{"id":184314,"title":184315,"body":184316,"description":184320,"extension":793,"meta":184635,"navigation":797,"path":184637,"seo":184638,"stem":184639,"__hash__":184640},"content/newsletter/2023/11/27/spring-boot-3-2.md","Spring Boot 3.2, VMware by Broadcom and Conference Planning for 2024",{"type":648,"value":184317,"toc":184619},[184318,184321,184327,184330,184334,184341,184346,184348,184355,184358,184451,184453,184461,184473,184475,184477,184483,184485,184489,184501,184510,184514,184523,184528,184531,184534,184537,184541,184544,184550,184553,184559,184566,184568,184570,184583,184585,184591,184593,184599,184601,184606],[651,184319,184320],{},"Happy Monday and welcome to another edition of the newsletter. If you celebrated Thanksgiving, I hope you had a wonderful holiday filled with family, food, laughter, and love. Even if you're not in the US, I wish the same for you. We had a wonderful Thanksgiving feast in my home as we hosted over 20 people. My wife did an amazing job on putting together a great day filled with amazing food and ambiance. I successfully deep fried my first turkey and did it without burning the house down 🤣",[651,184322,184323],{},[660,184324],{"alt":184325,"src":184326},"Deep Fried Turkey","/images/newsletter/2023/11/27/turkey.png",[651,184328,184329],{},"Today, I have a lot to discuss, including Spring Boot 3.2, VMware by Broadcom, and conference planning for 2024.",[4542,184331,184333],{"id":184332},"spring-boot-32","Spring Boot 3.2",[651,184335,184336,184337,184340],{},"Last week, Spring Boot 3.2 was released, bringing with it a host of exciting new features. If you visit ",[812,184338,77478],{"href":20748,"rel":184339},[816]," and attempt to create a new project, you will see that the current version is now 3.2. Additionally, you will notice that only Java versions 17 and 21 are supported. This is because Spring Boot 3.0 is based on Java 17, and the latest release includes support for Java 21.",[651,184342,184343],{},[660,184344],{"alt":7117,"src":184345},"/images/newsletter/2023/11/27/spring-init.png",[5909,184347,183234],{"id":183187},[651,184349,184350,184351,97571,184353,664],{},"Speaking of Java 21, Spring Boot 3.2 introduces support for Virtual Threads. I created a video on Virtual Threads during the preview phase, but I plan on making an updated video soon to cover all the exciting changes in Spring Boot 3.2. To use Virtual Threads, you need to run Java 21 and set the property ",[676,184352,118085],{},[676,184354,3441],{},[651,184356,184357],{},"While Project Loom and Virtual Threads are the highlights of Java 21, there are other great features as well. As I start using Java 21, I'm excited to take advantage of all its amazing features. Here are a few of my favorites:",[1031,184359,184360,184368],{},[1034,184361,184362],{},[1037,184363,184364,184366],{},[1040,184365,182796],{},[1040,184367,182801],{},[1050,184369,184370,184379,184388,184397,184406,184415,184424,184433,184442],{},[1037,184371,184372,184374],{},[1055,184373,182808],{},[1055,184375,184376],{},[812,184377,182813],{"href":182813,"rel":184378},[816],[1037,184380,184381,184383],{},[1055,184382,182820],{},[1055,184384,184385],{},[812,184386,182825],{"href":182825,"rel":184387},[816],[1037,184389,184390,184392],{},[1055,184391,182844],{},[1055,184393,184394],{},[812,184395,182849],{"href":182849,"rel":184396},[816],[1037,184398,184399,184401],{},[1055,184400,182856],{},[1055,184402,184403],{},[812,184404,182861],{"href":182861,"rel":184405},[816],[1037,184407,184408,184410],{},[1055,184409,182880],{},[1055,184411,184412],{},[812,184413,182885],{"href":182885,"rel":184414},[816],[1037,184416,184417,184419],{},[1055,184418,182892],{},[1055,184420,184421],{},[812,184422,104628],{"href":104628,"rel":184423},[816],[1037,184425,184426,184428],{},[1055,184427,182902],{},[1055,184429,184430],{},[812,184431,182907],{"href":182907,"rel":184432},[816],[1037,184434,184435,184437],{},[1055,184436,182914],{},[1055,184438,184439],{},[812,184440,182919],{"href":182919,"rel":184441},[816],[1037,184443,184444,184446],{},[1055,184445,182974],{},[1055,184447,184448],{},[812,184449,182979],{"href":182979,"rel":184450},[816],[5909,184452,144990],{"id":118965},[651,184454,184455,184456,184458,184459,664],{},"Spring Boot 3.2 includes support for the new",[676,184457,113232],{},"interface which has been introduced in Spring Framework 6.1. This interface provides a functional style blocking HTTP API with a similar to design to",[676,184460,109920],{},[651,184462,184463,184464,184466,184467,184469,184470,184472],{},"Existing and new application might want to consider using",[676,184465,113232],{},"as an alternative to",[676,184468,23551],{},". If you want to learn more about the ",[676,184471,113232],{}," you can check out the following video I did on it.",[5988,184474],{"id":119294},[5909,184476,118347],{"id":118516},[651,184478,184479,184480,184482],{},"The JDBC Template abstraction has been around since the early stages of Spring. Spring Framework 6.1 introduced a new JDBC Client that gives us a new fluent API for talking to a database. What I love about this is that everything lines up you need longer need to write your own RowMapper which always felt like boiler plate code I didn’t need to be writing and now I don’t. Spring Boot 3.2 adds auto-configuration for a ",[676,184481,118531],{}," . If you want to learn more about you can check out the following video I did on it.",[5988,184484],{"id":118936},[5909,184486,184488],{"id":184487},"spring-boot-32-release-notes","Spring Boot 3.2 Release Notes",[651,184490,184491,184492,184495,184496,184500],{},"There are many other topics we can discuss regarding Spring Boot 3.2. A great resource to learn about all of this is the comprehensive ",[812,184493,95704],{"href":118322,"rel":184494},[816]," compiled by the Spring Team. If you want to learn more about Spring Boot 3.2 you can join me and DaShaun tomorrow on ",[812,184497,97345],{"href":184498,"rel":184499},"https://tanzu.vmware.com/developer/tv/spring-office-hours/0058/",[816]," as we go through everything.",[651,184502,184503,184504,184509],{},"This is also a gentle reminder that ",[812,184505,184508],{"href":184506,"rel":184507},"https://spring.io/projects/spring-boot#support",[816],"Spring Boot 2.7 support"," officially ends today. If you or your organization needs help upgrading to Spring Boot 3 please feel free to reach out to me.",[4542,184511,184513],{"id":184512},"vmware-by-broadcom","VMware by Broadcom",[651,184515,184516,184517,184522],{},"VMware is now VMware by Broadcom as the ",[812,184518,184521],{"href":184519,"rel":184520},"https://www.broadcom.com/blog/broadcom-announces-successful-acquisition-of-vmware",[816],"acquisition has closed",". A little after I started here, which is almost 2 years the acquisition was announced and 18 months later it has finally closed. I would be lying if I didn’t say it has been a stressful few months.",[651,184524,184525],{},[660,184526],{"alt":184513,"src":184527},"/images/newsletter/2023/11/27/vmware-by-broadcom.png",[651,184529,184530],{},"The uncertainty about my future has been the hardest part. I know a lot of people who left so that they didn’t have to go through it but for me that was never an option. I landed my dream job and I love the people that I work with so I was going to ride this out as long as I could.",[651,184532,184533],{},"I’m very thankful for my time at VMware and I happy to say that I will continue to do what I love at VMware by Broadcom 🥳 I love the community that I serve and if there is anything I can do to help please don’t hesitate to reach out. I’m excited about the future and have already started putting some big plans together for the new year.",[651,184535,184536],{},"While that is the good news for me, acquisitions are never easy on everyone. I know many people are currently finding out there fate and my thoughts are with them. We have some of the smartest people I have ever had the joy of working with and I hope it works out here but If it doesn’t I know they will move on to bigger and better things. 💔",[4542,184538,184540],{"id":184539},"conference-planning-for-2024","Conference Planning for 2024",[651,184542,184543],{},"If you aren't new around here you already know that I am a huge fan of Notion and being organized. I have a database in Notion where I keep my list of possible conferences by year. I have begun preparing talks and submitting CFPs for conferences in 2024 and this is what my current list looks like",[651,184545,184546],{},[660,184547],{"alt":184548,"src":184549},"Notion CFP","/images/newsletter/2023/11/27/notion-cfp.png",[651,184551,184552],{},"Last week I found out that I got accepted to GIDS and I am beyond excited about attending and speaking here. I have always wanted to visit India and now I will get my opportunity in April.",[651,184554,184555],{},[812,184556,184557],{"href":184557,"rel":184558},"https://twitter.com/developersummit/status/1726384860959215665",[816],[651,184560,184561,184562,184565],{},"If you are ever curious about upcoming speaking engagements you can check out my ",[812,184563,120451],{"href":120449,"rel":184564},[816]," on my website.",[4542,184567,177889],{"id":157573},[5909,184569,164983],{"id":39439},[651,184571,178347,184572,184576,184577,184582],{},[812,184573,179945],{"href":184574,"rel":184575},"https://airhacks.fm/#episode_269",[816]," by Adam Bein on why Kotlin is better than Java. This is a very timely episode as I just put out a ",[812,184578,184581],{"href":184579,"rel":184580},"https://twitter.com/therealdanvega/status/1725531488932737205",[816],"poll on Twitter"," last week asking all of you If I should learn Kotlin.",[5909,184584,165017],{"id":165016},[651,184586,184587,184588],{},"\"Gratitude is not only the greatest of virtues, but the parent of all the others.\" - ",[7300,184589,184590],{},"Marcus Tullius Cicero",[5909,184592,178573],{"id":95961},[651,184594,184595],{},[812,184596,184597],{"href":184597,"rel":184598},"https://twitter.com/therealdanvega/status/1726962300484296726",[816],[4542,184600,157704],{"id":157703},[651,184602,178368,184603,166271],{},[812,184604,41499],{"href":44086,"rel":184605},[816],[651,184607,41105,184608,69920,184610,184612,184614,184616],{},[41107,184609],{},[41107,184611],{},[812,184613,161560],{"href":161111},[41107,184615],{},[812,184617,53869],{"href":53869,"rel":184618},[816],{"title":674,"searchDepth":790,"depth":790,"links":184620},[184621,184627,184628,184629,184634],{"id":184332,"depth":790,"text":184333,"children":184622},[184623,184624,184625,184626],{"id":183187,"depth":892,"text":183234},{"id":118965,"depth":892,"text":144990},{"id":118516,"depth":892,"text":118347},{"id":184487,"depth":892,"text":184488},{"id":184512,"depth":790,"text":184513},{"id":184539,"depth":790,"text":184540},{"id":157573,"depth":790,"text":177889,"children":184630},[184631,184632,184633],{"id":39439,"depth":892,"text":164983},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":119998,"date":184636},"2023-11-27T07:00:00.000Z","/newsletter/2023/11/27/spring-boot-3-2",{"title":184315,"description":184320},"newsletter/2023/11/27/spring-boot-3-2","OEN0vuE9XjdxGywm1rP5SFrlfPbRtOrLXSPgTQy0vsk",{"id":184642,"title":184643,"body":184644,"description":184648,"extension":793,"meta":184775,"navigation":797,"path":184777,"seo":184778,"stem":184779,"__hash__":184780},"content/newsletter/2023/12/04/runtime-efficiency.md","Runtime Efficiency in Spring Boot 3",{"type":648,"value":184645,"toc":184760},[184646,184649,184652,184655,184658,184662,184665,184667,184669,184671,184673,184676,184679,184681,184684,184686,184689,184692,184694,184696,184698,184705,184707,184714,184716,184725,184727,184732,184734,184740,184742,184747],[651,184647,184648],{},"Happy Monday and welcome to another edition of the newsletter. This week, I have a quick trip to Cincinnati to spend a day with a customer. I'll be discussing everything that's happening in Spring Boot 3, including some of the newest features in Spring Boot 3.2. It's also that time of the year where we are starting to wind down and look forward to the holidays and the new year.",[651,184650,184651],{},"When it comes to scheduling, I tend to be meticulous and detail-oriented. I enjoy carefully planning and organizing my time to ensure it is well-structured and efficiently managed. As the new year approaches, I like to start fresh with a new calendar and a list of things I would like to accomplish over the year, which I then break down into quarters.",[651,184653,184654],{},"I’m starting to put together some things I would like to learn in the new year and some goals I would like to accomplish. I plan on sharing those with you in the new year but until then I would like to hear from you. How do you go about planning for the new year?",[651,184656,184657],{},"In this edition of the newsletter I want to talk to you about runtime efficiency in Spring Boot 3.",[4542,184659,184661],{"id":184660},"runtime-efficiency-in-spring-boot-3","Runtime Efficiency In Spring Boot 3",[651,184663,184664],{},"Over the past year, I have had the opportunity to travel to conferences and meet with customers, discussing the impressive features of Spring Boot 3 and beyond. One of the prominent themes in Spring Boot 3 is Runtime Efficiency. But what does that really mean, and which features are contributing to it?",[651,184666,118363],{},[651,184668,118366],{},[651,184670,118369],{},[5909,184672,184333],{"id":184332},[651,184674,184675],{},"In last week's episode of Spring Office Hours, we discussed the new release of Spring Boot 3.2. DaShaun and I had the opportunity to talk about the new features at a high level. We reviewed the release notes, which is a great resource for learning about the upgrade path, new features, and any deprecations. You can find the live stream below and listen to it on your favorite podcast player.",[5988,184677],{"id":184678},"opji4Hue4xM",[5909,184680,117624],{"id":118372},[651,184682,184683],{},"Virtual Threads were finalized in JDK 21, and now Spring has first-class support for them with the release of Spring Boot 3.2. In this tutorial I discuss what Virtual Threads are and how to enable them in a Spring Boot Application. We take a look at some benchmarks using Apache Benchmark to understand how Virtual Threads will provide significant scalability benefits.",[5988,184685],{"id":118434},[5909,184687,118336],{"id":184688},"project-crac-coordinated-restore-at-checkpoint",[651,184690,184691],{},"Last but not least I recorded a tutorial on Project CRaC with my good friend DaShaun. In this tutorial we take a look at what CRaC is and how it will help us improve startup times in our Spring Applications.",[5988,184693],{"id":118479},[4542,184695,177889],{"id":157573},[5909,184697,164959],{"id":69848},[651,184699,178347,184700,184704],{},[812,184701,109096],{"href":184702,"rel":184703},"https://foojay.io/today/springboot-3-2-crac/",[816]," from Azul Developer Advocate and Java Champion Gerrit Grunwald. In this article Gerrit dives in CRaC support in Spring Boot 3.2.",[5909,184706,164983],{"id":39439},[651,184708,178347,184709,184713],{},[812,184710,179945],{"href":184711,"rel":184712},"https://thenewstack.io/hey-programming-language-developer-get-over-yourself/",[816]," episode of The New Stack with Jean Yang of Akita Software. This episode got a little philosphical and I absolutely loved it.",[5909,184715,165005],{"id":39340},[651,184717,184718,184719,184724],{},"I got an advanced copy of ",[812,184720,184723],{"href":184721,"rel":184722},"https://amzn.to/3sTnI8P",[816],"Spring Security in Action, The Second Edition"," by Laurentiu Splica. If you haven’t had a chance to check out the first edition you really should. In the meantime I am really looking forward to diving into this one.",[5909,184726,165017],{"id":165016},[1004,184728,184729],{},[651,184730,184731],{},"It’s not the load that breaks you down. It’s the way you carry it. – Lou Holtz",[5909,184733,178573],{"id":95961},[651,184735,184736],{},[812,184737,184738],{"href":184738,"rel":184739},"https://twitter.com/therealdanvega/status/1730676167487737950",[816],[4542,184741,157704],{"id":157703},[651,184743,178368,184744,166271],{},[812,184745,41499],{"href":44086,"rel":184746},[816],[651,184748,41105,184749,69920,184751,184753,184755,184757],{},[41107,184750],{},[41107,184752],{},[812,184754,161560],{"href":161111},[41107,184756],{},[812,184758,53869],{"href":53869,"rel":184759},[816],{"title":674,"searchDepth":790,"depth":790,"links":184761},[184762,184767,184774],{"id":184660,"depth":790,"text":184661,"children":184763},[184764,184765,184766],{"id":184332,"depth":892,"text":184333},{"id":118372,"depth":892,"text":117624},{"id":184688,"depth":892,"text":118336},{"id":157573,"depth":790,"text":177889,"children":184768},[184769,184770,184771,184772,184773],{"id":69848,"depth":892,"text":164959},{"id":39439,"depth":892,"text":164983},{"id":39340,"depth":892,"text":165005},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":118356,"date":184776},"2023-12-04T07:00:00.000Z","/newsletter/2023/12/04/runtime-efficiency",{"title":184643,"description":184648},"newsletter/2023/12/04/runtime-efficiency","c8ma7LeAfRgF5IiBNUXxP4R76l-Ee16UScRHgSiSw44",{"id":184782,"title":184783,"body":184784,"description":184788,"extension":793,"meta":184935,"navigation":797,"path":184938,"seo":184939,"stem":184940,"__hash__":184941},"content/newsletter/2023/12/11/santa-baby.md","What is the difference between JDBC Client and Spring Data and Http Interfaces",{"type":648,"value":184785,"toc":184921},[184786,184789,184792,184795,184801,184804,184806,184809,184812,184814,184817,184821,184824,184827,184831,184834,184840,184842,184844,184850,184852,184860,184862,184865,184868,184872,184881,184883,184888,184890,184893,184899,184901,184906],[651,184787,184788],{},"Happy Monday and welcome to another edition of the newsletter. I hope you had an amazing weekend and are refreshed and ready to tackle another week. As we approach the end of the year, many of us are thinking about wrapping things up and planning for the new year. Personally, I'm excited to start setting my goals for the upcoming year, and I'll be sure to share them with all of you once I have them finalized. But I'm also curious to hear about your technical goals for the new year. Are you looking to build something new or learn something new?",[651,184790,184791],{},"Last week, I had a quick trip to Cincinnati where I had the opportunity to meet with a customer and engage with some incredible developers. While I can't disclose the customer's name, I can say that I have been a long-time customer of theirs and it was truly exciting for me to visit their location. During my visit, I gave two presentations, one of which focused on the new features in Spring Boot 3 and Beyond. The presentation covered the exciting enhancements in Spring Boot 3.2, with Virtual Threads being particularly well-received. It seems that many in the community are eager to leverage Virtual Threads and deploy their blocking applications on Java 21 and Spring Boot 3.2. If you missed it, I recently did a video showcasing the scalability benefits of Spring Boot 3.2 using Virtual Threads.",[651,184793,184794],{},"The holiday season is a special time for my family, and we have a number of traditions that we hold dear. This past weekend, we took the kids to see Santa and enjoyed a meal at one of our favorite restaurants, The Cheesecake Factory. I wanted to share a memorable moment with you: both of my kids sat with Santa and actually smiled for the photo. This has never happened before and may never happen again, so it was a special moment for us.",[651,184796,184797],{},[660,184798],{"alt":184799,"src":184800},"My girls with Santa","/images/newsletter/2023/12/11/santa.jpeg",[651,184802,184803],{},"In this weeks newsletter I want to talk to you about a video I made this week on Spring Data, Spring Office Hours where I interviewed 2 members of the Spring Team and an upcoming talk with Azul.",[4542,184805,118940],{"id":118939},[651,184807,184808],{},"I created a video about the new JDBC Client in Spring Boot 3.2, and it received excellent feedback. One of the questions that arose from that was, \"What is the difference between JDBC Client and Spring Data?\"",[651,184810,184811],{},"I thought this was a good opportunity to explore the various options available in Java and Spring for accessing and persisting data to a database. This led me to create an example where we connect to a database in Java and explain the origins of the original JDBC Template abstraction in Spring. Then, I demonstrated how to access the same database in Spring using the JDBC Template. After discussing some of the issues with the JDBC Template, I introduced the new JDBC Client in Spring Boot 3.2 and highlighted how its simple and fluent API simplifies database access. Finally, we delved into Spring Data and how the repository pattern can handle the mundane CRUD functionality, allowing you to focus on your business requirements.",[5988,184813],{"id":118952},[651,184815,184816],{},"I hope you enjoyed that video and by the look of the comments you did. I got a really good question around multiple datasources and the JDBC Client that I hope to make a video on this week.",[4542,184818,184820],{"id":184819},"http-interfaces-in-spring","Http Interfaces in Spring",[651,184822,184823],{},"Last week on the Spring Office Hours Podcast, I had the opportunity to interview Olga Maciaszek-Sharma and Rossen Stoyanchev from the Spring Team. We discussed the history of clients in the Spring Framework and Spring Cloud. Additionally, we had a great conversation about Http Interfaces, their origin, and when to use them. I thoroughly enjoyed this discussion, and I hope you did too. If you are listening to these conversations on your favorite podcast player, please consider leaving us a review. It would greatly help us. Thank you!",[5988,184825],{"id":184826},"OgyBTbsNIjo",[4542,184828,184830],{"id":184829},"getting-up-to-speed-with-spring-boot-32","Getting up to speed with Spring Boot 3.2",[651,184832,184833],{},"I’m really excited about an upcoming talk with my friend Pratik Patel at Azul. We are going to be talking all about Spring Boot 3.2 but I have a hunch we will spend some time talking about Project CRaC (Coordinated Restore at Checkpoint). If you’re interested in attending this talk online for free please visit the link below to register.",[651,184835,184836],{},[812,184837,184838],{"href":184838,"rel":184839},"https://www.azul.com/webinar/20231219-spring-boot-updates/",[816],[4542,184841,177889],{"id":157573},[5909,184843,164959],{"id":69848},[651,184845,178347,184846,184849],{},[812,184847,109096],{"href":118500,"rel":184848},[816]," that discusses the introduction of Class Data Sharing (CDS) support in Spring Framework 6.1, which aims to optimize the startup time and memory footprint of Java applications. CDS is highlighted as a mature technology that can offer startup improvements with relatively little effort compared to alternatives like GraalVM. The article also mentions that combining CDS with Spring AOT optimizations can further reduce startup times, and it seeks feedback from the Spring community for future enhancements and integration possibilities.",[5909,184851,164971],{"id":157591},[651,184853,184854,184855,184859],{},"The newest version of IntelliJ 2023.3 is out and it’s packed full of features. I really enjoyed ",[812,184856,85142],{"href":184857,"rel":184858},"https://www.youtube.com/watch?v=zYvfIMrcpt8",[816]," where Maria Kosukhina walked us through all the highlights.",[5909,184861,165005],{"id":39340},[651,184863,184864],{},"I spent a lot of time in the car last week and it enabled me to get through 2 audio books, both by Jesse Itzler. The first one was Living with a Seal where Jesse had none other than David Goggins live with him for a month. The workouts were insane but at the end of the day I think the moral of the story was consistency and becoming mentally tough.",[651,184866,184867],{},"The next was Living with the Monks where he spent 15 days, living with the monks. 2 weeks might not seem like a long time but try to imagine living without phones, internet or tv for even a day. I really enjoyed this book as it got me to think about what’s important and how to be thankful and present.",[5909,184869,184871],{"id":184870},"spring-projects","🍃 Spring Projects",[651,184873,184874,184875,184880],{},"It was really exciting to see ",[812,184876,184879],{"href":184877,"rel":184878},"https://spring.io/blog/2023/12/06/spring-cloud-2023-0-0-aka-leyton-is-now-available",[816],"the release of Spring Cloud 2023.0",". I know this is something a lot of folks were waiting for before they could move to Spring Boot 3.2 and now it is there. There are a lot of exciting features in the release but the star of the show has to be Spring MVC Gateway. Thanks to Virtual Threads we now have a Gateway that supports imperative style programming.",[5909,184882,165017],{"id":165016},[1004,184884,184885],{},[651,184886,184887],{},"Everyone wants to live on top of the mountain, but all the happiness and growth occurs while you’re climbing it. - Andy Rooney",[5909,184889,178573],{"id":95961},[651,184891,184892],{},"I got access to Grok on X (Twitter) last week and asked it to roast me based on my Tweets. Not going to lie, that was pretty accurate 🤷♂️",[651,184894,184895],{},[812,184896,184897],{"href":184897,"rel":184898},"https://twitter.com/therealdanvega/status/1733177769083666505",[816],[4542,184900,157704],{"id":157703},[651,184902,178368,184903,166271],{},[812,184904,41499],{"href":44086,"rel":184905},[816],[651,184907,41105,184908,69920,184910,184912,184914,184916,184919],{},[41107,184909],{},[41107,184911],{},[812,184913,161560],{"href":161111},[41107,184915],{},[812,184917,53869],{"href":53869,"rel":184918},[816],[41107,184920],{},{"title":674,"searchDepth":790,"depth":790,"links":184922},[184923,184924,184925,184926,184934],{"id":118939,"depth":790,"text":118940},{"id":184819,"depth":790,"text":184820},{"id":184829,"depth":790,"text":184830},{"id":157573,"depth":790,"text":177889,"children":184927},[184928,184929,184930,184931,184932,184933],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39340,"depth":892,"text":165005},{"id":184870,"depth":892,"text":184871},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":184936,"date":184937},"santa-baby","2023-12-11T07:00:00.000Z","/newsletter/2023/12/11/santa-baby",{"title":184783,"description":184788},"newsletter/2023/12/11/santa-baby","i080WcKVGPZHWOFEGdcO0J5xer0P1vRkvc_TET1G2l8",{"id":184943,"title":184944,"body":184945,"description":184949,"extension":793,"meta":185064,"navigation":797,"path":185067,"seo":185068,"stem":185069,"__hash__":185070},"content/newsletter/2023/12/18/multiple-datasources-spring.md","Multiple DataSources in Spring, Vaadin and Spring Boot 3.2",{"type":648,"value":184946,"toc":185054},[184947,184950,184957,184970,184973,184977,184980,184982,184986,184993,184996,184999,185001,185004,185009,185011,185013,185026,185028,185034,185036,185041],[651,184948,184949],{},"Happy Monday and welcome to another edition of the newsletter. It's hard to believe, but we have 1 week until Christmas and 2 weeks until the new year. It was a pretty crazy weekend around here where we saw weather in the mid-50s in the middle of December, which is not normal.",[651,184951,184952,184953,184956],{},"Last week, I spent some more time cleaning up the newest edition of my website over at ",[812,184954,53863],{"href":53869,"rel":184955},[816],". I did some work optimizing pages to improve their search rankings, and I even cleaned up my URL scheme. All of my blog posts are now under the folder /blog/slug, instead of the previous /blog/year/month/day/slug structure. I also set up redirects for the longer URLs to redirect to the shorter ones.",[651,184958,184959,184960,184965,184966,184969],{},"After making these changes, I sat down and wrote a new blog post with search in mind. I knew that my latest video on Virtual threads was doing well, so I wanted to make sure there was an accompanying blog post. Instead of just writing whatever came to mind, I decided to be a little more strategic about it. I downloaded the ",[812,184961,184964],{"href":184962,"rel":184963},"https://keywordseverywhere.com/",[816],"Keywords Everywhere"," Chrome Plugin, which provides stats on what people are searching for and the volume of those keywords. While I'm not an SEO expert, I believe the few things I did with this post worked, as I received about 7k views on ",[812,184967,119291],{"href":118429,"rel":184968},[816]," in a week. If you have any suggestions on tools or techniques that I can use to improve old posts or maximize the reach of new ones, I would love to hear from you.",[651,184971,184972],{},"In today's newsletter, I want to discuss using the JDBC client with multiple datasources, Vaadin Flow, and Spring Boot 3.2.",[4542,184974,184976],{"id":184975},"multiple-jdbc-clients","Multiple JDBC Clients",[651,184978,184979],{},"I answered a question from a subscriber last week on how to use multiple JDBC Clients when you’re talking to multiple datasources. I thought this was a great question because if you’re only using a single datasource then auto-configuration kicks in and creates a datasource and JDBC Client for you. As soon as you need more than one you have to define both a datasource and JDBC Client for each connection. I thought this was a fun tutorial to go through and I hope you enjoy it.",[5988,184981],{"id":118962},[4542,184983,184985],{"id":184984},"vaadin-flow-and-hilla","Vaadin Flow and Hilla",[651,184987,184201,184988,184992],{},[812,184989,97345],{"href":184990,"rel":184991},"https://www.youtube.com/watch?v=ZCGg8MSuEGY",[816],", we had the opportunity to interview Lawrence Lockhart, a developer advocate with Vaadin. This was exciting for me on a couple of different levels. First off I have long been an admirer of Lawrence and his story on how he got started in tech. I have also had Vaadin on my long list of things I want to try / learn.",[651,184994,184995],{},"During the show I was able to get some clarification on Vaadin the company and the different products they offer. Vaadin Flow is server side UI framework that allows you build full-stack applications completely in Java. Hilla is there full-stack framework for building web applications with React + Java. I really enjoyed this conversation with Lawrence and I hope you do too.",[5988,184997],{"id":184998},"ZCGg8MSuEGY",[4542,185000,184830],{"id":184829},[651,185002,185003],{},"I’m really excited about my upcoming talk on Tuesday with my friend Pratik Patel at Azul. We are going to be talking all about Spring Boot 3.2 but I have a hunch we will spend some time talking about Project CRaC (Coordinated Restore at Checkpoint). If you’re interested in attending this talk online for free please visit the link below to register.",[651,185005,185006],{},[812,185007,184838],{"href":184838,"rel":185008},[816],[4542,185010,177889],{"id":157573},[5909,185012,164959],{"id":69848},[651,185014,185015,185016,185021,185022,664],{},"I was really excited to see that my friends at ",[812,185017,185020],{"href":185018,"rel":185019},"https://techcrunch.com/2023/12/11/docker-acquires-atomicjar-a-testing-startup-that-raised-25m-in-january/",[816],"AtomicJar got acquired by Docker",". If you don’t know AtomcJar is the company behind the amazing project ",[812,185023,171725],{"href":185024,"rel":185025},"https://testcontainers.com/",[816],[5909,185027,178573],{"id":95961},[651,185029,185030],{},[812,185031,185032],{"href":185032,"rel":185033},"https://twitter.com/therealdanvega/status/1735017607479083047",[816],[4542,185035,157704],{"id":157703},[651,185037,178368,185038,166271],{},[812,185039,41499],{"href":44086,"rel":185040},[816],[651,185042,41105,185043,69920,185045,185047,185049,185051],{},[41107,185044],{},[41107,185046],{},[812,185048,161560],{"href":161111},[41107,185050],{},[812,185052,53869],{"href":53869,"rel":185053},[816],{"title":674,"searchDepth":790,"depth":790,"links":185055},[185056,185057,185058,185059,185063],{"id":184975,"depth":790,"text":184976},{"id":184984,"depth":790,"text":184985},{"id":184829,"depth":790,"text":184830},{"id":157573,"depth":790,"text":177889,"children":185060},[185061,185062],{"id":69848,"depth":892,"text":164959},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":185065,"date":185066},"multiple-datasources-spring","2023-12-18T07:00:00.000Z","/newsletter/2023/12/18/multiple-datasources-spring",{"title":184944,"description":184949},"newsletter/2023/12/18/multiple-datasources-spring","ItfMlQ2z4s-jhackoNxgLo-GIsiUMq3uqRoBc0nlat0",{"id":185072,"title":185073,"body":185074,"description":185078,"extension":793,"meta":185256,"navigation":797,"path":185259,"seo":185260,"stem":185261,"__hash__":185262},"content/newsletter/2023/12/25/happy-holidays-2024.md","Happy Holidays, Frontends for Java Developers and What’s new in Spring Boot 3.2",{"type":648,"value":185075,"toc":185241},[185076,185079,185082,185086,185089,185092,185100,185106,185115,185121,185124,185128,185134,185141,185144,185147,185153,185159,185163,185169,185171,185174,185178,185181,185186,185190,185193,185199,185201,185203,185211,185213,185219,185221,185226,185228,185233],[651,185077,185078],{},"Happy Monday and welcome to a special edition of the newsletter. Today is Christmas here in the Vega house so I want to start out by saying Merry Christmas and Happy Holidays to all of you out there. It’s hard to believe that we are 1 week away from the new year but here we are. I’m working on some blog posts / thoughts around how my year looked and what I plan on focusing on next year so stay tuned for that.",[651,185080,185081],{},"In today’s newsletter I want to talk to you about Frontend Development for Java Developers, What’s New in Spring Boot 3.2 and Upcoming Presentations.",[4542,185083,185085],{"id":185084},"frontends-for-java-developers","Frontends for Java Developers",[651,185087,185088],{},"I am not just a Java developer, although that is what I consider myself most proficient in. I actually like to consider myself a polyglot developer and enjoy working with HTML, CSS, JavaScript, TypeScript, and more. There is something gratifying about building for the web and it’s been a passion of mine all throughout my career.",[651,185090,185091],{},"This made me contemplate the options available to Java developers when they need to develop a frontend for their application. I realize that this statement is quite broad, as different requirements call for different solutions to address specific problems.",[651,185093,185094,185095,185099],{},"I decided to sit down and start creating different scenarios or buckets, listing out available options. I like to use the application ",[812,185096,178640],{"href":185097,"rel":185098},"https://excalidraw.com/",[816]," for this. When I was done, I had a wonderful idea: turning this into a live collaboration and allowing others to contribute. It was really great to see so many people jump in and add to it.",[651,185101,185102],{},[660,185103],{"alt":185104,"src":185105},"Excalidraw Collaboration","/images/blog/2023/12/25/colloboration.png",[651,185107,185108,185109,185114],{},"After everyone was done contributing to it I cleaned it up a bit. Some added other buckets like developing a CLI or Desktop Application in Java but I decided to narrow this down to building web applications. This is what we ",[812,185110,185113],{"href":185111,"rel":185112},"https://twitter.com/therealdanvega/status/1737936945982972409",[816],"ended up"," with and I think this is a really great start.",[651,185116,185117],{},[660,185118],{"alt":185119,"src":185120},"Java Frontends","/images/blog/2023/12/25/java_frontends.png",[651,185122,185123],{},"My plan is to turn this into a blog post that the community can use as a resource to stay up to date with what options are available. If you have any thoughts on this or feedback I would love to hear from you.",[4542,185125,185127],{"id":185126},"whats-new-in-spring-boot-32","What’s New In Spring Boot 3.2",[651,185129,185130],{},[660,185131],{"alt":185132,"src":185133},"What's new in Spring Boot 3.2","/images/blog/2023/12/25/whats-new.jpeg",[651,185135,185136,185137,185140],{},"I have been talking a lot about Spring Boot 3.2 lately and decided to sit down and write a blog post. Instead of simply duplicating the ",[812,185138,95704],{"href":118322,"rel":185139},[816],", I want to highlight the major themes in this release: Runtime Efficiency, New Client Abstractions, and Observability Improvements.",[651,185142,185143],{},"Spring Boot 3 introduced runtime efficiency with features like Ahead-of-Time (AOT) compilation and support for creating GraalVM native images. In Spring Framework 6.1 and Spring Boot 3.2, we now have support for Java 21 and first-class support for Virtual Threads. Additionally, this release includes initial support for Project CRaC, which helps improve startup time.",[651,185145,185146],{},"I'm particularly excited about the new JDBC and REST Client features in this release. These two features will greatly benefit developers by providing easier and more fluent APIs, improving code readability and making coding more enjoyable.",[651,185148,185149,185150,185152],{},"Observability was a significant focus in Spring Boot 3.0. One of the key features was the Observability API, which allows both the framework and developers to add their own observations. In Spring Boot 3.2, support for a few annotations has been added, including ",[676,185151,119803],{},". Previously, some configuration was required to enable this feature, but now, as long as the Spring Boot AOP Starter is on the classpath, it will work seamlessly.",[651,185154,185155,185156,664],{},"I hope you enjoy reading the blog post, which can be found ",[812,185157,18263],{"href":120189,"rel":185158},[816],[4542,185160,185162],{"id":185161},"conferences-in-2024","Conferences in 2024",[651,185164,185165,185166,664],{},"I am preparing for several conferences in the new year, and I am starting to feel really excited. If you are interested in staying up to date with my upcoming speaking engagements or accessing a list of past presentations, you can visit my ",[812,185167,120451],{"href":120449,"rel":185168},[816],[5909,185170,97312],{"id":177852},[651,185172,185173],{},"When the new year begins, I will have two presentations at CodeMash in Sandusky, Ohio. I am excited to present on GraphQL and a new presentation on DevRel titled \"The Developer Relations Playbook: How to build bridges by putting people first\". I love this conference because I get to see my friend Chris Judd where he and Manifest Solutions puts on a great speaker dinner at a local pizzeria every year.",[5909,185175,185177],{"id":185176},"confoo","ConFoo",[651,185179,185180],{},"In February, I will be speaking at and attending my first ever ConFoo conference in Montreal, Canada. I am honored to have the opportunity to speak alongside many respected individuals in the community. I have heard only positive feedback about this conference, and I am thrilled to experience it firsthand. This week, I visited the conference's blog and was ecstatic to find my presentation listed there.",[651,185182,185183],{},[660,185184],{"alt":185177,"src":185185},"/images/blog/2023/12/25/confoo.png",[5909,185187,185189],{"id":185188},"great-international-developer-summit-gids","Great International Developer Summit (GIDS)",[651,185191,185192],{},"In April, I will be heading to India for the first time ever to attend and speak at the Great International Developer Summit. While browsing their website, I noticed that my talk is listed on the home page. It always serves as a reminder of how fortunate I am to be able to do what I love. I am also incredibly grateful and excited to reunite with old friends and make new acquaintances in India.",[651,185194,185195],{},[660,185196],{"alt":185197,"src":185198},"Great International Developer Summit","/images/blog/2023/12/25/gids.png",[4542,185200,177889],{"id":157573},[5909,185202,164959],{"id":69848},[651,185204,185205,185206,185210],{},"While we often refer to Project Loom as Virtual Threads it’s actually made up of a few different JEPs. I really enjoyed ",[812,185207,109096],{"href":185208,"rel":185209},"https://realjenius.com/2023/11/10/scoped-values/",[816]," about Scoped Values which is an important piece to the Loom puzzle.",[5909,185212,170434],{"id":79608},[651,185214,185215,185216,664],{},"The Java Champions Virtual Conference is coming in January and will be free 1/25/2024 - 1/30/2024. If you want to find out more about it you can check it out ",[812,185217,18263],{"href":168310,"rel":185218},[816],[5909,185220,165017],{"id":165016},[1004,185222,185223],{},[651,185224,185225],{},"“It’s a funny thing about life, once you begin to take note of the things you are grateful for, you begin to lose sight of the things that you lack.”― Germany Kent",[4542,185227,157704],{"id":157703},[651,185229,178368,185230,166271],{},[812,185231,41499],{"href":44086,"rel":185232},[816],[651,185234,185235,185236,185238],{},"Happy Coding\nDan Vega\n",[812,185237,161560],{"href":161111},[812,185239,53869],{"href":53869,"rel":185240},[816],{"title":674,"searchDepth":790,"depth":790,"links":185242},[185243,185244,185245,185250,185255],{"id":185084,"depth":790,"text":185085},{"id":185126,"depth":790,"text":185127},{"id":185161,"depth":790,"text":185162,"children":185246},[185247,185248,185249],{"id":177852,"depth":892,"text":97312},{"id":185176,"depth":892,"text":185177},{"id":185188,"depth":892,"text":185189},{"id":157573,"depth":790,"text":177889,"children":185251},[185252,185253,185254],{"id":69848,"depth":892,"text":164959},{"id":79608,"depth":892,"text":170434},{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":185257,"date":185258},"happy-holidays-2024","2023-12-25T07:00:00.000Z","/newsletter/2023/12/25/happy-holidays-2024",{"title":185073,"description":185078},"newsletter/2023/12/25/happy-holidays-2024","Cui-rUWABKPIJ6e5SmNNpLN2D72_J2Ns9FnuJYssMnc",{"id":185264,"title":185265,"body":185266,"description":185270,"extension":793,"meta":185459,"navigation":797,"path":185462,"seo":185463,"stem":185464,"__hash__":185465},"content/newsletter/2024/01/29/january-2024.md","Java Champion, REST Client and Conferences",{"type":648,"value":185267,"toc":185445},[185268,185271,185273,185282,185288,185290,185293,185301,185303,185305,185308,185316,185318,185321,185327,185331,185334,185336,185362,185364,185400,185402,185425,185427,185432],[651,185269,185270],{},"Hello and welcome to another edition of the newsletter. It's been a while since our last update, so there's a lot to catch up on. I decided to take a break in January, using it as a sort of \"scream test.\" This is a term from software development where we shut down a service to see if anyone is still using it. If anyone reacts, it shows us who is still engaged with that service. I applied this principle here, and while no one responded, I'm perfectly fine with that. I plan to continue sending this newsletter but may introduce some changes. For now, let's dive into some of the exciting things that happened in January.",[4542,185272,79570],{"id":79569},[651,185274,185275,185276,185281],{},"Earlier this month, I was named a Java Champion, a career goal I've had for a long time. I'm incredibly grateful for this honor as it's a recognition from my peers, whose opinions I highly value. I've decided to write a ",[812,185277,185280],{"href":185278,"rel":185279},"https://www.danvega.dev/blog/java-champion",[816],"detailed blog post"," about my career and what this award means to me. I was eager to receive my polo and jacket, which I decided to wear to church this week. 🤩",[651,185283,185284],{},[660,185285],{"alt":185286,"src":185287},"Dan's Java Champion Jacket","/images/newsletter/2024/01/29/dan-java-champion.jpeg",[4542,185289,144990],{"id":118965},[651,185291,185292],{},"I'm a big fan of the new Rest Client in Spring Framework 6.1 and Spring Boot 3.2. I've mentioned it in presentations and on Twitter. I started receiving many questions that dove deeper into the subject matter, questions I didn't initially know the answers to. So, I decided to take a deep dive into the Rest Client.",[651,185294,185295,185296,185300],{},"I've spent some time learning about all the things you can do with the Rest Client and created a ",[812,185297,89158],{"href":185298,"rel":185299},"https://github.com/danvega/rest-client-examples",[816]," to share these examples. I've also made a few videos which you'll find in the section below. If there's anything else you'd like me to cover about the Rest Client or clients in general, please don't hesitate to reach out.",[4542,185302,120025],{"id":120418},[5909,185304,97312],{"id":177852},[651,185306,185307],{},"Earlier this month, I participated in CodeMash in Sandusky, OH, where I presented two talks. The first one was on 'Building GraphQL APIs in Java with Spring for GraphQL', which had a fantastic turnout and received amazing feedback.",[651,185309,185310,185311,185315],{},"The second talk was on 'Developer Advocacy', a topic I was presenting for the first time. I believe it went well, and I'm excited to present it again. If you're in the Cleveland area and interested in hearing this talk, please consider attending a ",[812,185312,160392],{"href":185313,"rel":185314},"https://www.meetup.com/make-the-internet/events/298629881/",[816]," on Wednesday, February 7th.",[5909,185317,185177],{"id":185176},[651,185319,185320],{},"Next month I’m heading to Montreal Canada to attend and speak at my first ConFoo. I’m excited about this conference because it will be my first time there and there are so many amazing speakers in the lineup. I will be giving 2 talks, one on Spring for GraphQL and one on Spring Boot 3. If you plan on attending please stop by and say hello 👋🏻",[651,185322,185323,185324,184565],{},"If you’re ever interested in seeing what events or conferences I am speaking at you can check out the ",[812,185325,120451],{"href":120449,"rel":185326},[816],[4542,185328,185330],{"id":185329},"my-content","My Content",[651,185332,185333],{},"A collection of content I worked on in January.",[5909,185335,164959],{"id":69848},[5316,185337,185338,185343,185349,185356],{},[5332,185339,185340],{},[812,185341,111],{"href":120330,"rel":185342},[816],[5332,185344,185345],{},[812,185346,108],{"href":185347,"rel":185348},"https://www.danvega.dev/blog/happy-new-year-2024",[816],[5332,185350,185351],{},[812,185352,185355],{"href":185353,"rel":185354},"https://www.danvega.dev/blog/developer-advocate",[816],"What is Developer Relations (DevRel)",[5332,185357,185358],{},[812,185359,185361],{"href":185278,"rel":185360},[816],"I’m a Java Champion",[5909,185363,164971],{"id":157591},[5316,185365,185366,185373,185379,185386,185393],{},[5332,185367,185368],{},[812,185369,185372],{"href":185370,"rel":185371},"https://youtu.be/9M0NggD6Mbw",[816],"Spring Boot Rest Client (Underlying Http Client)",[5332,185374,185375],{},[812,185376,185378],{"href":185370,"rel":185377},[816],"Spring Boot Rest Client Test",[5332,185380,185381],{},[812,185382,185385],{"href":185383,"rel":185384},"https://twitter.com/therealdanvega/status/1750539660886004093",[816],"Rest Client Instance",[5332,185387,185388],{},[812,185389,185392],{"href":185390,"rel":185391},"https://twitter.com/therealdanvega/status/1750560170785054887",[816],"IntelliJ Live Templates",[5332,185394,185395],{},[812,185396,185399],{"href":185397,"rel":185398},"https://twitter.com/therealdanvega/status/1750891115178234046",[816],"Java Build Tools (Maven)",[5909,185401,164983],{"id":39439},[5316,185403,185404,185411,185418],{},[5332,185405,185406],{},[812,185407,185410],{"href":185408,"rel":185409},"https://www.youtube.com/watch?v=9zUaIiI47nc&t=2088s",[816],"Spring Office Hours S3E1 - Spring into the New Year",[5332,185412,185413],{},[812,185414,185417],{"href":185415,"rel":185416},"https://www.youtube.com/watch?v=lpJRW45v5qQ&t=1125s",[816],"Spring Office Hours S3E2 - Developer Advocacy",[5332,185419,185420],{},[812,185421,185424],{"href":185422,"rel":185423},"https://www.youtube.com/watch?v=KLQqK03TziI&t=36s",[816],"Spring Office Hours S3E3 - Spring Data JPA vs JDBC vs REST",[4542,185426,157704],{"id":157703},[651,185428,178368,185429,166271],{},[812,185430,41499],{"href":44086,"rel":185431},[816],[651,185433,41105,185434,69920,185436,185438,185440,185442],{},[41107,185435],{},[41107,185437],{},[812,185439,161560],{"href":161111},[41107,185441],{},[812,185443,53869],{"href":53869,"rel":185444},[816],{"title":674,"searchDepth":790,"depth":790,"links":185446},[185447,185448,185449,185453,185458],{"id":79569,"depth":790,"text":79570},{"id":118965,"depth":790,"text":144990},{"id":120418,"depth":790,"text":120025,"children":185450},[185451,185452],{"id":177852,"depth":892,"text":97312},{"id":185176,"depth":892,"text":185177},{"id":185329,"depth":790,"text":185330,"children":185454},[185455,185456,185457],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":39439,"depth":892,"text":164983},{"id":157703,"depth":790,"text":157704},{"slug":185460,"date":185461},"january-2024","2024-01-28T07:00:00.000Z","/newsletter/2024/01/29/january-2024",{"title":185265,"description":185270},"newsletter/2024/01/29/january-2024","zBr8O33VzZOhhH_xH12UFeBS3vMTdGORXDoVJK-ebc8",{"id":185467,"title":185468,"body":185469,"description":185473,"extension":793,"meta":185656,"navigation":797,"path":185659,"seo":185660,"stem":185661,"__hash__":185662},"content/newsletter/2024/03/04/february-2024.md","February Update - ConFoo, freeCodeCamp, YouTube and Upcoming Talks",{"type":648,"value":185470,"toc":185639},[185471,185474,185477,185480,185482,185485,185491,185494,185497,185500,185503,185506,185509,185514,185517,185520,185523,185526,185529,185531,185534,185537,185540,185543,185547,185550,185553,185557,185560,185563,185565,185568,185572,185575,185579,185588,185592,185599,185603,185612,185616,185619,185621,185626],[651,185472,185473],{},"Happy Monday and welcome to another edition of the newsletter. February was an amazing month and I’m excited to tell you what I have been up to. I also want to look ahead to a busy month of March and talk about some of my upcoming talks.",[651,185475,185476],{},"But first I want to start with the fact that we are into the month of March. This is one of my favorite times of the year because January and February are tough to get through around here. It’s freezing outside combined with gray skies and the fact that it gets dark out early makes it the dog days of winter.",[651,185478,185479],{},"When we get into March around here it starts to get warmer out and we know the short depressing days are behind us. One of the days I look forward to the most will happen this weekend when we Spring our clocks forward for Daylight Savings. This means that it will stay lighter out longer, and we can enjoy being outside.",[4542,185481,185177],{"id":185176},[651,185483,185484],{},"I was lucky enough to be invited to speak at this year's ConFoo conference in Montreal Canada. I have never been to this conference or to Montreal, so I was particularly excited about the unknown and visiting a new location. I checked in to the hotel only to be greeted by a rooftop pool that was heated 🤩",[651,185486,185487],{},[660,185488],{"alt":185489,"src":185490},"Hot Pool","/images/newsletter/2024/03/04/hot_pool.jpeg",[651,185492,185493],{},"The conference kicked off with a Java User Group Meeting at the Hotel where the conference was being held. Free food and drinks is always a great way to invite me to your parties. I also decided to get checked into the conference at this point because I heard from Yann (The conference organizer) that it would be pretty busy the next morning. After the user group meeting I got to bed knowing that the first day of the conference was going to be a busy one.",[651,185495,185496],{},"I woke up ready to go knowing that there was going to be a keynote and I had 2 talks to give. My first talk was right after the keynote, and it was in a pretty big room. No time or need to get nervous though, this talk was on GraphQL and a variation of one that I have given a lot. During the talk the projector decided to keep going out, and we ended up figuring out that it was the USB cable. Technical difficulties are never fun especially when they throw off your timing, but it is part of the deal and you need to learn to deal with them. I thought I did a good job and I thought the presentation went really well. I especially appreciated the feedback I got where people pointed out that I handled the technical issues well and I promise you that made me really happy.",[651,185498,185499],{},"My 2nd talk came later in the day and it was on Spring Boot 3 and beyond. I decided that in this talk I was going to show slides for half of the presentation and live code for the other half. All the rooms that I was in that morning had tables, so I thought I was going to luck out and have a nice environment to live code in. I got to my room though and realized I only had a podium, and I’m not a big fan of live coding on thos, but I made it work.",[651,185501,185502],{},"I thought both talks went really well, but I learned something at this conference, and it’s something I will use to improve on going forward. I try to pack way too much information into a talk. I need to get a little more specific with my talks and try not to pack so much into them, so I can slow down and explain things better.",[651,185504,185505],{},"I want to thank Yann and everyone at ConFoo for inviting me to this conference. The food was some of the best I have ever had at a conference, the speaker dinner was top-notch and the sessions were amazing. I had a great time in Montreal and I really hope to make it back one day.",[4542,185507,157957],{"id":185508},"freecodecamp",[651,185510,185511],{},[660,185512],{"alt":157957,"src":185513},"/images/newsletter/2024/03/04/freeCodeCamp.png",[651,185515,185516],{},"I have been a big fan of freeCodeCamp for a long time now. When I was doing curriculum development at a coding bootcamp we use to refer some of the students to their materials as prerequisites for day 1, and I think it really helped give them a good foundation.",[651,185518,185519],{},"I noticed that there wasn’t a ton of Java / Spring content on the channel and nothing with a lot of the recent updates in Spring Boot 3.2, so I thought it would be great to publish a course there. I reached out to a few people I knew and even asked on Twitter if anyone could me in touch with the right people. Thanks to a couple of people they were able to put me in touch with the right people in charge.",[651,185521,185522],{},"After talking to them they decided that I would be a good instructor for the platform so I pitched them my idea, and they gave me their blessing. With an initial outline for a course I decided to flush it out a little bit more and figure out exactly what was in scope and more inmportantly what was going to be out of scope.",[651,185524,185525],{},"I wanted to make this course, but I realized that I don’t have time to put together a 15-hour course which is probably where I would be If I put everything in that I wanted to. I also can’t speak for everyone but these really long courses are a little bit intimidating as a student. I don’t want to learn every possible feature, I just want to learn what I need to know to perform certain tasks.",[651,185527,185528],{},"With the code and the readme ready to go I sat down this past week to record and edit the course. I ended up with somehwere around 4 hours of content that I trimmed down to 3.5 for the final product. I did this all in about 3 days which I was really proud of. I am really happy with how this turned out and I can’t wait for this to get published on freeCodeCamp’s YouTube channel, so I can share it with you.",[4542,185530,15432],{"id":61224},[651,185532,185533],{},"I only produced 3 videos over the last month because of everything else I was working on with the conference and freeCodeCamp course but I thought they were good ones and based on the numbers and comments you really enjoyed them.",[5909,185535,185536],{"id":167023},"Spring Boot Starter",[651,185538,185539],{},"In this tutorial you will learn how to create your own custom Spring Boot Starter. Spring Boot has a number of pre-built starters that are great for bootstrapping your applications but how can you create your own? We will walk through the process of building a starter from scratch and talk about some of the processes for doing so.",[5988,185541],{"id":185542},"9m1bC57oWrc",[5909,185544,185546],{"id":185545},"common-mistakes","Common Mistakes",[651,185548,185549],{},"In this tutorial we will look at 5 common mistakes we make as Spring Developers and how we can improve on them. This isn't in any specific order but just 5 mistakes that I notice often when looking at the code of Spring Boot applications.",[5988,185551],{"id":185552},"PbkROQPTBao",[5909,185554,185556],{"id":185555},"http-interfaces-in-spring-mvc","Http Interfaces in Spring MVC",[651,185558,185559],{},"In this tutorial you will learn how to use Http Interfaces in Spring MVC. In Spring Boot 3.2 Http Interfaces will use the new Rest Client allowing you take advantage of Http Interfaces without having to pull in any other dependencies.",[5988,185561],{"id":185562},"aR580OCEp7w",[4542,185564,173888],{"id":173887},[651,185566,185567],{},"Conference season has officially begun and this is going to be a busy few months for me. I was bummed out that I didn’t get accepted to any of the Devoxx conferences I submitted to (UK, Greece and Paris) because I still haven’t been to any of those conferences but hopefully I will make it there one day.",[5909,185569,185571],{"id":185570},"atlanta-java-user-group","Atlanta Java User Group",[651,185573,185574],{},"Instead of dwelling on that I am going to focus on the positives. This month I am honored to be speaking at the Atlanta Java Users Group on Spring Boot 3. I love visiting my friends in Atlanta, and I am really looking forward to hanging out there.",[5909,185576,185578],{"id":185577},"intellij-live-stream","IntelliJ Live Stream",[651,185580,185581,185582,185587],{},"Later this month I will be sitting down with my good friends at ",[812,185583,185586],{"href":185584,"rel":185585},"https://blog.jetbrains.com/idea/category/livestreams/",[816],"IntelliJ,"," and we will be talking about Spring AI. I’m really looking forward to talking about generative AI and how Spring can help you write intelligent applications.",[5909,185589,185591],{"id":185590},"kcdc-conference","KCDC Conference",[651,185593,185594,185595,185598],{},"I am so excited to say that I had one of my talks accepted to ",[812,185596,173081],{"href":97034,"rel":185597},[816]," and this will be my 3rd year in a row presenting at this amazing conference. I’m really looking forward to catching up with friends, having some amazing BBQ and attending all the great sessions.",[5909,185600,185602],{"id":185601},"dev2next-conference","dev2Next Conference",[651,185604,185605,185606,185611],{},"Last but not least I had a couple of my sessions accepted to the inaugural ",[812,185607,185610],{"href":185608,"rel":185609},"https://www.dev2next.com/",[816],"dev2Next"," conference in Colorado. This conference is put on by Venkat and it looks like its going to be an amazing lineup of speakers.",[5909,185613,185615],{"id":185614},"customer-talks","Customer Talks",[651,185617,185618],{},"I have a few talks this month with customers around GraphQL, Spring Boot 3 and Spring AI. If you would like to have me and our team speak at your company please feel free to reach out about a SpringOne Tour stop at your place.",[4542,185620,157704],{"id":157703},[651,185622,178368,185623,166271],{},[812,185624,41499],{"href":44086,"rel":185625},[816],[651,185627,41105,185628,69920,185630,185632,185634,185636],{},[41107,185629],{},[41107,185631],{},[812,185633,161560],{"href":161111},[41107,185635],{},[812,185637,53869],{"href":53869,"rel":185638},[816],{"title":674,"searchDepth":790,"depth":790,"links":185640},[185641,185642,185643,185648,185655],{"id":185176,"depth":790,"text":185177},{"id":185508,"depth":790,"text":157957},{"id":61224,"depth":790,"text":15432,"children":185644},[185645,185646,185647],{"id":167023,"depth":892,"text":185536},{"id":185545,"depth":892,"text":185546},{"id":185555,"depth":892,"text":185556},{"id":173887,"depth":790,"text":173888,"children":185649},[185650,185651,185652,185653,185654],{"id":185570,"depth":892,"text":185571},{"id":185577,"depth":892,"text":185578},{"id":185590,"depth":892,"text":185591},{"id":185601,"depth":892,"text":185602},{"id":185614,"depth":892,"text":185615},{"id":157703,"depth":790,"text":157704},{"slug":185657,"date":185658},"february-2024","2024-03-04T07:00:00.000Z","/newsletter/2024/03/04/february-2024",{"title":185468,"description":185473},"newsletter/2024/03/04/february-2024","S3p4yX9c0tzU235RFVO0RTNvZjhyQCkX2bOWdovOQeY",{"id":185664,"title":185665,"body":185666,"description":185670,"extension":793,"meta":185779,"navigation":797,"path":185782,"seo":185783,"stem":185784,"__hash__":185785},"content/newsletter/2024/03/11/free-code-camp.md","Resources for learning Spring and Java is NOT slow and verbose",{"type":648,"value":185667,"toc":185773},[185668,185671,185674,185680,185684,185691,185694,185702,185705,185708,185712,185715,185718,185721,185724,185728,185731,185734,185739,185745,185753,185755,185760],[651,185669,185670],{},"Happy Monday and welcome to another edition of the newsletter. If you follow me you know this weekend was a big one for me as we hit Daylight Savings which is a big milestone around here. This means that our days are going to get longer, and it will stay light out later. This means it will start to get warmer here (it already has) and that long days of winter are over.",[651,185672,185673],{},"Seasonal depression is a real thing, and I’m excited to get back to running outside. This also means I will be able to start golfing again, and I’m very excited about that. I tell you this because it makes me happy and I think we should all celebrate whatever makes us happy.",[651,185675,185676,185677,185679],{},"Today I want to share want to share with you a resource for learning spring, tell you that Java is ",[2939,185678,10922],{}," slow and verbose.",[4542,185681,185683],{"id":185682},"resources-for-learning-spring","Resources for learning Spring",[651,185685,184201,185686,185690],{},[812,185687,97345],{"href":185688,"rel":185689},"https://spring-office-hours.transistor.fm/episodes/spring-office-hours-s3e8-resources-for-learning-spring",[816]," we built out a list of resources for learning Spring live on the show. It was a lot of fun because we got to collaborate with the audience and we put a pretty solid list together in an hour.",[5988,185692],{"id":185693},"W0u9XaCyWPo",[651,185695,185696,185697,185701],{},"After the show I took what we did and turned it into a ",[812,185698,89158],{"href":185699,"rel":185700},"https://github.com/spring-office-hours/resources-learning-spring",[816]," complete with categories and links to all the resources we discussed. When I was done with it I let everyone know that PRs are welcomed, and we got a lot of really great additions to the repo.",[651,185703,185704],{},"I also thought it might be a good idea to have a bookmarks file that you could just import into your browser and get all of these great links. I was able to manually put this together, but I would really love to find a way to automate this.",[651,185706,185707],{},"Finally, I have been on GitHub forever and I think the most stars a repo of mine has ever seen is 335. As of the time of writing this on Sunday night we have 420 stars on the repo which is just crazy considering it’s not even a week old yet. Thank you to everyone for the contributions and support, and we are going to talk more about this on this week's episode of Office Hours.",[4542,185709,185711],{"id":185710},"java-is-slow-and-verbose","Java is slow and verbose",[651,185713,185714],{},"I've worked with JVM for most of my 23-year career, and it seems like every week someone declares Java as dead, calling it ancient, verbose, and slow. However, these claims couldn't be further from the truth.",[651,185716,185717],{},"Such comments often come from people who haven't written any Java since their introductory computer science course 20 years ago. Yes, Java has evolved over the years, but that's expected of a language that has been around for 28 years. The world relies heavily on Java, and it remains one of the most popular programming languages, regardless of the metric or survey you consider.",[651,185719,185720],{},"I decided to make a video talking about some features that make Java fast and concise.",[5988,185722],{"id":185723},"lGAYLVk5HaY",[4542,185725,185727],{"id":185726},"youtube-studio","YouTube Studio",[651,185729,185730],{},"I have been really unhappy with some of my recent videos because I felt like they haven’t been looking good. Mainly I thought I had some lighting issues and I decided it was time to fix them. The best way I could think of doing this is to pull a lot of stuff out of my office and start with a blank cavas.",[651,185732,185733],{},"This meant to turn off all the lights and start introducing lights to see if I could get a better result than I was before. Here is an example of the before and after.",[651,185735,185736],{},[660,185737],{"alt":13157,"src":185738},"/images/newsletter/2024/03/11/before.png",[651,185740,185741],{},[660,185742],{"alt":185743,"src":185744},"After","/images/newsletter/2024/03/11/after.png",[651,185746,185747,185748,664],{},"If you’re interested in following the progress of this I put together a ",[812,185749,185752],{"href":185750,"rel":185751},"https://twitter.com/therealdanvega/status/1765746519427121276",[816],"Twitter thread",[4542,185754,157704],{"id":157703},[651,185756,178368,185757,166271],{},[812,185758,41499],{"href":44086,"rel":185759},[816],[651,185761,41105,185762,69920,185764,185766,185768,185770],{},[41107,185763],{},[41107,185765],{},[812,185767,161560],{"href":161111},[41107,185769],{},[812,185771,53869],{"href":53869,"rel":185772},[816],{"title":674,"searchDepth":790,"depth":790,"links":185774},[185775,185776,185777,185778],{"id":185682,"depth":790,"text":185683},{"id":185710,"depth":790,"text":185711},{"id":185726,"depth":790,"text":185727},{"id":157703,"depth":790,"text":157704},{"slug":185780,"date":185781},"resources-learning-spring","2024-03-11T07:00:00.000Z","/newsletter/2024/03/11/free-code-camp",{"title":185665,"description":185670},"newsletter/2024/03/11/free-code-camp","K5UHEcQ8Y9uSS1dhmVcN5g4QXCJzYnm2WPBnW4DPduE",{"id":185787,"title":185788,"body":185789,"description":185793,"extension":793,"meta":185990,"navigation":797,"path":185993,"seo":185994,"stem":185995,"__hash__":185996},"content/newsletter/2024/03/18/pull-requests-welcomed.md","freeCodeCamp, Pull Requests are welcomed and Spring AI",{"type":648,"value":185790,"toc":185978},[185791,185794,185797,185801,185814,185817,185820,185824,185831,185834,185851,185853,185858,185867,185872,185875,185881,185890,185894,185897,185901,185904,185907,185911,185914,185917,185921,185924,185927,185931,185937,185958,185960,185965],[651,185792,185793],{},"Happy Monday and welcome to another edition of the newsletter. In typical Cleveland weather I was able to get out and play a quick 9 holes of golf earlier this week and now as I’m writing this on Sunday it’s snowing. This week I’m heading out on the road for a quick trip to Atlanta to visit the Atlanta Java User Group. This is one of the biggest user groups in the country and I’m really looking forward to this visit.",[651,185795,185796],{},"This past week was an excellent one and started out strong with one of our best Spring Office Hours shows ever. I’m also in the midst of putting together a talk on building intelligent applications with Spring AI. This is a project I’m very excited about and can’t wait to tell you more about it. Finally, I was able to film, edit and publish 3 videos this week on Spring Native Images, Primitive vs Reference Types in Java and Upgrading with OpenRewrite.",[4542,185798,185800],{"id":185799},"freecode-camp-course-is-live","freeCode Camp Course is Live",[651,185802,185803,185804,185808,185809,185813],{},"A few weeks ago I reached out to ",[812,185805,157957],{"href":185806,"rel":185807},"https://www.freecodecamp.org/",[816]," to see if I could build a course for their ",[812,185810,101297],{"href":185811,"rel":185812},"https://www.youtube.com/@freecodecamp",[816],". If you aren’t familiar with freeCodeCamp they are a place where you can learn to code for free. When I was developing curriculum for a coding bootcamp we used to use a lot of their courses as prerequisites for the course, and they are an amazing resource.",[651,185815,185816],{},"I sat down and wrote a curriculum, recorded about 4 hours of video and published this course in a week. This is an introduction to building web applications in Java with Spring Boot 3. I’m really happy with how this course turned out, and I’m really excited to share it with you. If you have a chance to go through it, I would really love your feedback.",[5988,185818],{"id":185819},"31KTdfRH6nY",[4542,185821,185823],{"id":185822},"pull-requests-welcome","Pull Requests Welcome",[651,185825,185826,185827,185830],{},"Last week started with one of our best Spring Office Hours shows yet! In a previous episode we built out a ",[812,185828,16596],{"href":185699,"rel":185829},[816]," that contains some really great resources for learning Spring. In this episode we decided to have a chat around contributing to open source software, including this repo we created. We ended up having like 333 live viewers and that repo that we started now has 565 stars. This was just another reminder of great this community is, and we can’t thank you enough for all of your support.",[5988,185832],{"id":185833},"Ap1XaG0WPeE",[651,185835,185836,185837,185840,185841,185845,185846,664],{},"As a reminder we stream live every Monday at 1 PM EDT and the replay for that stream can be found on the ",[812,185838,164910],{"href":101239,"rel":185839},[816],". We also turn that stream into a podcast and if you would like to find out more about that you can head over to ",[812,185842,185843],{"href":185843,"rel":185844},"https://springofficehours.io",[816],". This week we will be discussing how to do ",[812,185847,185850],{"href":185848,"rel":185849},"https://www.youtube.com/watch?v=0Os62f_KDPs",[816],"batch processing with Spring Batch",[4542,185852,123549],{"id":140820},[651,185854,185855],{},[660,185856],{"alt":123549,"src":185857},"/images/newsletter/2024/03/18/spring-ai.png",[651,185859,185860,185861,185866],{},"I had some time last week to spend working with ",[812,185862,185865],{"href":185863,"rel":185864},"https://spring.io/projects/spring-ai",[816],"Spring AI,"," and I’m really excited about where this project already is and where it is headed. What is Spring AI you ask?",[1004,185868,185869],{},[651,185870,185871],{},"Spring AI is an application framework for AI engineering. Its goal is to apply to the AI domain Spring ecosystem design principles such as portability and modular design and promote using POJOs as the building blocks of an application to the AI domain.",[651,185873,185874],{},"In short, it gives you a nice abstraction layer over the different LLMs (Large Language Models) and simplifies working with the inputs (Prompts) and outputs (Output Parsers). I was able to put a bunch of really cool examples in a short amount of time starting with this simple example that talks to Open AI (The folks behind ChatGPT).",[651,185876,185877],{},[660,185878],{"alt":185879,"src":185880},"spring-ai-open-ai.png","/images/newsletter/2024/03/18/spring-ai-open-ai.png",[651,185882,185883,185884,185889],{},"If you’re interested in hearing more about this you can register for an ",[812,185885,185888],{"href":185886,"rel":185887},"https://blog.jetbrains.com/idea/2024/03/new-livestream-building-intelligent-applications-with-spring-ai/",[816],"upcoming livestream"," with my good friends at JetBrains.",[4542,185891,185893],{"id":185892},"_3-new-videos-last-week","3 New Videos Last week!",[651,185895,185896],{},"I was able to knock out 3 new videos last week and I have been receiving some really great feedback on them.",[5909,185898,185900],{"id":185899},"building-native-images-in-spring-with-graalvm","Building native images in Spring with GraalVM",[651,185902,185903],{},"In this tutorial you will learn the what, why and how of building native images in Spring Boot with GraalVM. Ahead-of-Time (AoT) compilation is a prerequisite for building native images with GraalVM. You will learn where this makes sense, and then you will build out 2 simple Spring Boot Applications and build a native image from them.",[5988,185905],{"id":185906},"FjRBHKUP-NA",[5909,185908,185910],{"id":185909},"java-class-design-primitive-vs-reference-types","Java Class Design: Primitive vs Reference Types",[651,185912,185913],{},"In this video I will discuss when to use a primitive vs reference type when designing Java classes. I will also answer some questions I have received around immutability and record types.",[5988,185915],{"id":185916},"bQG5lzlzo6I",[5909,185918,185920],{"id":185919},"upgrading-your-java-and-spring-boot-apps-with-openrewrite","Upgrading your Java and Spring Boot apps with OpenRewrite",[651,185922,185923],{},"In this tutorial we will take a look at a project called OpenRewrite that allows you to automate your upgrades. We are going to look at the newly bundled plugin in IntelliJ for running your recipes right in your IDE. We will look at both a simple Java example and then how to upgrade a Spring Boot 2.7.x app to 3.0.",[5988,185925],{"id":185926},"e4R6AZHpAD8",[4542,185928,185930],{"id":185929},"upcoming-presentations","Upcoming Presentations",[651,185932,185933,185934,664],{},"Last week I gave a really great presentation to (You know who you are) on Spring Boot 3 and I have a bunch of really great events lined up over the next month. As always if you’re ever interested in finding out where I will be next you can check out my ",[812,185935,120451],{"href":120449,"rel":185936},[816],[5316,185938,185939,185946,185952],{},[5332,185940,185941],{},[812,185942,185945],{"href":185943,"rel":185944},"https://www.meetup.com/atlantajug/events/298455293/",[816],"Tuesday, March 19 - Atlanta Java User Group",[5332,185947,185948],{},[812,185949,185951],{"href":181719,"rel":185950},[816],"Wednesday, March 20 - O’Reilly Learning Platform",[5332,185953,185954],{},[812,185955,185957],{"href":185886,"rel":185956},[816],"Thursday, March 28 - IntelliJ Live Stream",[4542,185959,157704],{"id":157703},[651,185961,178368,185962,166271],{},[812,185963,41499],{"href":44086,"rel":185964},[816],[651,185966,41105,185967,69920,185969,185971,185973,185975],{},[41107,185968],{},[41107,185970],{},[812,185972,161560],{"href":161111},[41107,185974],{},[812,185976,53869],{"href":53869,"rel":185977},[816],{"title":674,"searchDepth":790,"depth":790,"links":185979},[185980,185981,185982,185983,185988,185989],{"id":185799,"depth":790,"text":185800},{"id":185822,"depth":790,"text":185823},{"id":140820,"depth":790,"text":123549},{"id":185892,"depth":790,"text":185893,"children":185984},[185985,185986,185987],{"id":185899,"depth":892,"text":185900},{"id":185909,"depth":892,"text":185910},{"id":185919,"depth":892,"text":185920},{"id":185929,"depth":790,"text":185930},{"id":157703,"depth":790,"text":157704},{"slug":185991,"date":185992},"free-code-camp","2024-03-18T07:00:00.000Z","/newsletter/2024/03/18/pull-requests-welcomed",{"title":185788,"description":185793},"newsletter/2024/03/18/pull-requests-welcomed","_Q0aP_qLpnsL-d3mZ4xCczzz2UN0eUWIy958lrKENO4",{"id":185998,"title":185999,"body":186000,"description":186004,"extension":793,"meta":186185,"navigation":797,"path":186188,"seo":186189,"stem":186190,"__hash__":186191},"content/newsletter/2024/03/25/atlanta-jug.md","Hello Atlanta, Java 22 and a BIG YouTube Milestone",{"type":648,"value":186001,"toc":186175},[186002,186005,186011,186017,186020,186024,186027,186033,186039,186042,186048,186051,186055,186063,186069,186073,186085,186088,186096,186099,186102,186108,186111,186117,186123,186126,186128,186136,186138,186142,186150,186155,186157,186162],[651,186003,186004],{},"Happy Monday and welcome to another edition of the newsletter. While Spring has officially arrived here in Northeast Ohio we are still seeing snow and temps in the 20s. This week's forecast is starting to look a little better as we are getting ready to celebrate Easter. We had an Easter egg hunt this past weekend and the girls had a blast!",[651,186006,186007],{},[660,186008],{"alt":186009,"src":186010},"Easter Girls","/images/newsletter/2024/03/25/easter_girls.JPG",[651,186012,186013],{},[660,186014],{"alt":186015,"src":186016},"Easter Family","/images/newsletter/2024/03/25/easter_family.JPG",[651,186018,186019],{},"Major League Baseball starts back up this week and I always look forward to this time of year. I know I’m beginning to sound like a broken record but my physical and mental health need this cold weather to move on to wherever it goes. In this edition of the newsletter I want to tell you about my trip down to Atlanta for the Atlanta Java User Group meetup, the release of Java 22 and I crossed a huge milestone on YouTube.",[4542,186021,186023],{"id":186022},"my-trip-to-atlanta","My trip to Atlanta",[651,186025,186026],{},"I took a quick trip down to Atlanta for a meetup at the Atlanta Java User Group. I have a lot of friends in Atlanta, so I would always see pictures of the meetups and really wanted to get there one day. I got to the venue a little bit earlier and to my surprise Neal Ford was there. Neal is the author of so many books that line my bookshelf, so it was really great to meet him. I also had an opportunity to sit down with my friend Glenn and that was really nice to catch up with him for a little bit.",[651,186028,186029],{},[660,186030],{"alt":186031,"src":186032},"Dan + Neal","/images/newsletter/2024/03/25/dan_neal.jpeg",[651,186034,186035],{},[660,186036],{"alt":186037,"src":186038},"Dan + Neal + Glenn","/images/newsletter/2024/03/25/dan_neal_glenn.jpeg",[651,186040,186041],{},"I gave a talk on Spring Boot 3 and Beyond, and it was about half slides and half coding. I thought the presentation went really well with everyone asking some really amazing questions. The crowd was amazing and I really fed off of it. After the presentation I had some time to chat with a lot of folks, and it was a really great time.",[651,186043,186044],{},[660,186045],{"alt":186046,"src":186047},"Atlanta JUG Crowd","/images/newsletter/2024/03/25/atlanta_jug_crowd.jpeg",[651,186049,186050],{},"I really want to thank the AJUG for having me and for everyone that was in attendance. It really was an awesome crowd and I feel honored to have been there.",[5909,186052,186054],{"id":186053},"oreilly-spring-recipes","O’Reilly - Spring Recipes",[651,186056,186057,186058,186062],{},"The very next morning at 9 AM EDT I had a training for the O’Reilly Learning Platform. This was a 4-hour training on “",[812,186059,186061],{"href":181719,"rel":186060},[816],"Spring Recipes - Practical Solutions to common problems","” with my good friend Nate Schutta. I wouldn’t have time to get home, so I had a little bit of a traveling setup ready to go in my hotel room. The hotel room Wi-Fi was good and I thought the training went really well. Thank you to everyone who joined us!",[651,186064,186065],{},[660,186066],{"alt":186067,"src":186068},"My Hotel Setup","/images/newsletter/2024/03/25/hotel_presentation.jpeg",[4542,186070,186072],{"id":186071},"java-22","Java 22",[651,186074,186075,186076,186079,186080,186084],{},"Java 22 was released last week, and it's already available as on option on the ",[812,186077,94303],{"href":51358,"rel":186078},[816],"! If you want to learn more about it, you can check out the ",[812,186081,95704],{"href":186082,"rel":186083},"https://jdk.java.net/22/release-notes",[816],". The team at Oracle had a really great release party where they went through the new features on a live stream that was almost 5 hours long. If you haven't had a chance to check it you can watch the recording below.",[5988,186086],{"id":186087},"AjjAZsnRXtE",[651,186089,186090,186091,186095],{},"If you want to learn more you can check out ",[812,186092,180074],{"href":186093,"rel":186094},"https://www.youtube.com/watch?v=ew_wc-mNHfM",[816]," as DaShaun and I will discuss Java 22 and Spring Boot 3.3.",[4542,186097,172040],{"id":186098},"youtube-milestone",[651,186100,186101],{},"I crossed a pretty big milestone this past week passing 50k subscribers on YouTube! Funny enough my wife informed me she wasn't subscribed, so we were able to time it out, and she was my 50,000 subscriber.",[651,186103,186104],{},[660,186105],{"alt":186106,"src":186107},"50k","/images/newsletter/2024/03/25/50k.jpeg",[651,186109,186110],{},"We went out to one of our favorite restaurants that evening. They were so nice and gave me this really cool card and dessert!",[651,186112,186113],{},[660,186114],{"alt":186115,"src":186116},"50k Card","/images/newsletter/2024/03/25/50k_card.JPG",[651,186118,186119],{},[660,186120],{"alt":186121,"src":186122},"50k Dessert","/images/newsletter/2024/03/25/50k_dessert.jpeg",[651,186124,186125],{},"I had so many people reach out to me and say some really nice things and I can't thank you all enough. YouTube, Java and Spring are really great communities to be a part of and I feel very lucky that I get to do this every day!",[4542,186127,185800],{"id":185799},[651,186129,186130,186131,186135],{},"If you missed it my 3.5 hour course on Spring Boot 3 is now live on freeCodeCamp. Beau from freeCodeCamp also wrote up a really nice ",[812,186132,24879],{"href":186133,"rel":186134},"https://www.freecodecamp.org/news/learn-app-development-with-spring-boot-3/",[816]," that you can check out. This video has gotten 70,000 views in the first week which is really mind-blowing 🤯 Thank you to everyone who has given this a watch and had something nice to say about it.",[5988,186137],{"id":185819},[4542,186139,186141],{"id":186140},"whats-happening-this-week","What’s happening this week",[651,186143,186144,186145,186149],{},"This week I'm putting the final touches on my ",[812,186146,186148],{"href":185886,"rel":186147},[816],"Spring AI talk"," for my friends at IntelliJ. In this presentation I will be covering some of the fundamentals of AI and how to get started with Spring AI. If you're interested please register for free and I will see you on Thursday!",[651,186151,186152],{},[812,186153,185886],{"href":185886,"rel":186154},[816],[4542,186156,157704],{"id":157703},[651,186158,178368,186159,166271],{},[812,186160,41499],{"href":44086,"rel":186161},[816],[651,186163,41105,186164,69920,186166,186168,186170,186172],{},[41107,186165],{},[41107,186167],{},[812,186169,161560],{"href":161111},[41107,186171],{},[812,186173,53869],{"href":53869,"rel":186174},[816],{"title":674,"searchDepth":790,"depth":790,"links":186176},[186177,186180,186181,186182,186183,186184],{"id":186022,"depth":790,"text":186023,"children":186178},[186179],{"id":186053,"depth":892,"text":186054},{"id":186071,"depth":790,"text":186072},{"id":186098,"depth":790,"text":172040},{"id":185799,"depth":790,"text":185800},{"id":186140,"depth":790,"text":186141},{"id":157703,"depth":790,"text":157704},{"slug":186186,"date":186187},"atlanta-jug","2024-03-25T07:00:00.000Z","/newsletter/2024/03/25/atlanta-jug",{"title":185999,"description":186004},"newsletter/2024/03/25/atlanta-jug","fgjvkFev-LGji8U2jEn5JlNJ1wU9wfKvk423TXr23QI",{"id":186193,"title":186194,"body":186195,"description":186199,"extension":793,"meta":186438,"navigation":797,"path":186440,"seo":186441,"stem":186442,"__hash__":186443},"content/newsletter/2024/04/01/spring-into-ai.md","Spring into AI and the last episode of Spring Office Hours 😉",{"type":648,"value":186196,"toc":186431},[186197,186200,186206,186209,186213,186216,186219,186226,186233,186236,186239,186241,186248,186257,186395,186398,186401,186405,186408,186411,186413,186418],[651,186198,186199],{},"Happy Monday and welcome to another edition of our newsletter. For anyone who celebrates Easter I hope you had an amazing day filled with love, food, family and friends. I was able to take this picture of my family before we went to church yesterday morning. Pro Tip: If you have an iPhone and an Apple Watch you can set your phone on a tripod and take pictures using your watch 🤯",[651,186201,186202],{},[660,186203],{"alt":186204,"src":186205},"Family Picture on Easter Sunday","/images/newsletter/2024/04/01/easter-family.jpeg",[651,186207,186208],{},"Although it's April Fools' Day, I'll skip the jokes as I know you're all too clever for that, so let's get straight to business. Please note that there will be no newsletter next week since my family and I are taking a much-needed vacation. We're off to Marco Island, Florida for some fun in the sun, and I couldn't be more thrilled. I plan to set aside my laptop for a week to recharge ahead of a busy few months. In today's newsletter, I'll discuss Spring AI and some exciting developments with the podcast, Spring Office Hours.",[4542,186210,186212],{"id":186211},"spring-into-ai","Spring into AI",[651,186214,186215],{},"Artificial Intelligence (AI) is a prevalent topic in today's tech industry. While some may worry about AI taking over their jobs, I am excited about its potential as a tool to enhance my daily work.",[651,186217,186218],{},"Machine Learning is often associated with Python, as it has been the go-to language for training these models for a long time. However, with the advent of Large Language Models (LLMs), we are entering a new phase where these models are pre-trained and can be accessed through REST APIs.",[651,186220,186221,186222,186225],{},"Java is one of the most popular programming languages in the world and is used all over enterprise companies. With LLMs like ",[676,186223,186224],{},"gpt-4"," making their services available via REST API you could just call the API with your existing clients. It turns out there is a lot more you have to think about though when it comes to configuration, tokens, prompts, output parsing, embeddings, vector databases and more.",[651,186227,186228,186229,186232],{},"This is why I am particularly excited about a new project in the Spring ecosystem called ",[812,186230,123549],{"href":185863,"rel":186231},[816],". This project offers an abstraction layer over various LLMs, providing a consistent method of working with them.",[651,186234,186235],{},"Last week I had an opportunity to live stream with my friends over at JetBrains, the maker of my favorite IDE, IntelliJ. I had a chance to talk about AI terminology, Java and AI, Spring AI, and I was able to answer some really great questions from the crowd. Thanks to everyone who joined me live and for those of you who missed it, you can catch the replay below.",[5988,186237],{"id":186238},"x6KmUyPWy2Q",[4542,186240,97345],{"id":97344},[651,186242,186243,186244,186247],{},"I've seen significant growth in the ",[812,186245,158537],{"href":101215,"rel":186246},[816]," recently and I want to begin by expressing my gratitude to everyone who has supported it. I think we have had some really great topics lately and last week we had the opportunity to talk about Spring Boot 3.3 and Java 22.",[651,186249,186250,186251,186256],{},"Java 22 was recently released, and we examined all the JEPs included in this version. It featured many previews and second previews, including two JEPs from Project Loom (462,464). I had hoped these would finalize in version 22, but we will need to ",[812,186252,186255],{"href":186253,"rel":186254},"http://wait.One",[816],"wait."," One cool feature that I was excited to see was the ability to launch multi-file source code programs.",[1031,186258,186259,186272],{},[1034,186260,186261],{},[1037,186262,186263,186266],{},[1040,186264,186265],{},"423:",[1040,186267,186268],{},[812,186269,186270],{"href":186270,"rel":186271},"https://openjdk.org/jeps/423",[816],[1050,186273,186274,186285,186296,186307,186318,186329,186340,186351,186362,186373,186384],{},[1037,186275,186276,186279],{},[1055,186277,186278],{},"447:",[1055,186280,186281],{},[812,186282,186283],{"href":186283,"rel":186284},"https://openjdk.org/jeps/447",[816],[1037,186286,186287,186290],{},[1055,186288,186289],{},"454:",[1055,186291,186292],{},[812,186293,186294],{"href":186294,"rel":186295},"https://openjdk.org/jeps/454",[816],[1037,186297,186298,186301],{},[1055,186299,186300],{},"456:",[1055,186302,186303],{},[812,186304,186305],{"href":186305,"rel":186306},"https://openjdk.org/jeps/456",[816],[1037,186308,186309,186312],{},[1055,186310,186311],{},"457:",[1055,186313,186314],{},[812,186315,186316],{"href":186316,"rel":186317},"https://openjdk.org/jeps/457",[816],[1037,186319,186320,186323],{},[1055,186321,186322],{},"458:",[1055,186324,186325],{},[812,186326,186327],{"href":186327,"rel":186328},"https://openjdk.org/jeps/458",[816],[1037,186330,186331,186334],{},[1055,186332,186333],{},"459:",[1055,186335,186336],{},[812,186337,186338],{"href":186338,"rel":186339},"https://openjdk.org/jeps/459",[816],[1037,186341,186342,186345],{},[1055,186343,186344],{},"460:",[1055,186346,186347],{},[812,186348,186349],{"href":186349,"rel":186350},"https://openjdk.org/jeps/460",[816],[1037,186352,186353,186356],{},[1055,186354,186355],{},"461:",[1055,186357,186358],{},[812,186359,186360],{"href":186360,"rel":186361},"https://openjdk.org/jeps/461",[816],[1037,186363,186364,186367],{},[1055,186365,186366],{},"462:",[1055,186368,186369],{},[812,186370,186371],{"href":186371,"rel":186372},"https://openjdk.org/jeps/462",[816],[1037,186374,186375,186378],{},[1055,186376,186377],{},"463:",[1055,186379,186380],{},[812,186381,186382],{"href":186382,"rel":186383},"https://openjdk.org/jeps/463",[816],[1037,186385,186386,186389],{},[1055,186387,186388],{},"464:",[1055,186390,186391],{},[812,186392,186393],{"href":186393,"rel":186394},"https://openjdk.org/jeps/464",[816],[651,186396,186397],{},"We also had a chance to look at what’s coming in Spring Boot 3.3 which will launch on May 23. We took a look at the 3 milestone releases by reviewing the release notes. If you’re interested in learning about Java 22 and Spring Boot 3.3 you can check out the replay of our live stream below",[5988,186399],{"id":186400},"ew_wc-mNHfM",[5909,186402,186404],{"id":186403},"the-very-last-spring-office-hours","The very last Spring Office Hours 😉",[651,186406,186407],{},"With that said I am sad to say that today’s show is going to be our very last episode of Spring Office Hours. We hope you can drop by and hangout with us and say goodbye to our Podcast. It’s been a lot of fun hanging out with all of you every week but all good things eventually come to an end.",[5988,186409],{"id":186410},"V7dO-gJ8fMY",[4542,186412,157704],{"id":157703},[651,186414,178368,186415,166271],{},[812,186416,41499],{"href":44086,"rel":186417},[816],[651,186419,41105,186420,69920,186422,186424,186426,186428],{},[41107,186421],{},[41107,186423],{},[812,186425,161560],{"href":161111},[41107,186427],{},[812,186429,53869],{"href":53869,"rel":186430},[816],{"title":674,"searchDepth":790,"depth":790,"links":186432},[186433,186434,186437],{"id":186211,"depth":790,"text":186212},{"id":97344,"depth":790,"text":97345,"children":186435},[186436],{"id":186403,"depth":892,"text":186404},{"id":157703,"depth":790,"text":157704},{"slug":186211,"date":186439},"2024-04-01T07:00:00.000Z","/newsletter/2024/04/01/spring-into-ai",{"title":186194,"description":186199},"newsletter/2024/04/01/spring-into-ai","Pl8AUpdIulT70_bZYZVIoXuy7gXcUgw8TAwMGNugQfA",{"id":186445,"title":186446,"body":186447,"description":186451,"extension":793,"meta":186619,"navigation":797,"path":186621,"seo":186622,"stem":186623,"__hash__":186624},"content/newsletter/2024/04/22/spring-boot-frontends.md","Spring Boot Frontends, Spring AI and Spring Academy Pro is now FREE!",{"type":648,"value":186448,"toc":186607},[186449,186452,186455,186457,186466,186469,186472,186478,186482,186485,186488,186497,186500,186509,186515,186519,186527,186529,186531,186561,186563,186572,186574,186579,186581,186587,186589,186594],[651,186450,186451],{},"Happy Monday, and welcome to another edition of the newsletter. Unfortunately, I must start today's newsletter on a somber note. This week is the Great International Developer Summit (GIDS), where I was scheduled to deliver two talks. Regrettably, I won't be able to attend. I was truly excited about meeting many amazing developers and speakers in India, a country I have yet to visit. I’m going to try and not get too hung up on though and hope I have another chance next year.",[651,186453,186454],{},"I have exciting news about two other conferences, but I'll keep you in suspense for now. Keep reading to find out more! In this week's edition of the newsletter, I'll discuss Spring AI, Frontend Development for Java Developers, and an exciting update on Spring Academy Pro.",[4542,186456,123549],{"id":140820},[651,186458,186459,186460,186465],{},"In other conference news, I was recently accepted to ",[812,186461,186464],{"href":186462,"rel":186463},"https://2024.jconf.dev/",[816],"jConf.dev",", taking place in Dallas, TX from September 24 to 26. I will be presenting a talk on building intelligent applications in Spring with AI, a topic I have been deeply researching and am passionate about. If you missed my introduction to Spring AI, you can watch the video below.",[5988,186467],{"id":186468},"yyvjT0v3lpY",[651,186470,186471],{},"I have a handful of videos that are ready to record so I am going to work hard on getting some of those out the door this week.I also started working on some demos around Retrieval Augmentation Generation (RAG) and this has been a lot of fun working on. I ended up building a native command-line application using Spring Shell + Spring Ai that uses the Spring Boot Reference docs to answer questions.",[651,186473,186474],{},[660,186475],{"alt":186476,"src":186477},"Spring AI + Spring Shell Native App","/images/newsletter/2024/04/22/spring-ai-cml.png",[4542,186479,186481],{"id":186480},"spring-boot-frontends","Spring Boot Frontends",[651,186483,186484],{},"In the last episode of Spring Office Hours we took some time to discuss the ever changing landscape of frontend development but specifically for Java Developers. We discussed a lot of the options out there and when you might want to reach for one over the other.",[5988,186486],{"id":186487},"-wi4U1rOn-k",[651,186489,186490,186491,186496],{},"One of the topics we touched on in that show was htmx. I understand the concepts but admittedly don’t have a ton of experience with it. That is why we decided to bring on the author of the book ",[812,186492,186495],{"href":186493,"rel":186494},"https://leanpub.com/modern-frontends-with-htmx",[816],"Modern Frontends with htmx",", Wim Deblauwe. In this weeks episode we will ask Wim about htmx, how to get started with it in Spring and when to reach for it.",[5988,186498],{"id":186499},"y5wcruuSfvc",[651,186501,186502,186503,186508],{},"On the topic of frontends I’m happy to announce that I will be ",[812,186504,186507],{"href":186505,"rel":186506},"https://2024.springio.net/sessions/a-spring-developers-guide-to-navigating-the-frontend-landscape/",[816],"giving a talk"," on this subject at Spring I/O in Barcelona next month. This will be my first time attending and speaking at this conference and I’m beyond excited to meet so many people I work with and talk to online for the first time.",[651,186510,186511],{},[660,186512],{"alt":186513,"src":186514},"Spring I/O Talk","/images/newsletter/2024/04/22/spring-io-talk.png",[4542,186516,186518],{"id":186517},"spring-academy-pro","Spring Academy Pro",[651,186520,186521,186522,186526],{},"I mentioned this on last weeks episode of Spring Office Hours but in case you missed it ",[812,186523,120369],{"href":186524,"rel":186525},"https://spring.academy/",[816]," Pro is now FREE. There was always a free tier available but if you wanted access to more of the pro courses you needed to pay for the pro subscription. This also came with an exam voucher to take the Spring Certification which will no longer be a part of that package. If you want to get certified you can still pay the $299 but until you’re ready you can binge all of the content Spring Academy has to offer. If you’re looking to learn Spring this is the best place to start.",[4542,186528,177889],{"id":157573},[5909,186530,164959],{"id":69848},[5316,186532,186533,186540,186547,186554],{},[5332,186534,186535],{},[812,186536,186539],{"href":186537,"rel":186538},"https://maciejwalkowiak.com/blog/spring-data-jpa-dynamic-projections/",[816],"Spring Data JPA Dynamic Projections",[5332,186541,186542],{},[812,186543,186546],{"href":186544,"rel":186545},"https://github.com/pkl-community/awesome-pkl",[816],"Awesome PKL",[5332,186548,186549],{},[812,186550,186553],{"href":186551,"rel":186552},"https://blog.jetbrains.com/idea/2024/04/java-frameworks-you-must-know-in-2024",[816],"Java Frameworks you must know in 2024",[5332,186555,186556],{},[812,186557,186560],{"href":186558,"rel":186559},"https://spring.io/blog/2024/04/16/spring-framework-6-2-0-m1-overriding-beans-in-tests",[816],"Spring Framework 6.2.0-M1 - Overriding beans in tests",[5909,186562,164971],{"id":157591},[651,186564,186565,186566,186571],{},"In preparation for my upcoming chat with Wim about htmx in Spring I watched ",[812,186567,186570],{"href":186568,"rel":186569},"https://www.youtube.com/watch?v=okCdaBTQsik",[816],"his presentation"," from Spring I/O last year and I really enjoyed it.",[5909,186573,165017],{"id":165016},[1004,186575,186576],{},[651,186577,186578],{},"“When one door closes another door opens; but we so often look so long and so regretfully upon the closed door, that we do not see the ones which open for us.” - Alexander Graham Bell",[5909,186580,178573],{"id":95961},[651,186582,186583],{},[812,186584,186585],{"href":186585,"rel":186586},"https://twitter.com/therealdanvega/status/1780621419795062849",[816],[4542,186588,157704],{"id":157703},[651,186590,178368,186591,166271],{},[812,186592,41499],{"href":44086,"rel":186593},[816],[651,186595,41105,186596,69920,186598,186600,186602,186604],{},[41107,186597],{},[41107,186599],{},[812,186601,161560],{"href":161111},[41107,186603],{},[812,186605,53869],{"href":53869,"rel":186606},[816],{"title":674,"searchDepth":790,"depth":790,"links":186608},[186609,186610,186611,186612,186618],{"id":140820,"depth":790,"text":123549},{"id":186480,"depth":790,"text":186481},{"id":186517,"depth":790,"text":186518},{"id":157573,"depth":790,"text":177889,"children":186613},[186614,186615,186616,186617],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":165016,"depth":892,"text":165017},{"id":95961,"depth":892,"text":178573},{"id":157703,"depth":790,"text":157704},{"slug":186480,"date":186620},"2024-04-22T07:00:00.000Z","/newsletter/2024/04/22/spring-boot-frontends",{"title":186446,"description":186451},"newsletter/2024/04/22/spring-boot-frontends","2Y70EiWnhmMz0OL4eMTIA5W__IVmt_ylAvuvYwUH2vI",{"id":186626,"title":186627,"body":186628,"description":186632,"extension":793,"meta":186785,"navigation":797,"path":186788,"seo":186789,"stem":186790,"__hash__":186791},"content/newsletter/2024/05/13/spring-office-hours-turns-1.md","Spring Office Hours turns 1, Spring I/O and Spring AI",{"type":648,"value":186629,"toc":186776},[186630,186633,186636,186639,186641,186644,186650,186653,186656,186660,186663,186671,186675,186678,186681,186683,186691,186699,186702,186708,186710,186713,186716,186719,186756,186758,186763],[651,186631,186632],{},"Happy Monday and welcome to another edition of the newsletter. It's being a while since my last update and I apologize for that, but I have been pretty busy with life and work.",[651,186634,186635],{},"I don’t want to let the cat of the bag just yet, but I had a really great meeting last week about an upcoming project that I am beyond excited about. This is something that I have been thinking about for as long as I can remember, and it is one of those projects that have been at the top of my career bucket list. As soon as it becomes official I will tell you more about it.",[651,186637,186638],{},"In this edition of the newsletter I want to talk about the podcast turning 1, Spring I/O and Spring AI.",[4542,186640,180219],{"id":180218},[651,186642,186643],{},"DaShaun and I have been working on the Spring Office Hours live stream for about 2 years. I was reminded a couple of weeks ago that we hit the 1-year milestone for launching the podcast and what a year it's been. I bring this up because I want to share some valuable advice with you.",[651,186645,186646],{},[660,186647],{"alt":186648,"src":186649},"Spring Office Hours Podcast Turns 1","/images/newsletter/2024/05/13/podcast-turns-one.png",[651,186651,186652],{},"We kept going back and forth on when would be ready to launch the podcast portion of Spring Office Hours. We needed a great intro, hook, theme music and a more professional sounding show. We needed to make sure we had a list of great guests lined up, and we needed to make sure we had some really great topics planned.",[651,186654,186655],{},"No we didn’t. We just needed to start it. I don’t know about you but the idea of perfection used to stop me from starting things. Now I see the perfection in imperfection and I’m here to tell you it’s so much better on this side. The next time you are struggling with these decisions just get started right now and remember that you can always improve what you’re doing over time.",[5909,186657,186659],{"id":186658},"spring-office-hours-road-show","Spring Office Hours Road Show",[651,186661,186662],{},"I’m also excited to announce that we are taking Spring Office Hours on the road this year. We are still working on what these shows are going to look like but we are excited to do some live recordings of the podcast this year. If you’re going to be at any of the following conferences be on the lookout for our live show",[5316,186664,186665,186667,186669],{},[5332,186666,36441],{},[5332,186668,173081],{},[5332,186670,82113],{},[5909,186672,186674],{"id":186673},"spring-security-with-dan","Spring Security with Dan",[651,186676,186677],{},"Last week I had the opportunity to sit down with my colleague Daniel Garnier-Moiroux. We had a chance to preview his upcoming talk at Spring I/O, what’s new in Spring Security 6.3 as well as answering some questions live. I love chatting with Daniel because he is just an amazing person and has so much experience that I always leave our conversations learning something.",[5988,186679],{"id":186680},"GdgzXLAbnn8",[4542,186682,36441],{"id":36440},[651,186684,186685,186686,186690],{},"I am really excited to be heading to my very first ",[812,186687,36441],{"href":186688,"rel":186689},"https://2024.springio.net/",[816]," in Barcelona Spain at the end of the month. I was just in Barcelona for the first time in November and wow what a beautiful city it was, but it was also a little colder out. I’m looking forward to experiencing the city with some better running weather.",[651,186692,186693,186694,186698],{},"I have heard so many great things about Spring I/O and I can’t wait to take in the talks, the people and everything that the conference has to offer. I will be giving a ",[812,186695,186697],{"href":186505,"rel":186696},[816],"talk"," on navigating the frontend landscape for Spring Developers. The idea behind this talk is that you’re a Spring Developer and you need to build out a frontend for your application, what technology do you use to do it? There are so many questions that go into this decision and in this session I will try and answer some of them.",[651,186700,186701],{},"I already threw out this question on Twitter and got a lot of really great answers. If you have questions please add them to this tweet or email me and I will try to answer them. Over the couple of weeks and even after my presentation I am going to focus a lot of my content on this subject.",[651,186703,186704],{},[812,186705,186706],{"href":186706,"rel":186707},"https://twitter.com/therealdanvega/status/1788654559843209302",[816],[4542,186709,123549],{"id":140820},[651,186711,186712],{},"It’s been awhile since you have heard from and during that time I have been pretty busy working on Spring AI tutorials. We are getting close to a Spring AI milestone and with that comes an understanding that some things will change between now and that highly anticipated 1.0 release.",[651,186714,186715],{},"In the meantime I have focused on putting together tutorials to cover all the basics of building intelligent applications in Java with Spring AI. If you have ever wanted to build an application that talks to one of the Large Language Models (LLMs) like Open AI’s GPT-4 there are REST APIs available. You might wonder why would I need a library when I can simply call the REST API in Java using JDK Http Client or in Spring using something like the Rest Client.",[651,186717,186718],{},"The answer is you can but as you start to build out these applications you will quickly find out that you need to solve for certain problems. When you get a response from the LLM how do you turn that into something you can work with? How can you augment the LLM with your organizations private data? These are just a couple of the questions I answer in this series of videos.",[5316,186720,186721,186728,186735,186742,186749],{},[5332,186722,186723],{},[812,186724,186727],{"href":186725,"rel":186726},"https://youtu.be/CuIr3FiG_fc",[816],"Spring AI’s Output Parser",[5332,186729,186730],{},[812,186731,186734],{"href":186732,"rel":186733},"https://youtu.be/TL3cko6YkgU",[816],"Open AI - Stuffing the Prompt",[5332,186736,186737],{},[812,186738,186741],{"href":186739,"rel":186740},"https://youtu.be/4-rG2qsTrAs",[816],"Java and Spring AI - Retrieval Augmented Generation (RAG)",[5332,186743,186744],{},[812,186745,186748],{"href":186746,"rel":186747},"https://youtu.be/ZoPVGrB8iHU",[816],"Building a Spring Boot Documentation Assistant with AI",[5332,186750,186751],{},[812,186752,186755],{"href":186753,"rel":186754},"https://youtu.be/n7IvE1VSbvI",[816],"Spring AI Function Calling",[4542,186757,157704],{"id":157703},[651,186759,178368,186760,166271],{},[812,186761,41499],{"href":44086,"rel":186762},[816],[651,186764,41105,186765,69920,186767,186769,186771,186773],{},[41107,186766],{},[41107,186768],{},[812,186770,161560],{"href":161111},[41107,186772],{},[812,186774,53869],{"href":53869,"rel":186775},[816],{"title":674,"searchDepth":790,"depth":790,"links":186777},[186778,186782,186783,186784],{"id":180218,"depth":790,"text":180219,"children":186779},[186780,186781],{"id":186658,"depth":892,"text":186659},{"id":186673,"depth":892,"text":186674},{"id":36440,"depth":790,"text":36441},{"id":140820,"depth":790,"text":123549},{"id":157703,"depth":790,"text":157704},{"slug":186786,"date":186787},"spring-office-hours-turns-1","2024-05-13T07:00:00.000Z","/newsletter/2024/05/13/spring-office-hours-turns-1",{"title":186627,"description":186632},"newsletter/2024/05/13/spring-office-hours-turns-1","LVQcR9wjweAefGSqjtz1Qxzx_iuTAu6uDtPfJAKSCiQ",{"id":186793,"title":186794,"body":186795,"description":186799,"extension":793,"meta":186925,"navigation":797,"path":186927,"seo":186928,"stem":186929,"__hash__":186930},"content/newsletter/2024/05/20/gpt-4o.md","GPT-4o and Spring I/O",{"type":648,"value":186796,"toc":186916},[186797,186800,186803,186806,186809,186812,186815,186818,186821,186847,186853,186856,186860,186863,186866,186868,186875,186878,186886,186889,186891,186896,186898,186903],[651,186798,186799],{},"Happy Monday and welcome to another edition of the newsletter. As I write this on Sunday afternoon the weather here in Northeast Ohio is warming up and its my favorite time of the year. We took the girls to a birthday party today, had lunch by the water and now I’m getting ready for a quick trip to Dallas. I will be in Dallas to give a presentation on Spring AI and immediately following that talk I am giving a virtual presentation on Spring Boot 3.",[651,186801,186802],{},"I love this time of year at work as well because it is conference season. Next week I will be heading to the beautiful city of Barcelona Spain for Spring I/O. This is one of those conferences that I haven’t been to yet but was definitely on the bucket list. I have heard nothing but amazing things about it and I am beyond excited about.",[651,186804,186805],{},"In this edition of the newsletter I want to talk about AI and Spring I/O.",[4542,186807,186808],{"id":123612},"Hello, GPT-4o 👋🏻",[651,186810,186811],{},"Last week Open AI announced its newest flagship model GPT-4o that can reason across audio, vision, and text in real time. If you interested in learning more about you can check out their Spring Update in the video below.",[5988,186813],{"id":186814},"DQacCB9tDaw",[651,186816,186817],{},"I really enjoyed it because it got straight to the point, had some great demos and it was fairly short for an announcement video. I also found it interested that OpenAI is heavily funded by Microsoft who I believe owns something like 49% and all of their demos were on iPhones and Macbook’s 🤷♂️",[651,186819,186820],{},"Some of the big improvements in GPT-4o include:",[5316,186822,186823,186829,186835,186841,186844],{},[5332,186824,186825,186828],{},[2939,186826,186827],{},"50% lower pricing."," GPT-4o is 50% cheaper than GPT-4 Turbo, across both input tokens ($5 per 1 million tokens) and output tokens ($15 per 1 million tokens).",[5332,186830,186831,186834],{},[2939,186832,186833],{},"2x faster latency."," GPT-4o is 2x faster than GPT-4 Turbo.",[5332,186836,186837,186840],{},[2939,186838,186839],{},"5x higher rate limits."," Over the coming weeks, GPT-4o will ramp to 5x those of GPT-4 Turbo—up to 10 million tokens per minute for developers with high usage.",[5332,186842,186843],{},"128k Context Window",[5332,186845,186846],{},"Knowledge cut-off date of October 2023",[651,186848,186849,186850,186852],{},"After the news broke I thought I would give it a spin in Spring AI. Sure enough all I had to do was change the version of the model I was using to ",[676,186851,122475],{}," and I was now on the latest and greatest. I haven’t run any benchmarks yet but in the few demos I went through it seems much faster. The responses I get back seem very snappy. I created a video on using this in Spring AI in the snapshot version and taking advantage of both the chat and vision modals.",[5988,186854],{"id":186855},"y90CkHvDGls",[5909,186857,186859],{"id":186858},"gpt-4o-and-java","GPT-4o and Java",[651,186861,186862],{},"I also thought I would take some time and introduce Java developers to GPT-4o. Spring makes it really easy to talk to the different LLMs out there but for someone just getting started I wanted to make it accessible and easy to get going. That is why I put together a simple demo that doesn't use any external dependencies. As long as your own JDK 11+ and have access to the Http Client this will work.",[5988,186864],{"id":186865},"EDJLHWcFvpQ",[4542,186867,36441],{"id":36440},[651,186869,186870,186871],{},"As I mentioned in the open I am extremely excited about Spring I/O next week. Last week we had a chance to sit down with Sergi Almar the creator and organizer of the conference. I had a chance to talk to him about how the conference started and all of the planning that goes into each of these events. You can check out the livestream recording below or listen to the podcast where ever you get your podcasts. If you want to get a list of archives for the show check out ",[812,186872,186873],{"href":186873,"rel":186874},"https://www.springofficehours.io/",[816],[5988,186876],{"id":186877},"LGx5gX7k-ao",[651,186879,186880,186881,186885],{},"The schedule is now out for ",[812,186882,36441],{"href":186883,"rel":186884},"https://2024.springio.net/sessions/",[816]," so if you’re heading there check it out and start making plans for your favorite talks. Sergi also mentioned that the app should be updated this week so you can start planning your conference in the app. There are so many amazing sessions in this lineup and I have no idea how I am going to choose which talks to attend. Thankfully all of the talks are being recorded and will be available sometime after the conference has ended. I will be giving a presentation on navigating the frontend landscape and I hope to see some of you live in the audience.",[651,186887,186888],{},"I think the biggest thing I am looking forward to is meeting so many amazing people that I have talked to online for years and never had the chance to meet. If you are going to be there please say hello and lets chat about how you’re using Java and Spring!",[5909,186890,165017],{"id":165016},[1004,186892,186893],{},[651,186894,186895],{},"“Happiness is a choice, a repetitive one.” - Akilnathan Logeswaran",[4542,186897,157704],{"id":157703},[651,186899,178368,186900,166271],{},[812,186901,41499],{"href":44086,"rel":186902},[816],[651,186904,41105,186905,69920,186907,186909,186911,186913],{},[41107,186906],{},[41107,186908],{},[812,186910,161560],{"href":161111},[41107,186912],{},[812,186914,53869],{"href":53869,"rel":186915},[816],{"title":674,"searchDepth":790,"depth":790,"links":186917},[186918,186921,186924],{"id":123612,"depth":790,"text":186808,"children":186919},[186920],{"id":186858,"depth":892,"text":186859},{"id":36440,"depth":790,"text":36441,"children":186922},[186923],{"id":165016,"depth":892,"text":165017},{"id":157703,"depth":790,"text":157704},{"slug":122475,"date":186926},"2024-05-20T07:00:00.000Z","/newsletter/2024/05/20/gpt-4o",{"title":186794,"description":186799},"newsletter/2024/05/20/gpt-4o","FBmEBGnd0IU3o8qTFXbqyE9MfcxbT3BP7guQ7Qp_saM",{"id":186932,"title":186933,"body":186934,"description":186938,"extension":793,"meta":186981,"navigation":797,"path":186984,"seo":186985,"stem":186986,"__hash__":186987},"content/newsletter/2024/06/03/spring-io-2024-newsletter.md","Spring I/0 2024",{"type":648,"value":186935,"toc":186978},[186936,186939,186942,186945,186952,186958,186960,186965],[651,186937,186938],{},"Happy Monday and welcome to another edition of the newsletter. I just returned from Spring I/O 2024 and I feel rejuvenated in the only way a conference full of passionate Java and Spring developers can do. I work remote, and it isn’t often I get to hang out with many of my coworkers.",[651,186940,186941],{},"It was amazing to hear so many people come up to me during the conference and tell me how much my content is helping them on their journey. I heard from junior developers learning Spring for the first time, Senior developers brushing up on their skills and even a couple who were coming from another language & framework.",[651,186943,186944],{},"It was a great reminder that what I am doing is making a real impact in the community and that I need to stay consistent with it and keep improving. If you want to read my recap of Spring I/O 2024 you can check out my blog post below.",[651,186946,186947],{},[812,186948,186951],{"href":186949,"rel":186950},"https://www.danvega.dev/blog/spring-io-2024",[816],"Spring I/O Recap",[651,186953,186954],{},[660,186955],{"alt":186956,"src":186957},"Dan at Spring I/O 2024","/images/newsletter/2024/06/03/spring_io_cover.jpeg",[4542,186959,157704],{"id":157703},[651,186961,178368,186962,166271],{},[812,186963,41499],{"href":44086,"rel":186964},[816],[651,186966,41105,186967,69920,186969,186971,186973,186975],{},[41107,186968],{},[41107,186970],{},[812,186972,161560],{"href":161111},[41107,186974],{},[812,186976,53869],{"href":53869,"rel":186977},[816],{"title":674,"searchDepth":790,"depth":790,"links":186979},[186980],{"id":157703,"depth":790,"text":157704},{"slug":186982,"date":186983},"spring-io-2024-newsletter","2024-06-03T07:00:00.000Z","/newsletter/2024/06/03/spring-io-2024-newsletter",{"title":186933,"description":186938},"newsletter/2024/06/03/spring-io-2024-newsletter","ZHoap956mT7yn_AJM7FO-TJopHzRQjzI2R5UsouLt7o",{"id":186989,"title":186990,"body":186991,"description":186995,"extension":793,"meta":187118,"navigation":797,"path":187121,"seo":187122,"stem":187123,"__hash__":187124},"content/newsletter/2024/06/17/writing-my-first-book.md","I’m writing my first book and Spring AI 1.0.0 M1 has been released",{"type":648,"value":186992,"toc":187112},[186993,186996,186999,187002,187005,187008,187012,187019,187025,187033,187039,187048,187051,187054,187058,187067,187073,187084,187088,187091,187094,187096,187101],[651,186994,186995],{},"Happy Monday and welcome to another edition of the newsletter. I know it's been a couple of weeks since you heard from me, but I promise I have some good reasons for that. I’m excited to announce that I have begun writing my first book!",[651,186997,186998],{},"This has been a significant career goal for me. I've always enjoyed writing, whether it's for this newsletter, my blog with over 1,000 posts, or documentation. It's something I've consistently enjoyed over the years. While this goal is not yet accomplished, I'm pleased to announce that I've started writing my first chapter.",[651,187000,187001],{},"I have had some offers to work on books over the years but the timing was never right. I have had little kids at home for the last 6 years and during those days of not sleeping I knew I couldn’t take on any extra work.",[651,187003,187004],{},"I also knew that I was going to hold to work on a book for a specific publisher. O’Reilly has long been the gold standard for tech books for me and you will find my bookshelf (Physical and Digital) lined with them. I knew that if I was going to write a book I wanted it to be with them. Lucky for me, I have been doing work with them on various projects over the years and the stars aligned with them on this one.",[651,187006,187007],{},"I have more to tell you about how this opportunity came up and who I’m co-authoring this book with (I’m really excited about that part) but that information will be coming soon. In the meantime I need to finish this newsletter and get back to writing the book. In this newsletter I want to talk about Spring AI 1.0.0 M1 and some things I have been working on around that release.",[4542,187009,187011],{"id":187010},"spring-ai-100-m1","Spring AI 1.0.0 M1",[651,187013,187014,187015,187018],{},"The first milestone release of Spring AI happened when I was in Barcelona for Spring I/O. I had a chance to see a lot of the new features being demonstrated at the conference and I was really excited to get back home and try them all out. If you haven’t had a chance to check out the blog post announcing 1.0.0 M1 you can check it out ",[812,187016,18263],{"href":121612,"rel":187017},[816],". If you look at the bottom of the announcement you will even see that I got a mention as a contributor to the project and that was AWESOME to see!",[651,187020,187021],{},[660,187022],{"alt":187023,"src":187024},"Spring AI Contributor","/images/newsletter/2024/06/17/contributor.png",[651,187026,187027,187028,187032],{},"The first thing I did was go through ",[812,187029,187031],{"href":123553,"rel":187030},[816],"my repository"," of examples that I use when I present on Spring AI and try to update them all based on the new features. I already thought that Spring AI was easy to use but the new fluent API for the chat client has made everything so much more concise and easy to read. Since the newsletter isn’t a great place to drop a bunch of code here is a screenshot that I think sums up the new API pretty well.",[651,187034,187035],{},[660,187036],{"alt":187037,"src":187038},"Spring AI Fluent API","/images/newsletter/2024/06/17/spring-ai-fluent-api.png",[651,187040,187041,187042,187047],{},"After I went through and got all of my demos updated I was ready to share the new features with some friends. A couple of weeks ago I presented on Building Intelligent Applications with Spring AI to my friends at the ",[812,187043,187046],{"href":187044,"rel":187045},"https://www.meetup.com/sfjava/events/299876110/",[816],"San Francisco Java User Group",". This meetup was recorded and when the recording becomes available I will share it with all of you.",[651,187049,187050],{},"Finally, we discussed the new release on last week’s episode of Spring Office Hours.",[5988,187052],{"id":187053},"a6mgGDyDq5o",[4542,187055,187057],{"id":187056},"️-speaking","🗣️ Speaking",[651,187059,187060,187061,187066],{},"Last week I gave a presentation virtually to the ",[812,187062,187065],{"href":187063,"rel":187064},"https://www.javaday.org.ua/program-2024-online",[816],"JavaDay Lviv Conference",". I really want to thank them for allowing me to share some of my favorite features in Spring Boot 3 and Beyond 🚀I’m not sure if these sessions were recorded, but I will look into that and if they were I will share them out.",[651,187068,187069],{},[812,187070,187071],{"href":187071,"rel":187072},"https://x.com/Javadaylviv/status/1801599652556353774",[816],[651,187074,187075,187076,187079,187080,187083],{},"This week I will be in Toronto giving a presentation on Spring AI and a Workshop on Spring AI which I am really looking forward to. Next week I will be at ",[812,187077,173081],{"href":97034,"rel":187078},[816]," to both present on Spring Boot 3 and get some good BBQ with my friend DaShaun! As always if you want to find out where I will be next please check out my ",[812,187081,120451],{"href":120449,"rel":187082},[816],". If you would like to have me speak at your event please feel free to reach out to me.",[4542,187085,187087],{"id":187086},"my-videos","🎥 My Videos",[651,187089,187090],{},"I only have one video to share with you this week but I think it’s a good one. This is an introduction to using HTMX in Spring along with some fun demos. If you want to learn about how to add dynamic behavior to your web applications without having to write JavaScript this video is for you!",[5988,187092],{"id":187093},"cjL0n1NApRA",[4542,187095,157704],{"id":157703},[651,187097,178368,187098,166271],{},[812,187099,41499],{"href":44086,"rel":187100},[816],[651,187102,41105,187103,69920,187105,187107,102662,187109],{},[41107,187104],{},[41107,187106],{},[812,187108,161560],{"href":161111},[812,187110,53869],{"href":53869,"rel":187111},[816],{"title":674,"searchDepth":790,"depth":790,"links":187113},[187114,187115,187116,187117],{"id":187010,"depth":790,"text":187011},{"id":187056,"depth":790,"text":187057},{"id":187086,"depth":790,"text":187087},{"id":157703,"depth":790,"text":157704},{"slug":187119,"date":187120},"writing-my-first-book","2024-06-17T07:00:00.000Z","/newsletter/2024/06/17/writing-my-first-book",{"title":186990,"description":186995},"newsletter/2024/06/17/writing-my-first-book","iKLOplOwsmipemvZtcoKieniNlgOgjUnZbjWQ0rbkIQ",{"id":187126,"title":187127,"body":187128,"description":187132,"extension":793,"meta":187246,"navigation":797,"path":187249,"seo":187250,"stem":187251,"__hash__":187252},"content/newsletter/2024/07/15/hello-fundamentals-software-engineering.md","Fundamentals of Software Engineering, Spring I/O and Video Content",{"type":648,"value":187129,"toc":187237},[187130,187133,187139,187142,187145,187148,187150,187153,187156,187159,187162,187165,187169,187172,187181,187184,187187,187191,187194,187197,187201,187204,187207,187211,187214,187217,187219,187224],[651,187131,187132],{},"Happy Monday and welcome to another edition of the newsletter. Last time we talked, I mentioned that I was writing a new book but didn’t give you many details. I’m happy to announce that the contracts have been signed and I have begun writing, so I can share a bit more about it now. The name of the book is \"Fundamentals of Software Engineering,\" and I am co-authoring it with my good friend Nate Schutta.",[651,187134,187135],{},[660,187136],{"alt":187137,"src":187138},"fose_book_cover.png","/images/newsletter/2024/07/15/fose_cover.png",[651,187140,187141],{},"The purpose of the book is to help those who are new to the industry and or 1-2 years on the job. This will be a bit of a guide or a reminder on some of the most important things you should remember as a new developer. Nate and I have over 40 years of experience on a wide range of roles between the 2 of us.",[651,187143,187144],{},"I'm not only excited about this because this is my first book, and I’m doing it with my good friend but because I honestly believe in the book. I think this is one of those books that new developers will continually reach for and come back to time and time again.",[651,187146,187147],{},"This book is on the O’Reilly Learning Platform which means that when we publish new chapters you can get early access to the content. This doesn’t mean that this is the final version, but you can get it before everyone else. I’m excited for all of you to get your hands on it and of course I would love some feedback.",[4542,187149,36441],{"id":36440},[651,187151,187152],{},"It’s been almost 2 months since Spring I/O in beautiful Barcelona, Spain, and there have been a few updates I want to share with you. First, there is a new highlight video that showcases some of the amazing talks and events from the conference. I got really excited about the conference all over again after watching this video. I even made it into the video a couple of times, so I thought I would share it with you.",[5988,187154],{"id":187155},"CsiCKAInWhg",[651,187157,187158],{},"My presentation titled “A Spring Developers Guide to Navigating the Frontend Landscape” has been published to the Spring I/O YouTube Channel. This was my first time giving this exact talk and I thought it went pretty well. If you need to build a frontend for your Spring application and aren’t sure where to start I think this is a pretty good overview.",[5988,187160],{"id":187161},"CdtyvO9jg8E",[651,187163,187164],{},"Also, a huge shout-out to Sergi and the entire team at Spring I/O for posting all the presentations. The recordings are high quality and the speakers were top-notch. I am still going through all of these presentations myself.",[4542,187166,187168],{"id":187167},"video-content","Video Content",[651,187170,187171],{},"I am always working on ways to improve my video content. I noticed my videos were taking a lot longer to record, edit, and publish, so I looked for ways to cut down on that time to create more content.",[651,187173,187174,187175,187180],{},"I came across new screen recording software called ",[812,187176,187179],{"href":187177,"rel":187178},"https://www.screen.studio/",[816],"Screen Studio",", and I loved its unique look. The camera was in the lower right-hand corner with a unique shape and drop shadow, and it zoomed in to show certain parts of the video.",[651,187182,187183],{},"I enjoyed this feature, so I decided to purchase the software and make a couple of videos using it. The first video had more zooming in and out than the second one. You gave feedback saying you didn’t like the automatic zooming, so in the second video, I tried to use less of it.",[651,187185,187186],{},"In the end, I had a single video that I could edit and publish more quickly. I don’t know if I will keep using it forever, but I will for now. I also have some ideas for redesigning my home studio to improve the look, and I'll let you know when that happens. For now, here are some of the videos I worked on since we last spoke:",[5909,187188,187190],{"id":187189},"spring-boot-rest-client-testing-issue-and-fix","Spring Boot Rest Client Testing (Issue and Fix)",[651,187192,187193],{},"In this tutorial you will learn how to test your Rest Client calls using the Rest Client Test annotation (@RestClientTest) in Spring Boot. If you need to change out the underlying HTTP Client library to the JDK Http Client you might have run into an issue, and we will talk about it and a work-around.",[5988,187195],{"id":187196},"-ChpDCIjyh0",[5909,187198,187200],{"id":187199},"building-a-chatgpt-clone-in-java-with-htmx-spring-boot-and-spring-ai","Building a ChatGPT Clone in Java with HTMX, Spring Boot, and Spring AI",[651,187202,187203],{},"In this video you are going to learn how to build a ChatGPT clone with HTMX, Spring Boot and Spring AI. HTMX allows you to add dynamic behavior to your frontend without having to write JavaScript. Spring AI allows you to easily build intelligent applications with generative AI. In this application you will use OpenAI's GPT-4o, but you could easily replace that with your Large Language Model (LLM) of choice.",[5988,187205],{"id":187206},"jJ63tedBAzI",[5909,187208,187210],{"id":187209},"whats-new-in-spring-ai-100-m1","What’s new in Spring AI 1.0.0 M1",[651,187212,187213],{},"In this tutorial you will learn about the new features in the latest release of Spring AI, 1.0.0 M1. I am a huge fan of the Chat Client's new fluent API as it makes the code so much more concise and readable.",[5988,187215],{"id":187216},"De9a-TaJImI",[4542,187218,157704],{"id":157703},[651,187220,178368,187221,166271],{},[812,187222,41499],{"href":44086,"rel":187223},[816],[651,187225,41105,187226,69920,187228,187230,187232,187234],{},[41107,187227],{},[41107,187229],{},[812,187231,161560],{"href":161111},[41107,187233],{},[812,187235,53869],{"href":53869,"rel":187236},[816],{"title":674,"searchDepth":790,"depth":790,"links":187238},[187239,187240,187245],{"id":36440,"depth":790,"text":36441},{"id":187167,"depth":790,"text":187168,"children":187241},[187242,187243,187244],{"id":187189,"depth":892,"text":187190},{"id":187199,"depth":892,"text":187200},{"id":187209,"depth":892,"text":187210},{"id":157703,"depth":790,"text":157704},{"slug":187247,"date":187248},"hello-fundamentals-software-engineering","2024-07-15T07:00:00.000Z","/newsletter/2024/07/15/hello-fundamentals-software-engineering",{"title":187127,"description":187132},"newsletter/2024/07/15/hello-fundamentals-software-engineering","Jsp9K7HoanPgakXwuEgxI2c7ho9AiithSYzzpnAHpeA",{"id":187254,"title":187255,"body":187256,"description":187260,"extension":793,"meta":187466,"navigation":797,"path":187469,"seo":187470,"stem":187471,"__hash__":187472},"content/newsletter/2024/09/01/spring-one-2024.md","SpringOne at Explore 2024 Recap, new Blog Posts & Videos",{"type":648,"value":187257,"toc":187455},[187258,187261,187264,187267,187270,187273,187276,187279,187282,187289,187292,187298,187303,187308,187313,187316,187318,187321,187323,187332,187349,187352,187355,187359,187362,187370,187373,187376,187383,187386,187389,187398,187400,187403,187407,187410,187414,187428,187437,187439,187442],[651,187259,187260],{},"Happy Tuesday and welcome to another edition of the newsletter. This is coming to you a day late because we celebrated Labor Day here yesterday. I have a lot to catch you up on because it was a busy August for me starting with SpringOne at Explore 2024.",[651,187262,187263],{},"I was back in Vegas for SpringOne at Explore 2024 last week and wow was it a long but productive few days. I got into Vegas on Saturday, August 24th because I had to be up and early on Sunday morning for rehearsals. I was the host of the Spring Spotlight (Keynote) and I had to record some voice-overs as well as run through the entire show on site with the whole team.",[651,187265,187266],{},"The keynote started on Monday at 9:00 AM and I had to be up early to prepare for the big show. It was a lot of fun and the honor of a lifetime to be up on the main stage introducing so many talented people that I had to pinch myself a couple of times during the show. If you want to see a recording of the keynote you can check it out below.",[5988,187268],{"id":187269},"7S6M8H2hz6w",[651,187271,187272],{},"I spent the rest of my trip in the Spring Theater as an emcee for most of our amazing speakers on Monday and Tuesday. I also had a couple sessions of my own which you can check out below.",[5988,187274],{"id":187275},"tMPC-u891XA",[5988,187277],{"id":187278},"CMpatvWLan8",[651,187280,187281],{},"If you want to check out the keynote and all the sessions from the Spring Theater you can view our YouTube playlist below.",[651,187283,187284],{},[812,187285,187288],{"href":187286,"rel":187287},"https://www.youtube.com/watch?v=7S6M8H2hz6w&list=PLgGXSWYM2FpPDrv8zmf3oN6SX1prqmESN",[816],"SpringOne 2024 Playlist",[651,187290,187291],{},"And here are a few photos from the event.",[651,187293,187294],{},[660,187295],{"alt":187296,"src":187297},"SpringOne 2024","/images/newsletter/2024/09/03/spring_one_2024_01.jpeg",[651,187299,187300],{},[660,187301],{"alt":187296,"src":187302},"/images/newsletter/2024/09/03/spring_one_2024_02.jpeg",[651,187304,187305],{},[660,187306],{"alt":187296,"src":187307},"/images/newsletter/2024/09/03/spring_one_2024_03.jpeg",[651,187309,187310],{},[660,187311],{"alt":187296,"src":187312},"/images/newsletter/2024/09/03/spring_one_2024_04.jpeg",[651,187314,187315],{},"Thank you to everyone who attended my sessions and spent some time talking with me. I had so much fun interacting with our amazing community and I can't wait to do it again!",[4542,187317,126904],{"id":178711},[651,187319,187320],{},"If you follow me at all you know I have been deep into the world of AI. I am having so much fun learning about how to leverage AI in my day to workflow as a developer and content creator. I have also had a lot of fun this year learning how to incorporate it into my Java applications with Spring AI.",[5909,187322,123549],{"id":140820},[651,187324,187325,187326,187331],{},"Speaking of Spring AI did you hear that the team just released ",[812,187327,187330],{"href":187328,"rel":187329},"https://spring.io/blog/2024/08/23/spring-ai-1-0-0-m2-released",[816],"Spring AI 1.0.0 M2",". A major emphasis for this release has been on observability functionality, crucial for monitoring, debugging, and optimizing AI applications. Comprehensive observability features have been introduced for:",[5316,187333,187334,187337,187340,187343,187346],{},[5332,187335,187336],{},"ChatClient (including Advisors)",[5332,187338,187339],{},"Chat Models (OpenAI, Ollama, Mistral, Anthropic)",[5332,187341,187342],{},"Embedding Models",[5332,187344,187345],{},"Image Generation Models",[5332,187347,187348],{},"Vector Stores",[5909,187350,120965],{"id":187351},"blog-posts",[651,187353,187354],{},"I have written a few blog posts over the last month that I thought I would share with you.",[5259,187356,187358],{"id":187357},"harnessing-local-llms-a-practical-guide-to-ollama-and-open-web-ui","Harnessing Local LLMs: A Practical Guide to Ollama and Open Web UI",[651,187360,187361],{},"In this blog post, I dive into Ollama, a cool tool that lets you run large language models (LLMs) right on your own machine. Imagine having the power of models like Llama 3.1 at your fingertips, with all the benefits of cost savings and enhanced security. I break down the whole process, from installation to model selection, and even introduce you to Open Web UI – think of it as your personal, customizable ChatGPT.",[651,187363,187364,187365,187369],{},"Curious about running your own local LLMs and supercharging your AI projects? Check out the full guide here: ",[812,187366,84],{"href":187367,"rel":187368},"https://www.danvega.dev/blog/ollama-web-ui",[816],". Trust me, whether you're a developer, AI enthusiast, or just love staying ahead of the tech curve, this is one you won't want to miss. Happy coding! 🚀💻",[5259,187371,81],{"id":187372},"claude-35-sonnet-ai-assistance-with-speed-and-artifacts",[651,187374,187375],{},"Anthropic has just released Claude 3.5 Sonnet, and it's a game-changer. This new model is not only smarter and faster than its predecessors, but it also introduces a cool new feature called Artifacts. Imagine having your own AI assistant that can generate code, create documents, and even design websites, all while you chat and collaborate in real-time. It's like having a super-powered brainstorming partner right at your fingertips!",[651,187377,187378,187379,187382],{},"But that's not all – Claude 3.5 Sonnet also comes with a nifty feature called Projects, helping you keep all your AI-assisted work organized and easily accessible. Whether you're a coding newbie, a content creator, or just someone curious about the latest in AI, this tool is definitely worth checking out. Want to learn more about how Claude 3.5 Sonnet can supercharge your productivity and creativity? Head over to my full blog post here: ",[812,187380,81],{"href":129597,"rel":187381},[816],". Trust me, you won't want to miss this glimpse into the future of human-AI collaboration! 🚀🤖",[5259,187384,78],{"id":187385},"supercharge-your-spring-apps-with-ai-a-beginners-guide-to-claude-35-sonnet-and-spring-ai",[651,187387,187388],{},"Ready to take your Java applications to the next level with some cutting-edge AI? I've got just the thing for you! In my latest blog post, I'm diving into how to integrate Claude 3.5 Sonnet (that awesome new AI model from Anthropic) with Spring applications using Spring AI. It's like giving your Spring apps a superpower boost! 🚀",[651,187390,187391,187392,187397],{},"I walk you through setting up a Spring Boot project that uses Spring AI to chat with Claude 3.5 Sonnet, complete with code samples for creating a chat controller and handling generated code. The best part? It's all done in the familiar Spring environment you know and love. Whether you're looking to add code generation, natural language processing, or other AI capabilities to your projects, this guide has got you covered. Curious to see how it all comes together? Check out the full post here: ",[812,187393,187396],{"href":187394,"rel":187395},"https://www.danvega.dev/blog/claude-sonnet-spring-ai",[816],"Supercharge Your Spring Apps with AI",". And don't forget to grab the source code from my GitHub repo to start experimenting. Happy coding, and let's build some AI-powered Spring magic! 🌟💻",[4542,187399,120990],{"id":157591},[651,187401,187402],{},"As I mentioned earlier August was a busy month, but I was able to knock out 1 video. I have a long list of videos in the queue and I will begin working on those this week.",[4542,187404,187406],{"id":187405},"must-watch-java-tutorial-method-references-explained","📺 Must-Watch Java Tutorial: Method References Explained",[651,187408,187409],{},"I had an opportunity to put together a video on method references in Java. I break down this powerful Java 8 feature, showing how it can make our code cleaner and more expressive. I cover all four types of method references with clear, practical examples that even Java beginners can follow.",[5909,187411,187413],{"id":187412},"key-takeaways","🔑 Key Takeaways:",[5316,187415,187416,187419,187422,187425],{},[5332,187417,187418],{},"Method references offer a concise way to reference methods or constructors",[5332,187420,187421],{},"They work alongside lambda expressions for more functional-style programming",[5332,187423,187424],{},"Types include: static, instance (particular object), instance (arbitrary object), and constructor references",[5332,187426,187427],{},"Your IDE can help spot opportunities to use method references",[651,187429,187430,187431,187436],{},"If you're looking to level up your Java skills, ",[812,187432,187435],{"href":187433,"rel":187434},"https://www.youtube.com/watch?v=DELCbBuCHHE",[816],"check out the video here",". It's time well spent!",[4542,187438,157704],{"id":157703},[651,187440,187441],{},"I hope you enjoyed this newsletter installment, and I will talk to you in the next one. If you have any links you would like me to include please contact me and I might add them to a future newsletter. I hope you have a great week and as always friends...",[651,187443,41105,187444,69920,187446,187448,187450,187452],{},[41107,187445],{},[41107,187447],{},[812,187449,161560],{"href":161111},[41107,187451],{},[812,187453,53869],{"href":53869,"rel":187454},[816],{"title":674,"searchDepth":790,"depth":790,"links":187456},[187457,187461,187462,187465],{"id":178711,"depth":790,"text":126904,"children":187458},[187459,187460],{"id":140820,"depth":892,"text":123549},{"id":187351,"depth":892,"text":120965},{"id":157591,"depth":790,"text":120990},{"id":187405,"depth":790,"text":187406,"children":187463},[187464],{"id":187412,"depth":892,"text":187413},{"id":157703,"depth":790,"text":157704},{"slug":187467,"date":187468},"spring-one-2024","2024-09-03T07:00:00.000Z","/newsletter/2024/09/01/spring-one-2024",{"title":187255,"description":187260},"newsletter/2024/09/01/spring-one-2024","Oe_3Vuw0odLOE6gcnwxtj70K71Aelc9fvYMCDTc5Amg",{"id":187474,"title":187475,"body":187476,"description":187480,"extension":793,"meta":187626,"navigation":797,"path":187629,"seo":187630,"stem":187631,"__hash__":187632},"content/newsletter/2024/09/16/bytesized-ai-dev.md","ByteSized AI, Java 23 & Upcoming Talks",{"type":648,"value":187477,"toc":187616},[187478,187481,187490,187498,187502,187515,187524,187527,187532,187536,187544,187556,187559,187561,187564,187572,187574,187576,187579,187585,187587,187590,187596,187598,187603],[651,187479,187480],{},"Happy Monday and welcome to another edition of the newsletter. I have had a lot going on this summer and that is why the issues have been a little infrequent. I have been writing a lot more lately and that is something that makes me happy.",[651,187482,187483,187484,187489],{},"I mentioned this in a previous newsletter, but I'm writing a book with good my friend Nate Schutta titled ",[812,187485,187488],{"href":187486,"rel":187487},"https://learning.oreilly.com/library/view/fundamentals-of-software/9781098143220/",[816],"\"Fundamentals of Software Engineering\"",". This has been a huge learning process for me but such a great experience. I finished my first chapter and as soon as it is in the early access edition I will be sure to let you know.",[651,187491,187492,187493,187497],{},"Next week I will be in Dallas for ",[812,187494,187496],{"href":186462,"rel":187495},[816],"jConf",", and I am really looking forward to it. I will be giving an updated version of my talk \"Building Intelligent Applications in Java with Spring AI\". If you're going to be at jConf please say hi and let's talk about what you're doing with AI. In other news I had to pull out of dev2Next, and I'm super bummed out about this. I was really looking forward to this conference and have to withdrawal from conferences you were selected to speak at is never an easy decision.",[4542,187499,187501],{"id":187500},"bytesized-ai-newsletter","ByteSized AI Newsletter",[651,187503,187504,187505,187510,187511,187514],{},"As if I wasn't busy enough writing I decided to launch a new newsletter called ",[812,187506,187509],{"href":187507,"rel":187508},"https://www.bytesizedai.dev/",[816],"ByteSized AI",". I sent out an email about this last week but if you didn't catch it you can check it out ",[812,187512,18263],{"href":187507,"rel":187513},[816]," and subscribe not to miss another issue.",[651,187516,187517,187518,187523],{},"I started this newsletter because I really love learning about everything related to Artificial Intelligence and then writing about it. I also started it because I wanted a reason to check out the newsletter platform ",[812,187519,187522],{"href":187520,"rel":187521},"https://www.beehiiv.com?via=Dan-Vega",[816],"Beehiv",". I am absolutely loving everything about this platform. It has a truly enjoyable writing experience. I'm enjoying it so much that I plan on moving this newsletter over to it as soon as I can find the time to do it.",[651,187525,187526],{},"In my next issue I will be talking about Open AI's two new models o1-preview and 01-mini. The o1 series introduces integrated chain-of-thought reasoning, a feature that promises to revolutionize how AI models approach complex problems and tasks.",[651,187528,187529],{},[660,187530],{"alt":187509,"src":187531},"/images/newsletter/2024/09/16/bytesized_ai.png",[4542,187533,187535],{"id":187534},"java-23","Java 23",[651,187537,187538,187539,187543],{},"What a great time it is to be a Java Developer! We get a new version of the JDK every 6 months. JDK 23 will be released this week, and I'm really looking forward to it. I have a ",[812,187540,24879],{"href":187541,"rel":187542},"https://www.danvega.dev/blog/jdk-23-first-look",[816]," highlighting some of the new features, enhancements and preview features.",[651,187545,187546,187547,187551,187552,187555],{},"You can join the Java 23 launch stream ",[812,187548,18263],{"href":187549,"rel":187550},"https://dev.java/community/java-23-launch/",[816],". This is a 3.5-hour live stream covering the launch of Java 23 as well as community updates and what to expect from future releases. We will also have a special Spring Office Hours planned for Tuesday with special guest ",[812,187553,179375],{"href":187554},"@BillyKorando"," where we will talk all about it.",[5988,187557],{"id":187558},"eDg4OkeePLM",[4542,187560,15432],{"id":61224},[651,187562,187563],{},"It's been awhile since I last posted a video on YouTube, but I'm hoping I can turn that around this week. I have a large number of ideas in the backlog and 3-4 videos that are ready to go. As I stated previously I have been pretty busy with other priorities but creating videos is what makes me the happiest. I crossed some pretty amazing milestones lately and I just want to say thank you for continuing to support the channel ❤️",[5316,187565,187566,187569],{},[5332,187567,187568],{},"60,000 Subscribers",[5332,187570,187571],{},"5.3 Million Views",[4542,187573,177889],{"id":157573},[5909,187575,164959],{"id":69848},[651,187577,187578],{},"This article introduces Htmx, a lightweight library that allows Spring Boot developers to create interactive web experiences without complex frontend frameworks. It demonstrates how to integrate Htmx with Spring Boot and Thymeleaf, showcasing simple examples like button clicks and search functionality, while highlighting the benefits of using Htmx for server-side rendering and reduced frontend complexity",[651,187580,187581],{},[812,187582,187583],{"href":187583,"rel":187584},"https://blog.jetbrains.com/idea/2024/09/introduction-to-htmx-for-spring-boot-developers/",[816],[5909,187586,164971],{"id":157591},[651,187588,187589],{},"This session will provide a comprehensive overview of the new feature in IntelliJ IDEA: workspaces. Andrey will explain what workspaces are, how they function, and how they can be integrated into your current projects. We will demonstrate some practical examples to get you started: creating a workspace, adding projects, and running them within it.",[651,187591,187592],{},[812,187593,187594],{"href":187594,"rel":187595},"https://www.youtube.com/live/ewe8AgP7oUE",[816],[4542,187597,157704],{"id":157703},[651,187599,178368,187600,166271],{},[812,187601,41499],{"href":44086,"rel":187602},[816],[651,187604,41105,187605,69920,187607,187609,187611,187613],{},[41107,187606],{},[41107,187608],{},[812,187610,161560],{"href":161111},[41107,187612],{},[812,187614,53869],{"href":53869,"rel":187615},[816],{"title":674,"searchDepth":790,"depth":790,"links":187617},[187618,187619,187620,187621,187625],{"id":187500,"depth":790,"text":187501},{"id":187534,"depth":790,"text":187535},{"id":61224,"depth":790,"text":15432},{"id":157573,"depth":790,"text":177889,"children":187622},[187623,187624],{"id":69848,"depth":892,"text":164959},{"id":157591,"depth":892,"text":164971},{"id":157703,"depth":790,"text":157704},{"slug":187627,"date":187628},"bytesized-ai-dev","2024-09-16T07:00:00.000Z","/newsletter/2024/09/16/bytesized-ai-dev",{"title":187475,"description":187480},"newsletter/2024/09/16/bytesized-ai-dev","LotrylGdY2Gtkib_HlgRVyrJVzlAwigfuEoEOZ5ADQ8",{"id":187634,"title":187635,"body":187636,"description":187835,"extension":793,"meta":187836,"navigation":797,"path":187839,"seo":187840,"stem":187841,"__hash__":187842},"content/newsletter/2024/09/23/jdk-23-jconf.md","JDK 23, jConf, and the move to Beehiiv",{"type":648,"value":187637,"toc":187822},[187638,187651,187654,187657,187660,187665,187681,187685,187688,187691,187694,187697,187707,187709,187712,187714,187717,187723,187734,187737,187741,187748,187751,187754,187760,187763,187767,187770,187773,187777,187786,187793,187801,187805,187811,187813,187818],[651,187639,187640,187641,187646,187647,187650],{},"Happy Monday and welcome to a brand-new edition of the newsletter! This is an exciting day for me because I've officially moved this newsletter over to ",[812,187642,187645],{"href":187643,"rel":187644},"https://www.beehiiv.com/?via=Dan-Vega",[816],"Beehiiv",". In last week's issue, I told you about a new newsletter I started called ",[812,187648,187509],{"href":187507,"rel":187649},[816],", which is also powered by Beehiiv. I'm really enjoying everything about this platform and looking forward to continuing to write to you.",[651,187652,187653],{},"I originally started this newsletter as a way to keep myself consistently writing. These days, I don't have that problem with a blog, two newsletters, and my first book underway. Don't worry—this newsletter isn't going anywhere—but I'm curious what you'd like to hear about every week.",[651,187655,187656],{},"I kind of like just writing about what I'm up to. I have my hands in so many projects, and it's fun talking to all of you and letting you know what I'm doing. If you like this format, please let me know. If there are things you'd like to see in the newsletter, I'm open to suggestions.",[651,187658,187659],{},"One feature I might add is an \"In the News\" section where I highlight articles, videos, or tweets that I came across that week. I've done this in the past, but I think I have a better way to approach it now. I would love any feedback you have, with that lets get into it",[651,187661,187662],{},[2939,187663,187664],{},"In todays email:",[5316,187666,187667,187670,187673,187676,187678],{},[5332,187668,187669],{},"JDK 23 was released last week",[5332,187671,187672],{},"jConf in Dallas TX this week",[5332,187674,187675],{},"Tweets from the bird app",[5332,187677,177889],{},[5332,187679,187680],{},"Quote of the week",[4542,187682,187684],{"id":187683},"what-happened-last-week","WHAT HAPPENED LAST WEEK",[5909,187686,130656],{"id":187687},"jdk-23",[651,187689,187690],{},"It's a great time to be a Java developer. If you have been around this ecosystem for a while you know how stagnant Java was for a long time. JDK 8 was released 3 years after JDK 7 and that waterfall approach took a long time. Now we get a new release every 6 months filled with new & preview features and enhancements.",[651,187692,187693],{},"Last week JDK 23 was released and there was a log of buzz around it. First off the team at Oracle had an awesome 3-hour release party where they went into great detail about everything included in this new version. If you haven't had a chance to catch the replay I suggested checking it out 👇️",[5988,187695],{"id":187696},"QG9xKpgwOI4",[651,187698,175600,187699,187703,187704,664],{},[812,187700,24879],{"href":187701,"rel":187702},"http://www.danvega.dev/blog/jdk-23-first-look",[816]," to highlight some of these features if you want to check that out 🤷♂️ If you want to download JDK 23 you can get it from IntelliJ or by using ",[812,187705,181245],{"href":121585,"rel":187706},[816],[5909,187708,97345],{"id":97344},[651,187710,187711],{},"Last week we had a special edition of the podcast on Tuesday where we sat down with Java Developer Advocate Billy Korando and talked about some of the new features in JDK 23. I love talking to Billy about all things Java because he is just so knowledgable and has really great insight and opinions on everything.",[5988,187713],{"id":187558},[651,187715,187716],{},"I had someone ask me on the live stream when Spring would support JDK 23. At the time I wasn't sure, but I thought it would be by the end of the week and sure enough it showed up on the Spring Initializr on Thursday last week. If you want to give JDK 23 a spin in a Spring Project you can do that right now!",[651,187718,187719],{},[660,187720],{"alt":187721,"src":187722},"Spring Boot 🤝 JDK 23","https://media.beehiiv.com/cdn-cgi/image/fit=scale-down,format=auto,onerror=redirect,quality=80/uploads/asset/file/16a2887f-bc3b-48d6-b01d-12c77022d265/image.png?t=1726776092",[651,187724,187725,187726,187729,187730,664],{},"If you want to read through all the JEPS included in this release you can do so ",[812,187727,18263],{"href":130654,"rel":187728},[816],". This release was not just about the JEPs though as there were many notable issues resolved and deprecations and if you want to find out about those you can read through the ",[812,187731,95704],{"href":187732,"rel":187733},"https://www.oracle.com/java/technologies/javase/23all-relnotes.html",[816],[4542,187735,187736],{"id":186140},"WHAT'S HAPPENING THIS WEEK",[5909,187738,187740],{"id":187739},"jconf-in-dallas-tx","jConf in Dallas TX",[651,187742,187743,187744,187747],{},"This week I will be heading to Dallas TX for ",[812,187745,187496],{"href":186462,"rel":187746},[816],". It's going to be a really quick trip for me, but I'm excited for a bunch of reasons. I have the honor of presenting on Building Intelligent Applications with Spring AI. If you have been following me you know I'm very much into the AI space, and I'm a huge fan of how easy Spring AI makes it to build intelligent apps.",[651,187749,187750],{},"I'm also really excited for my favorite part of conferences and that is the hallway track. So many really great speakers are going to be there some of which I have never met before. I'm also really looking forward to catching up with some friends. If you're going to be there I will be there Tuesday afternoon and I leave Wednesday evening. I would love to catch up, meet or just say hello 👋🏻",[651,187752,187753],{},"Last week I had a chance to give the same presentation to the Houston Java User Group. I want to thank them for having me present. I thought it went really well, and we got some really great questions. One of which you can read about in the Tweets section below.",[651,187755,187756,187757,664],{},"If you're interested in having me speak at your next user group meeting or conference please feel free to contact me. If you want to find out where I will be speaking next you can check out my ",[812,187758,120451],{"href":120449,"rel":187759},[816],[4542,187761,187762],{"id":165429},"TWEETS",[187764,187765],"tweet-embed",{"id":187766},"1836594043414892755",[187764,187768],{"id":187769},"1836773036822811093",[187764,187771],{"id":187772},"1836597076655824972",[4542,187774,187776],{"id":187775},"️-around-the-web","🌎️ AROUND THE WEB",[651,187778,187779,187780,187785],{},"📝 Mala Gupta had an amazing article on the JetBrains blog titled \"",[812,187781,187784],{"href":187782,"rel":187783},"https://blog.jetbrains.com/idea/2024/09/java-23-and-intellij-idea/",[816],"Java 23 and IntelliJ IDEA","\" and its really good.",[651,187787,187788,187789],{},"🎙️ Mala Gupta and Vikas Malik, live from New Delhi, on a ",[812,187790,101210],{"href":187791,"rel":187792},"https://www.youtube.com/watch?v=WxRpseXnr_w",[816],[651,187794,187795,187796],{},"📹 \"Pkl: Safe and Miantainable Config for Java Apps and Infrastructure\" by ",[812,187797,187800],{"href":187798,"rel":187799},"https://vimeo.com/1006138422",[816],"James Ward at JavaZone",[4542,187802,187804],{"id":187803},"quote-of-the-week","💡 QUOTE OF THE WEEK",[651,187806,187807,187808],{},"The only way to do great work is to love what you do. If you haven't found it yet, keep looking. Don't settle. - ",[7300,187809,187810],{},"Steve Jobs",[4542,187812,157704],{"id":157703},[651,187814,178368,187815,166271],{},[812,187816,41499],{"href":44086,"rel":187817},[816],[651,187819,41105,187820,69920],{},[41107,187821],{},{"title":674,"searchDepth":790,"depth":790,"links":187823},[187824,187828,187831,187832,187833,187834],{"id":187683,"depth":790,"text":187684,"children":187825},[187826,187827],{"id":187687,"depth":892,"text":130656},{"id":97344,"depth":892,"text":97345},{"id":186140,"depth":790,"text":187736,"children":187829},[187830],{"id":187739,"depth":892,"text":187740},{"id":165429,"depth":790,"text":187762},{"id":187775,"depth":790,"text":187776},{"id":187803,"depth":790,"text":187804},{"id":157703,"depth":790,"text":157704},"Happy Monday and welcome to a brand-new edition of the newsletter! This is an exciting day for me because I've officially moved this newsletter over to Beehiiv. In last week's issue, I told you about a new newsletter I started called ByteSized AI, which is also powered by Beehiiv. I'm really enjoying everything about this platform and looking forward to continuing to write to you.",{"slug":187837,"date":187838},"jdk-23-jconf","2024-09-23T07:00:00.000Z","/newsletter/2024/09/23/jdk-23-jconf",{"title":187635,"description":187835},"newsletter/2024/09/23/jdk-23-jconf","WMBEneUjj04_fQSAGVc65WpCaJ5ueP_qXsxeO6ABMes",{"id":187844,"title":187845,"body":187846,"description":187850,"extension":793,"meta":187960,"navigation":797,"path":187963,"seo":187964,"stem":187965,"__hash__":187966},"content/newsletter/2024/09/30/jconf-recap.md","jConf Recap, Upcoming talks and Spring Office Hours",{"type":648,"value":187847,"toc":187953},[187848,187851,187853,187861,187864,187867,187870,187873,187879,187884,187887,187890,187895,187900,187903,187905,187908,187914,187916,187919,187921,187924,187927,187929,187932,187935,187944,187946,187948],[651,187849,187850],{},"Happy Monday and welcome to another edition of the newsletter. After traveling to Dallas last week for jConf I'm home for a week and looking forward to getting caught up on some work. It's been a long time (almost 2 months) since I last posted a video on my YouTube channel. I have a pretty good backlog of videos that are ready to go and that will be my focus this week.",[5909,187852,187684],{"id":187683},[651,187854,187855,187856,187860],{},"Last week on Spring Office Hours DaShaun and I were joined by special guest Anthony Dahanne. Anthony is the Montreal Java User Group organizer and Buildpacks expert. In this episode we discussed why \"Friends don't let friends use Docker files\". You can catch the replay below or visit ",[812,187857,187859],{"href":178465,"rel":187858},[816],"www.springofficehours.io"," to listen to the podcast version.",[5988,187862],{"id":187863},"am4x5DNaJgs",[5259,187865,186464],{"id":187866},"jconfdev",[651,187868,187869],{},"As I mentioned earlier I traveled to Dallas last week for jConf. The conference was held at the Courtyard Dallas in Allen TX which was about 40 minutes away from the airport. I thought the hotel's location was really nice. There were a bunch of great restaurants and shopping all within walking distance which is always a bonus.",[651,187871,187872],{},"This was a really quick trip for me. I got in Tuesday afternoon and had to leave Wednesday evening. I was happy to get in Tuesday early enough to attend the Speaker dinner. I was immediately happy to see so many familiar faces. I have made some pretty good friends on the speaker circuit, and it was great to catch up with some of them.",[651,187874,187875],{},[660,187876],{"alt":187877,"src":187878},"jConf Speaker Dinner","/images/newsletter/2024/09/30/jconf_01.jpeg",[651,187880,187881],{},[660,187882],{"alt":187877,"src":187883},"/images/newsletter/2024/09/30/jconf_02.jpeg",[651,187885,187886],{},"Wednesday morning the conference started and I dropped by a few sessions. After that I was up in the afternoon and ready to talk about Spring AI.",[651,187888,187889],{},"I have given a version of this talk a lot over the last year or so but I decided to rewrite it from the ground up based on what I have learned this year and because It needed a refresh. I thought this session went really well and I got a bunch of really great questions. My friend Chandra said he had an excuse for not being there and captured the overflow into the hallway. Thank you to everyone who joined my session for your attention, time and wonderful questions.",[651,187891,187892],{},[660,187893],{"alt":187877,"src":187894},"/images/newsletter/2024/09/30/jconf_03.jpeg",[651,187896,187897],{},[660,187898],{"alt":187877,"src":187899},"/images/newsletter/2024/09/30/jconf_04.avif",[187764,187901],{"id":187902},"1839663768294351310",[5259,187904,97312],{"id":177852},[651,187906,187907],{},"Speaking of conferences I just found out that I was selected to speak at CodeMash next year. I'm a huge fan of this conference for many reasons. First off it is practically in my backyard about an hour away in Sandusky OH. Next the kids love going to this conference because it's in a water park, and they have a kids track. Finally, I am a big fan of the diverse amount of content. Looking forward to seeing everyone in beautiful Sandusky OH in January where I'm sure it will be snowing and very cold 🥶",[651,187909,187910],{},[660,187911],{"alt":187912,"src":187913},"CodeMash 2025","/images/newsletter/2024/09/30/codemash_2025.avif",[5909,187915,187736],{"id":186140},[651,187917,187918],{},"This week on Spring Office Hours we are joined by Chris Bono from the Spring Team. I happened to see Chris last week at jConf and I'm very much looking forward to talking with him this week. Chris is part of the team that delivers Spring Cloud Data Flow, Spring for Apache Pulsar, Spring Cloud Stream and more. If you're available you can join us for the live stream today from 1-2 PM EDT or catch the replay or podcast wherever you get your podcasts. If you are a podcast subscriber please do us a favor and leave us a review.",[5909,187920,187762],{"id":165429},[187764,187922],{"id":187923},"1839696692674757040",[187764,187925],{"id":187926},"1839670201996001389",[5909,187928,187776],{"id":187775},[651,187930,187931],{},"There was a conference around the programming language Swift. One of the news items to come out of that conference was some Swift/Java interoperability. It looks like these sessions were recorded but just haven't been posted yet. I find this pretty interesting and I want to learn a little bit more about this.",[187764,187933],{"id":187934},"1839328831200809217",[651,187936,187937,187938,187943],{},"William Deblauwe announced that his book ",[812,187939,187942],{"href":187940,"rel":187941},"https://www.lulu.com/shop/wim-deblauwe/modern-frontends-with-htmx/paperback/product-rme4eq9.html?page=1&pageSize=4",[816],"\"Modern frontends with htmx\""," is now available in print. This is an awesome book that will introduce you to htmx and how to get started with it in Spring Boot.",[5909,187945,157704],{"id":157703},[651,187947,187441],{},[651,187949,187950,187951,69920],{},"Happy Coding,",[41107,187952],{},{"title":674,"searchDepth":790,"depth":790,"links":187954},[187955,187956,187957,187958,187959],{"id":187683,"depth":892,"text":187684},{"id":186140,"depth":892,"text":187736},{"id":165429,"depth":892,"text":187762},{"id":187775,"depth":892,"text":187776},{"id":157703,"depth":892,"text":157704},{"slug":187961,"date":187962},"jconf-recap","2024-09-30T07:00:00.000Z","/newsletter/2024/09/30/jconf-recap",{"title":187845,"description":187850},"newsletter/2024/09/30/jconf-recap","7ciFBtqQE8fC5M3au4zplp7ghuMwaCVgrKSyXW8bd-Y",{"id":187968,"title":187969,"body":187970,"description":188160,"extension":793,"meta":188161,"navigation":797,"path":188163,"seo":188164,"stem":188165,"__hash__":188166},"content/newsletter/2024/10/07/java-template-engine.md","Spring Framework 7.0 and Java Template Engine & ChatGPT Canvas",{"type":648,"value":187971,"toc":188151},[187972,187981,187985,187994,187997,188001,188010,188013,188029,188037,188040,188043,188058,188062,188068,188071,188074,188082,188084,188087,188090,188093,188096,188130,188132,188137,188140,188148],[651,187973,187974,187975,187980],{},"Happy Monday and welcome to another edition of the newsletter. Last week was a pretty interesting week as we had some news drop about the future of the Spring Framework. I also found out last week the Java Template Engine (JTE) was added to the Spring Initalizr. This led to a lot of tweets, playing around with some code, writing blog posts and I posted 3 videos last week on this. Finally, there was a lot of news in the AI space which I try and cover on ",[812,187976,187979],{"href":187977,"rel":187978},"http://www.bytesizedai.dev",[816],"www.bytesizedai.dev"," but none bigger for software developers than the new release of ChatGPT Canvas.",[4542,187982,187984],{"id":187983},"spring-framework-70","Spring Framework 7.0",[651,187986,187987,187988,187993],{},"A new article was released last week from Juergen Hoeller titled ",[812,187989,187992],{"href":187990,"rel":187991},"https://spring.io/blog/2024/10/01/from-spring-framework-6-2-to-7-0",[816],"\"From Spring Framework 6.2 to 7.0\""," . Spring Framework is gearing up for significant updates. Version 6.2, set for November 2024, will bring major revisions to the core container and web support, supporting JDK 17-23 and Jakarta EE 9-10.",[651,187995,187996],{},"Looking ahead, Spring Framework 7.0 is slated for November 2025, upgrading to Jakarta EE 11 and embracing JDK 25 LTS while maintaining JDK 17 compatibility. It will also support Kotlin 2 and adopt JSpecify annotations for null-safety. The framework continues its focus on GraalVM, Project Leyden, and Spring AOT, with planned revisions to JPA and JMS support. Milestones for version 7.0 are expected in early 2025, with related Spring Boot and Spring Cloud updates following throughout the year.",[4542,187998,188000],{"id":187999},"java-template-engine","Java Template Engine",[651,188002,188003,188004,188009],{},"I found out last week that a ",[812,188005,188008],{"href":188006,"rel":188007},"https://x.com/therealdanvega/status/1840793222005567621",[816],"new template"," was added to the Spring Initializr. The Java Template Engine also known as JTE was added and can now be used seamlessly in your Spring Boot applications.",[651,188011,188012],{},"A template engine in Spring Boot is a tool that allows you to generate dynamic HTML content by combining static HTML with data from your application. From the Spring Initializr you can now select from the following template engines:",[5316,188014,188015,188018,188021,188024,188026],{},[5332,188016,188017],{},"Thymeleaf (default)",[5332,188019,188020],{},"Groovy Templates",[5332,188022,188023],{},"Apache FreeMarker",[5332,188025,18642],{},[5332,188027,188028],{},"Java Template Engine (JTE)",[651,188030,188031,188032,188036],{},"Once I saw this was available I decided to write up a ",[812,188033,188035],{"href":132775,"rel":188034},[816],"quick blog post"," and record a video on how to get started with JTE in Spring Boot.",[5988,188038],{"id":188039},"KoWgHSWA1cc",[651,188041,188042],{},"This video got a lot of attention quickly and I can't thank you all enough for that. Out of that came some fascinating questions. The questions I received the most were around how to use layouts in JTE and if you could use it with HTMX. I ended up creating a blog post and video for each of those topics, and you can check them out below.",[5316,188044,188045,188052],{},[5332,188046,188047],{},[812,188048,188051],{"href":188049,"rel":188050},"https://www.danvega.dev/blog/jte-layouts",[816],"Getting Started with JTE layouts in Spring Boot",[5332,188053,188054],{},[812,188055,66],{"href":188056,"rel":188057},"https://www.danvega.dev/blog/spring-boot-jte-htmx",[816],[4542,188059,188061],{"id":188060},"chatgpts-canvas","ChatGPT's Canvas",[651,188063,188064],{},[660,188065],{"alt":188066,"src":188067},"ChatGPT Canvas","/images/newsletter/2024/10/07/canvas.jpeg",[651,188069,188070],{},"OpenAI has unveiled Canvas, a groundbreaking feature for ChatGPT that promises to revolutionize AI collaboration for writers and coders. This innovative tool moves beyond the traditional chat interface, opening in a separate window to facilitate side-by-side work between users and AI. Canvas offers a suite of powerful features, including inline editing, length adjustment, and reading level changes for writers, as well as code review, bug fixing, and language porting capabilities for programmers.",[651,188072,188073],{},"Powered by GPT-4o, a model specifically trained for creative partnership, Canvas represents a significant shift from simple chat-based interactions to a more intuitive and collaborative AI workspace. This new approach has the potential to transform how professionals tackle writing, coding, and creative tasks, paving the way for more seamless integration of AI assistance in various fields. As Canvas evolves beyond its beta phase, it may well redefine our relationship with AI technology in the workplace.",[651,188075,188076,188077,188081],{},"If you want to learn more about it, I posted an article over on ",[812,188078,187509],{"href":188079,"rel":188080},"https://www.bytesizedai.dev/p/canvas-painting-a-new-future-for-ai-collaboration",[816]," on this.",[4542,188083,187762],{"id":165429},[187764,188085],{"id":188086},"1841432849028731139",[187764,188088],{"id":188089},"1841507795901812975",[187764,188091],{"id":188092},"1841561740619702769",[4542,188094,188095],{"id":157573},"AROUND THE WEB",[5316,188097,188098,188106,188115,188123],{},[5332,188099,188100,188105],{},[812,188101,188104],{"href":188102,"rel":188103},"https://www.oracle.com/javaone/",[816],"JavaOne"," is returning in 2025 and the CFP is still open for a few more days. They are still looking for speakers like you so please get those submissions in by October 10th.",[5332,188107,188108,188109,188114],{},"My colleague Christian Tzolov wrote an article for the Spring Blog titled ",[812,188110,188113],{"href":188111,"rel":188112},"https://spring.io/blog/2024/10/02/supercharging-your-ai-applications-with-spring-ai-advisors",[816],"\"Supercharging your AI Applications with Spring AI Advisors\"",". At their core, Spring AI Advisors are components that intercept and potentially modify the flow of chat-completion requests and responses in your AI applications.",[5332,188116,188117,188118,188122],{},"If you want some of the top Java Blogs you should be following be sure to check out ",[812,188119,109096],{"href":188120,"rel":188121},"https://blog.jetbrains.com/idea/2024/10/top-java-blogs-to-follow-in-2024-part-1/",[816]," by Irina Mariasova.",[5332,188124,178347,188125,188129],{},[812,188126,85142],{"href":188127,"rel":188128},"https://www.youtube.com/watch?v=VvWtoaeHQUQ",[816]," by JetBrains Developer Advocate Siva on Developing Spring Boot Applications with Joy.",[4542,188131,187804],{"id":187803},[1004,188133,188134],{},[651,188135,188136],{},"\"The measure of intelligence is the ability to change.\" - Albert Einstein",[4542,188138,188139],{"id":157703},"UNTIL NEXT WEEK",[651,188141,188142,188143,188147],{},"I hope you enjoyed this newsletter installment, and I will talk to you in the next one. If you have any questions for me or topics you would like me to cover please feel free to reply to this email or reach out to me on ",[812,188144,51474],{"href":188145,"rel":188146},"https://x.com/therealdanvega",[816]," (I'm not calling it X).",[651,188149,188150],{},"Happy Coding,\nDan Vega",{"title":674,"searchDepth":790,"depth":790,"links":188152},[188153,188154,188155,188156,188157,188158,188159],{"id":187983,"depth":790,"text":187984},{"id":187999,"depth":790,"text":188000},{"id":188060,"depth":790,"text":188061},{"id":165429,"depth":790,"text":187762},{"id":157573,"depth":790,"text":188095},{"id":187803,"depth":790,"text":187804},{"id":157703,"depth":790,"text":188139},"Happy Monday and welcome to another edition of the newsletter. Last week was a pretty interesting week as we had some news drop about the future of the Spring Framework. I also found out last week the Java Template Engine (JTE) was added to the Spring Initalizr. This led to a lot of tweets, playing around with some code, writing blog posts and I posted 3 videos last week on this. Finally, there was a lot of news in the AI space which I try and cover on www.bytesizedai.dev but none bigger for software developers than the new release of ChatGPT Canvas.",{"slug":187999,"date":188162},"2024-10-07T07:00:00.000Z","/newsletter/2024/10/07/java-template-engine",{"title":187969,"description":188160},"newsletter/2024/10/07/java-template-engine","gSeQwCTAYBP8Tnjx-5uqclcdIWIE_0FMF29uSjNHDsg",{"id":188168,"title":188169,"body":188170,"description":188174,"extension":793,"meta":188310,"navigation":797,"path":188313,"seo":188314,"stem":188315,"__hash__":188316},"content/newsletter/2024/10/14/streaming-chatbot.md","Building a Streaming ChatBot in Java, AI Memory, JTE Forms and I published a new tool",{"type":648,"value":188171,"toc":188300},[188172,188175,188178,188182,188196,188200,188206,188209,188212,188215,188232,188235,188238,188242,188245,188252,188256,188259,188262,188265,188268,188271,188274,188277,188282,188284,188287,188290,188293,188295,188298],[651,188173,188174],{},"Happy Monday and Welcome to Another Edition of the Newsletter! Last week, I drove down to Cincinnati to spend time with a customer. I stayed near the Cincinnati Reds stadium, which sits on the Ohio/Kentucky border. The area is known as \"The Banks,\" and I must say, I loved it there. If I have the chance, I'm going to try to make it back next summer to catch a game! Speaking of baseball, my Cleveland Guardians are in the ALCS ⚾️🤩",[651,188176,188177],{},"I gave a talk to our customer on GraphQL, similar to the one I gave at SpringOne. I absolutely love discussing GraphQL because it solves real problems. One thing I always stress to developers is the importance of understanding the \"why\" before diving into a new technology. Avoid resume-driven development—learning something just to put it on your resume. GraphQL addresses several issues that REST APIs face, such as over-fetching, multiple resource calls, API versioning, and more. If you have questions about building GraphQL APIs in Java & Spring feel free to reach out.",[4542,188179,188181],{"id":188180},"in-this-edition-of-the-newsletter","In this edition of the newsletter",[5316,188183,188184,188187,188190,188193],{},[5332,188185,188186],{},"How to build a streaming chatbot in Java",[5332,188188,188189],{},"How to build an AI ChatBot with Memory",[5332,188191,188192],{},"Working with forms in Java Template Engine",[5332,188194,188195],{},"JSON to Java Record Converter",[5909,188197,188199],{"id":188198},"building-a-streaming-chatbot-in-java","Building a Streaming ChatBot in Java",[651,188201,188202],{},[660,188203],{"alt":188204,"src":188205},"Streaming Chatbot","/images/newsletter/2024/10/14/streaming_chatbot.png",[651,188207,188208],{},"It is really easy to get started building generative AI applications in Java with Spring AI. With minimal lines of code you can communicate with a Large Language Model and get a response back. By default, that is a synchronous call, and you will need to wait for the entire response to return to display it to the user.",[651,188210,188211],{},"If this is a larger response this might not make for the best user experience. In that case Spring AI enables you to stream the response back to the client with a method called stream. I decided to put together a quick little tutorial on how to do this",[651,188213,188214],{},"🎯 What you'll learn:",[5316,188216,188217,188220,188223,188226,188229],{},[5332,188218,188219],{},"Setting up a Spring Boot project with Spring AI",[5332,188221,188222],{},"Implementing both traditional and streaming response methods",[5332,188224,188225],{},"Integrating with Anthropic Claude 3.5 Sonnet (easily adaptable to other LLMs)",[5332,188227,188228],{},"Creating a sleek UI with HTMX and Tailwind CSS",[5332,188230,188231],{},"Handling streaming data on the front-end with JavaScript",[651,188233,188234],{},"Whether you're a seasoned Spring developer or just getting started with AI integration, this tutorial offers valuable insights to elevate your projects.",[5988,188236],{"id":188237},"q2p0mG4RICM",[5909,188239,188241],{"id":188240},"building-an-ai-chatbot-with-memory","Building an AI ChatBot with Memory",[651,188243,188244],{},"I had a few people reach out to me and ask me how we can remember previous conversations when talking to an LLM. You have to remember that the web is stateless and when we are communicating with Large Language Models there is no record of previous conversations. There are products that sit in front of these LLMs though like ChatGPT which is able to remember previous conversations.",[651,188246,188247,188248,664],{},"I put together a quick video on how to do this in Java with Spring AI. If you're interested in the code check out the ",[812,188249,89158],{"href":188250,"rel":188251},"https://github.com/danvega/chat-memory",[816],[5909,188253,188255],{"id":188254},"working-with-forms-in-java-template-engine-jte","Working with forms in Java Template Engine (JTE)",[651,188257,188258],{},"I have been receiving a lot of comment on JTE over the last couple of weeks. I am getting all kinds of questions on how to do this and that and how it compares to Thymeleaf.",[651,188260,188261],{},"In this tutorial learn how to create interactive web forms using Spring Boot and the Java Template Engine (JTE). I'll walk you through building a full-featured user registration form, covering:",[651,188263,188264],{},"✅ Setting up a Spring Boot project with JTE\n✅ Creating a user model and repository\n✅ Implementing form validation\n✅ Persisting data to a PostgreSQL database using Docker\n✅ Handling form submissions and displaying success/error messages",[651,188266,188267],{},"Perfect for developers looking to move beyond Thymeleaf and explore JTE's capabilities in Spring Boot applications. Whether you're new to Spring or an experienced developer, this tutorial offers valuable insights into modern web form development.",[5988,188269],{"id":188270},"ifnv4kGtZgo",[5909,188272,188195],{"id":188273},"json-to-java-record-converter",[651,188275,188276],{},"I love hacking on things especially tools that I find useful. I have been messing around with a tool that can convert a block of JSON into a Java Record. I decided to put it on my website so that you can check it out.",[651,188278,188279],{},[660,188280],{"alt":188195,"src":188281},"/images/newsletter/2024/10/14/json-java-record.png",[4542,188283,187762],{"id":165429},[187764,188285],{"id":188286},"1844761724840198348",[187764,188288],{"id":188289},"1844755271991619837",[187764,188291],{"id":188292},"1844742997599994141",[4542,188294,188139],{"id":157703},[651,188296,188297],{},"I hope you enjoyed this newsletter installment, and I will talk to you in the next one. If you have any questions for me or topics you would like me to cover please feel free to reply to this email or reach out to me on Twitter (I'm not calling it X).",[651,188299,188150],{},{"title":674,"searchDepth":790,"depth":790,"links":188301},[188302,188308,188309],{"id":188180,"depth":790,"text":188181,"children":188303},[188304,188305,188306,188307],{"id":188198,"depth":892,"text":188199},{"id":188240,"depth":892,"text":188241},{"id":188254,"depth":892,"text":188255},{"id":188273,"depth":892,"text":188195},{"id":165429,"depth":790,"text":187762},{"id":157703,"depth":790,"text":188139},{"slug":188311,"date":188312},"streaming-chatbot","2024-10-14T07:00:00.000Z","/newsletter/2024/10/14/streaming-chatbot",{"title":188169,"description":188174},"newsletter/2024/10/14/streaming-chatbot","OXfbflGTeWtvZYANJ1UWPAIdVJlFRXV3_4D5ReOt2DU",{"id":188318,"title":188319,"body":188320,"description":188324,"extension":793,"meta":188463,"navigation":797,"path":188466,"seo":188467,"stem":188468,"__hash__":188469},"content/newsletter/2024/10/21/office-upgrades.md","Updating my office, Spring Office Hours and AI for Java Developers",{"type":648,"value":188321,"toc":188453},[188322,188325,188328,188334,188337,188343,188346,188352,188355,188361,188363,188366,188369,188371,188386,188389,188393,188396,188399,188403,188406,188409,188412,188415,188418,188421,188424,188427,188430,188434,188443,188445,188451],[651,188323,188324],{},"Happy Monday and welcome to another edition of the newsletter. Even before I became a “content creator” I was fascinated by the office tours on YouTube. How were creators utilizing the space they had and what were all the cool gear they were using. I have had a lot of fun over the years trying to improve my office space and we coming up on another makeover.",[651,188326,188327],{},"I decided it was time to upgrade the office and this past weekend was step 1 in that process. If you look at a vide from last weekend you can see this cream color wall behind me. It is pretty boring and honestly just dulls the video for me.",[651,188329,188330],{},[660,188331],{"alt":188332,"src":188333},"Boring Wall","/images/newsletter/2024/10/21/boring_wall.png",[651,188335,188336],{},"We decided paint the whole office and making that wall behind me a feature wall with a different color. The color we decided to go with is a navy blue from Sherwin Williams called “In the Navy”. Here is that same wall after a fresh coat of a paint with the blank shelf. It is extremely time consuming unloading and loading books on to this thing 🤦♂️",[651,188338,188339],{},[660,188340],{"alt":188341,"src":188342},"Blue Wall","/images/newsletter/2024/10/21/blue_wall.png",[651,188344,188345],{},"And here is what the background will look like in a video. It’s crazy how a single color can totally transform the shot like this but I am really happy with how this turned out. What do you think?",[651,188347,188348],{},[660,188349],{"alt":188350,"src":188351},"New Look","/images/newsletter/2024/10/21/new_look.png",[651,188353,188354],{},"I also decided to get a new desk. I have had this IKEA desk hack for what seems like forever now and it’s time to move on. I was looking for a large standing desk and after doing some searching I decided to go with the Uplift Standing desk. This desk is 72”x30” and the finish is Heritage Oak.",[651,188356,188357],{},[660,188358],{"alt":188359,"src":188360},"Uplift Desk","/images/newsletter/2024/10/21/uplift_desk.png",[4542,188362,97345],{"id":97344},[651,188364,188365],{},"This week on Spring Office Hours I’m excited to sit down with Nelson Djalo, founder of Amigoscode, to discuss the art of continuous learning and teaching Spring. In this episode, the we’ll explore Nelson's journey from software engineer to influential educator, diving into his unique approach to teaching Spring and DevOps. Discover valuable insights on staying current in the fast-paced world of software development, and learn how Nelson's platform is helping millions break into tech.",[5988,188367],{"id":188368},"zKSbm2Ce6p8",[4542,188370,123549],{"id":140820},[651,188372,188373,188374,188379,188380,188385],{},"There was a bunch of news related to Spring AI last week. First off the Spring Initializr now includes the ",[812,188375,188378],{"href":188376,"rel":188377},"https://x.com/therealdanvega/status/1846561630491992439",[816],"document reader starters",". There is also a new ",[812,188381,188384],{"href":188382,"rel":188383},"https://docs.spring.io/spring-ai/reference/api/chat/comparison.html",[816],"Chat Model Comparison"," matrix in the documentation that I think you’re really going to enjoy.",[651,188387,188388],{},"I created some new videos last week:",[5909,188390,188392],{"id":188391},"ai-for-java-developers","AI for Java Developers",[651,188394,188395],{},"I wanted to show off how to get started with AI for Java Developers. If you want to call a Large Language Model there are REST APIs that you can call from the command line or a simple Java Application. Once you start building real world applications though a framework like Spring AI really helps you by solving a lot of the problems you are going to run into.",[5988,188397],{"id":188398},"uoOwVWVl_eU",[5909,188400,188402],{"id":188401},"calling-multiple-llms","Calling Multiple LLMs",[651,188404,188405],{},"Learn how to integrate multiple Large Language Models (LLMs) in a single Java application using Spring AI. This tutorial demonstrates how to use both OpenAI's GPT-4 and Anthropic's Claude 3.5 Sonnet in the same Spring Boot project.",[5988,188407],{"id":188408},"bK1MTlEDQvk",[651,188410,188411],{},"Speaking of Spring AI I had a really great meeting with the team last week. I am going to help out with some resources for the community and I can’t wait to share those with you. Stay Tuned!",[4542,188413,188414],{"id":165429},"Tweets",[651,188416,188417],{},"Last week I worked on some graphics for a RAG video coming out",[187764,188419],{"id":188420},"1847335334641533118",[187764,188422],{"id":188423},"1846731132408418351",[651,188425,188426],{},"I have received so many amazing messages from the community lately but this one really hit.",[187764,188428],{"id":188429},"1846999540056183230",[4542,188431,188433],{"id":188432},"the-rest","THE REST",[651,188435,188436,188437,188442],{},"It appears that ",[812,188438,188441],{"href":188439,"rel":188440},"https://www.youtube.com/watch?v=UmbQGImXwvA",[816],"my presentation"," from ConnectTech last year has been posted to YouTube. If you’re interested in learning how I build a blog on top of Vue 3 / Nuxt 3 and Notion this presentation is right up your alley.",[4542,188444,188139],{"id":157703},[651,188446,188142,188447,188450],{},[812,188448,51474],{"href":188145,"rel":188449},[816]," (I’m not calling it X).",[651,188452,188150],{},{"title":674,"searchDepth":790,"depth":790,"links":188454},[188455,188456,188460,188461,188462],{"id":97344,"depth":790,"text":97345},{"id":140820,"depth":790,"text":123549,"children":188457},[188458,188459],{"id":188391,"depth":892,"text":188392},{"id":188401,"depth":892,"text":188402},{"id":165429,"depth":790,"text":188414},{"id":188432,"depth":790,"text":188433},{"id":157703,"depth":790,"text":188139},{"slug":188464,"date":188465},"office-upgrades","2024-10-21T09:00:00.000Z","/newsletter/2024/10/21/office-upgrades",{"title":188319,"description":188324},"newsletter/2024/10/21/office-upgrades","ZaBVZlUBDSAo-95LsY7fxqm3NJQqtiLNBlT7v_L42FA",{"id":188471,"title":188472,"body":188473,"description":188477,"extension":793,"meta":188619,"navigation":797,"path":188622,"seo":188623,"stem":188624,"__hash__":188625},"content/newsletter/2024/10/28/uplift-desk.md","New Standing Desk, JTE Spring Security Demo & RAG for Java Developers",{"type":648,"value":188474,"toc":188607},[188475,188478,188482,188485,188490,188493,188499,188503,188506,188510,188527,188530,188539,188542,188545,188549,188552,188555,188558,188561,188563,188566,188570,188588,188590,188593,188596,188599,188601,188603],[651,188476,188477],{},"Happy Monday and welcome to another edition of the newsletter. It's hard to believe that Halloween is this week but here we are. I love seeing Halloween costumes so if you have some pictures of you or you and family please share them with me!",[4542,188479,188481],{"id":188480},"office-updates","Office Updates",[651,188483,188484],{},"I told you last week that was upgrading my office and that continued last week. I got my new uplift desk and spent the weekend putting it together. I still haven't put the final touches on cable management because I am still trying to figure out what is actually going to go on the desk and how I want to lay things out.",[651,188486,188487],{},[660,188488],{"alt":188359,"src":188489},"/images/newsletter/2024/10/28/desk.png",[651,188491,188492],{},"I have a 27\" 4k and a 34\" Widescreen display and I have been using those with my laptop giving me 3 displays. I don't have the room for the 34\" widescreen now and quite frankly its overkill for a 3rd monitor. I think what I am going to do is sell that and purchase another 27\" 4k display. I also think I need a docking station because I pull my laptop from the desk often and I hate having to plugin 20 things. My tentative plan right now is to get another monitor and mount the displays on some swing mounts and not use my laptop as a display anymore and get rid of the laptop stand I am using. I also picked up this nice filing cabinet for underneath my Cleveland sign.",[651,188494,188495],{},[660,188496],{"alt":188497,"src":188498},"Filing Cabinet","/images/newsletter/2024/10/28/cabinet.jpeg",[4542,188500,188502],{"id":188501},"java-template-engine-spring-security-login","Java Template Engine + Spring Security Login",[651,188504,188505],{},"I just released a new tutorial showing how to build a Spring Security login system using Java Template Engine (JTE) and OAuth2. I created this in response to questions from viewers about using JTE with Spring Security, and I'm excited to share a clean solution that handles both traditional username/password authentication and social logins through Google and GitHub.",[5909,188507,188509],{"id":188508},"key-features-implemented","Key features implemented:",[5316,188511,188512,188515,188518,188521,188524],{},[5332,188513,188514],{},"Custom login form with Spring Security integration",[5332,188516,188517],{},"OAuth2 authentication with multiple providers",[5332,188519,188520],{},"Protected dashboard displaying user information",[5332,188522,188523],{},"Clean, modern UI built with Tailwind CSS",[5332,188525,188526],{},"Java Template Engine (JTE) integration with Spring Boot",[651,188528,188529],{},"In my tutorial, I walk through building a modern authentication system that I find essential for most projects. I also implemented a clean UI using Tailwind CSS, which I actually generated using AI to save time on the frontend work.",[651,188531,188532,188533,188538],{},"Rather than doing a line-by-line coding session, I focused on explaining the core concepts and implementation details, which I think makes the tutorial more practical and efficient. I've made all the code available on ",[812,188534,188537],{"href":188535,"rel":188536},"https://github.com/danvega/spring-boot-oauth-demo",[816],"GitHub"," as a starter template, so developers can quickly get up and running with secure authentication in their Spring Boot applications. For those interested in learning more about JTE, I've also included a link to my full playlist covering this template engine.",[651,188540,188541],{},"This tutorial also showcases how JTE can work seamlessly with Spring Security - something many developers have been asking me about since JTE was added to the Spring initializer.",[5988,188543],{"id":188544},"f1h4GkhxMp8",[4542,188546,188548],{"id":188547},"rag-for-java-developers","RAG for Java Developers",[651,188550,188551],{},"In my latest video, I tackled Retrieval Augmented Generation (RAG) in Java applications using Spring AI. I broke down why RAG matters - LLMs have training cutoff dates and aren't trained on your private data. More importantly, I clarified what RAG isn't: it's not just dumping all your documents into a prompt!",[651,188553,188554],{},"I demonstrated this by building a practical example, showing how to store economic analysis documents in a vector database and query them effectively. Using Spring AI made this process surprisingly straightforward, from document ingestion to similarity search.",[651,188556,188557],{},"The demo highlighted how RAG can provide accurate, up-to-date responses by intelligently retrieving relevant information chunks rather than sending entire documents to the LLM. For those concerned about data privacy, stay tuned for an upcoming video on using RAG with local LLMs like Ollama.",[5988,188559],{"id":188560},"6Pgmr7xMjiY",[4542,188562,169217],{"id":169216},[651,188564,188565],{},"Now that I have my office dialed in I want to make an effort to get back to live-streaming. Not that I have ever been a consistent live streamer but I want to be. I love hanging out with all of you and last week I was able to get 2 live streams in. It is so much easier to stream when so many of you show up and ask so many great questions and that is exactly what happened last week. Thank you to all of you for taking time out of your day and for the wonderful questions. Here are the recordings from those live streams and I will try and get some more in this week.",[5909,188567,188569],{"id":188568},"recent-streams","Recent Streams:",[5316,188571,188572,188580],{},[5332,188573,188574,188575],{},"10/23/2024 - ",[812,188576,188579],{"href":188577,"rel":188578},"https://www.youtube.com/live/e1c2GCL1vOM",[816],"Stream and Chill",[5332,188581,188582,188583],{},"10/24/2024 - ",[812,188584,188587],{"href":188585,"rel":188586},"https://www.youtube.com/live/9DRdXgYfjXg",[816],"Spring AI Workshop Updates",[4542,188589,188414],{"id":165429},[187764,188591],{"id":188592},"1849803941745152397",[187764,188594],{"id":188595},"1849844721448845627",[187764,188597],{"id":188598},"1849813392300671064",[4542,188600,157704],{"id":157703},[651,188602,188297],{},[651,188604,187950,188605,69920],{},[41107,188606],{},{"title":674,"searchDepth":790,"depth":790,"links":188608},[188609,188610,188613,188614,188617,188618],{"id":188480,"depth":790,"text":188481},{"id":188501,"depth":790,"text":188502,"children":188611},[188612],{"id":188508,"depth":892,"text":188509},{"id":188547,"depth":790,"text":188548},{"id":169216,"depth":790,"text":169217,"children":188615},[188616],{"id":188568,"depth":892,"text":188569},{"id":165429,"depth":790,"text":188414},{"id":157703,"depth":790,"text":157704},{"slug":188620,"date":188621},"uplift-desk","2024-10-28T09:00:00.000Z","/newsletter/2024/10/28/uplift-desk",{"title":188472,"description":188477},"newsletter/2024/10/28/uplift-desk","psOy4hErK_wY8HWCzTIKzHc-nDILjKxPFTRjTw7G1kU",{"id":188627,"title":188628,"body":188629,"description":188633,"extension":793,"meta":188764,"navigation":797,"path":188766,"seo":188767,"stem":188768,"__hash__":188769},"content/newsletter/2024/11/04/awesome-spring-ai.md","Awesome Spring AI, RestClient OAuth2 Support and new YouTube Tutorials",{"type":648,"value":188630,"toc":188755},[188631,188634,188640,188644,188657,188661,188664,188668,188671,188679,188682,188686,188689,188696,188700,188707,188739,188741,188744,188747,188749,188751],[651,188632,188633],{},"Happy Monday and welcome to another edition of the newsletter. Last week was Halloween, and we had some amazing weather and the girls go so much candy for the parents to go through while they are sleeping 🤣",[651,188635,188636],{},[660,188637],{"alt":188638,"src":188639},"Halloween 2024","/images/newsletter/2024/11/04/halloween_2024.png",[4542,188641,188643],{"id":188642},"new-macbook-announcement","New MacBook Announcement",[651,188645,146457,188646,188651,188652,188656],{},[812,188647,188650],{"href":188648,"rel":188649},"https://www.apple.com/macbook-pro",[816],"MacBook's were announced"," last week, and I was lucky enough to pick up a new one. I was able to order a new MacBook Pro M4 Max in space black, and I'm really excited to get my hands on this. I waited an entire day after the announcement though so mine won't be in for a couple of weeks. I did a quick ",[812,188653,184581],{"href":188654,"rel":188655},"https://x.com/therealdanvega/status/1852174428773249264",[816],", and it seems like you're interested in a live stream where I set a new machine up so I will get that scheduled.",[4542,188658,188660],{"id":188659},"in-this-edition","In This Edition",[651,188662,188663],{},"In this edition of the newsletter I want to talk to you about Awesome Spring AI which is a new resource for Spring AI. I'm also really excited about the new OAuth2 support in the Rest Client, so I put together a repo on that and I will be recording a video on that this week. Finally, October was a great month on the YouTube channel where I added 13 new videos, 3 of which came last week and I will tell you about them.",[4542,188665,188667],{"id":188666},"awesome-spring-ai","Awesome Spring AI",[651,188669,188670],{},"Last week on Spring Office Hours we sat down with the project lead for Spring AI, Dr Mark Pollack. It was great to catch up with him on where the project is and where it is heading.",[651,188672,188673,188674,188678],{},"We also had the opportunity to start a new resource for the Spring AI community called ",[812,188675,188667],{"href":188676,"rel":188677},"https://github.com/danvega/awesome-spring-ai?",[816],". This is a curated list of awesome resources, tools, tutorials, and projects for building generative AI applications using Spring AI. This repository aims to help developers leverage the power of Large Language Models (LLMs) within the Spring ecosystem.",[5988,188680],{"id":188681},"S1zmtURwc1o",[4542,188683,188685],{"id":188684},"restclient-oauth2-support","RestClient OAuth2 Support",[651,188687,188688],{},"A new blog post dropped on the Spring IO Blog last week introducing support for OAuth2 in the Rest Client. If you follow me you know that I am a huge fan of the Rest Client and have done a number of tutorials on it in the past.",[651,188690,188691,188692,188695],{},"One of the most asked questions about it was how to use it in conjunction with OAuth2. There was a way to do this in the past, but it involved writing your own interceptor. The new support in Spring Security 6.4 (and Spring Boot 3.4) makes this really easy to do. I put together a ",[812,188693,16596],{"href":147061,"rel":188694},[816]," showing this off and I plan to record a tutorial on it early this week.",[4542,188697,188699],{"id":188698},"youtube-videos-new","YouTube Videos (New)",[651,188701,188702,188703,188706],{},"I was able to record 3 new video last week and in case you missed them here they are. By the way if you aren't subscribed to my ",[812,188704,101297],{"href":101295,"rel":188705},[816]," you should do so now so you don't miss my next one!",[27665,188708,188709,188719,188729],{},[5332,188710,188711,188718],{},[812,188712,188715],{"href":188713,"rel":188714},"https://www.youtube.com/watch?v=rQV76dufxz4",[816],[2939,188716,188717],{},"Spring Environment Variables",": Never expose your API keys again! In this tutorial, we'll dive into managing environment variables in Spring Boot applications - the secure and professional way to handle configuration across different environments.",[5332,188720,188721,188728],{},[812,188722,188725],{"href":188723,"rel":188724},"https://www.youtube.com/watch?v=l35P5GylXN8",[816],[2939,188726,188727],{},"RestClient Logging",": Learn how to implement clean, efficient request/response logging for Spring Boot's REST Client! In this comprehensive tutorial, we'll build a reusable solution that gives you exactly the logging information you need, without the overwhelming verbosity of the underlying HTTP client logging.",[5332,188730,188731,188738],{},[812,188732,188735],{"href":188733,"rel":188734},"https://www.youtube.com/watch?v=ZUCVRppXPSc",[816],[2939,188736,188737],{},"Tokens are the currency of LLMs",": Ever wondered how much your AI requests are actually costing you? In this comprehensive tutorial, we dive deep into understanding tokens - the currency of Large Language Models (LLMs) - and build a practical Spring Boot application to track and monitor your token usage.",[4542,188740,188414],{"id":165429},[187764,188742],{"id":188743},"1852333793517936878",[187764,188745],{"id":188746},"1850939479978135999",[4542,188748,157704],{"id":157703},[651,188750,188297],{},[651,188752,187950,188753,69920],{},[41107,188754],{},{"title":674,"searchDepth":790,"depth":790,"links":188756},[188757,188758,188759,188760,188761,188762,188763],{"id":188642,"depth":790,"text":188643},{"id":188659,"depth":790,"text":188660},{"id":188666,"depth":790,"text":188667},{"id":188684,"depth":790,"text":188685},{"id":188698,"depth":790,"text":188699},{"id":165429,"depth":790,"text":188414},{"id":157703,"depth":790,"text":157704},{"slug":188666,"date":188765},"2024-11-04T09:00:00.000Z","/newsletter/2024/11/04/awesome-spring-ai",{"title":188628,"description":188633},"newsletter/2024/11/04/awesome-spring-ai","unaE4cpqSej3AXmZD9GjnYAxCdmMv3yM_LtQUg7nCjk",{"id":188771,"title":188772,"body":188773,"description":188899,"extension":793,"meta":188900,"navigation":797,"path":188902,"seo":188903,"stem":188904,"__hash__":188905},"content/newsletter/2024/11/11/generating-github-docs.md","Generating GitHub Docs, Notebook LM, Rest Client OAuth2 Support and Query by Example",{"type":648,"value":188774,"toc":188891},[188775,188782,188785,188789,188792,188800,188803,188806,188809,188812,188816,188822,188837,188840,188849,188852,188855,188858,188862,188865,188868,188871,188874,188876,188879,188882,188884,188889],[651,188776,188777,188778,188781],{},"Happy Monday and welcome to another edition of the newsletter. This week I'm getting my new MacBook Pro and my plan was to live stream my setup. I would love to do this, but I also realize what a time commitment that would be and I have a lot of things that have to take priority this week. If I have time you will get notified if you're subscribed to my ",[812,188779,101297],{"href":101295,"rel":188780},[816]," but if I don't I will take notes and share my setup with you.",[651,188783,188784],{},"I want to share a little bit more of what I have been using AI for lately. I was able to publish 2 new videos last week on Spring Security and Spring Data and I will tell you a little bit about those.",[4542,188786,188788],{"id":188787},"generating-github-readme-documentation","Generating GitHub Readme Documentation",[651,188790,188791],{},"If you're new around here I am really into AI and how it can help increase productivity. I will admit that when I first introduced to AI I thought it was just another gimmick. I would run off to ChatGPT, ask a question and get a decent response. Now I see it as the ultimate Swiss Army Knife that I can use to be a more productive developer.",[651,188793,188794,188795,188799],{},"I have talked about ",[812,188796,188798],{"href":101295,"rel":188797},[816],"Claude Projects",", but I don't think I have gone deep on them before. I like using each project and setting the custom instructions to streamline my workflows.",[651,188801,188802],{},"I wanted to share a time-saving workflow I've been using lately that's completely changed how I handle documentation for my demo projects. Last week, I made a video about this because I think it could help many of you who regularly create and share code repositories.",[651,188804,188805],{},"Here's the situation: We all know that good documentation is crucial. When someone lands on your GitHub repo, they need to understand what the project does, how to get started, and what components are involved. But let's be honest – writing comprehensive README files isn't exactly the most exciting part of our day, is it?",[651,188807,188808],{},"Instead of manually writing documentation (which I could definitely find better uses of my time for), I've developed a streamlined process using generative AI. Check out the video below where I go through my process for generating documentation.",[187764,188810],{"id":188811},"1854546291620974994",[4542,188813,188815],{"id":188814},"notebook-lm-audio-overview","Notebook LM Audio Overview",[651,188817,188818],{},[660,188819],{"alt":188820,"src":188821},"Robot Podcast","/images/newsletter/2024/11/11/2-robots-conducting-a-podcast.jpg",[651,188823,188824,188825,188830,188831,188836],{},"Last week I published a ",[812,188826,188829],{"href":188827,"rel":188828},"https://www.bytesizedai.dev/p/notebook-lm-audio-overview",[816],"new issue"," on my other newsletter, ByteSized AI on ",[812,188832,188835],{"href":188833,"rel":188834},"https://notebooklm.google/",[816],"Notebook LM",". In that issue I talked about a particularly interesting feature called Audio Overview. NotebookAM's Audio Overview is a fascinating feature that transforms your written content into dynamic audio discussions. Instead of a monotonous text-to-speech conversion, it creates an engaging conversation between two AI hosts who discuss your content, make connections, and break down complex ideas into digestible segments.",[651,188838,188839],{},"I mention this because I have started taking my YouTube videos, blog posts and whatever other content I have and turning them into podcasts. The results were fascinating. Within minutes, NotebookLM generated a podcast-style discussion that not only summarized my main points but also added valuable context and connections I hadn't explicitly made in the original content.",[651,188841,188842,188843,188848],{},"I don't think I can stress that point enough. It is not just taking a blog post or a video and talking verbatim about the points made in its reference material. It is generating new content based on the document provided, and it does a perfect job of this. I have been throwing around the idea of turning this into an actual podcast, but I'm not there yet. For now, I have decided to create a ",[812,188844,188847],{"href":188845,"rel":188846},"https://danvega.notion.site/Developing-with-Dan-138550ad79c380ac935fdad17513b19c",[816],"Notion site"," where you can find all the episodes that I have generated.",[4542,188850,188851],{"id":147057},"Rest Client OAuth2 Support",[651,188853,188854],{},"I talked about this in last week's episode but Spring Security 6.4 is adding Rest Client OAuth2 Support. This tutorial walks you through building a secure multi-module application from scratch, complete with authorization server, resource server, and client applications. In this step-by-step guide, we'll build a OAuth2 implementation using Spring Boot 3.4's latest features. Perfect for developers looking to implement secure authentication in their Spring applications!",[5988,188856],{"id":188857},"nFKcJDpUuZ8",[4542,188859,188861],{"id":188860},"query-by-example","Query by Example",[651,188863,188864],{},"I received a question about using QueryByExampleExectuor in a GraphQL application. I thought it was a great question and plan on doing a video on it this week. Before we dive into that though I thought it was a good opportunity to take a look at what Query by Example is and when you might want to reach for it.",[651,188866,188867],{},"Tired of writing endless custom repository methods for your Spring Boot applications? In this tutorial, I'll show you how Query By Example (QBE) can revolutionize the way you handle dynamic database searches. Whether you're building a search form with multiple filters or need flexible querying capabilities, QBE is about to become your new best friend! 🚀",[651,188869,188870],{},"In this comprehensive tutorial, we'll build a complete Spring Boot application that demonstrates the power of Query By Example. I'll walk you through every step, from basic setup to advanced implementation, showing you how to eliminate boilerplate code and create clean, maintainable search functionality.",[5988,188872],{"id":188873},"NGVWHdGNbiI",[4542,188875,188414],{"id":165429},[187764,188877],{"id":188878},"1853462460235169794",[187764,188880],{"id":188881},"1854966561049903444",[4542,188883,188139],{"id":157703},[651,188885,188142,188886,188450],{},[812,188887,51474],{"href":188145,"rel":188888},[816],[651,188890,188150],{},{"title":674,"searchDepth":790,"depth":790,"links":188892},[188893,188894,188895,188896,188897,188898],{"id":188787,"depth":790,"text":188788},{"id":188814,"depth":790,"text":188815},{"id":147057,"depth":790,"text":188851},{"id":188860,"depth":790,"text":188861},{"id":165429,"depth":790,"text":188414},{"id":157703,"depth":790,"text":188139},"Happy Monday and welcome to another edition of the newsletter. This week I'm getting my new MacBook Pro and my plan was to live stream my setup. I would love to do this, but I also realize what a time commitment that would be and I have a lot of things that have to take priority this week. If I have time you will get notified if you're subscribed to my YouTube channel but if I don't I will take notes and share my setup with you.",{"slug":188901,"date":148683},"generating-github-docs","/newsletter/2024/11/11/generating-github-docs",{"title":188772,"description":188899},"newsletter/2024/11/11/generating-github-docs","fNKKHj41OP1itAp8RwOI_uMBmqrMRCeECqi39qehgCE",{"id":188907,"title":188908,"body":188909,"description":188913,"extension":793,"meta":189068,"navigation":797,"path":189071,"seo":189072,"stem":189073,"__hash__":189074},"content/newsletter/2024/11/18/new-macbook-pro-m4.md","Setting up a new MacBook Pro, Spring Raycast Extension and website updates",{"type":648,"value":188910,"toc":189063},[188911,188914,188923,188926,188928,188931,188934,188947,188956,188959,188962,188966,188969,188972,188981,188987,188989,188998,189001,189023,189027,189030,189033,189036,189039,189042,189045,189049,189054],[651,188912,188913],{},"Happy Monday and welcome to another edition of the newsletter. Last week I got a new laptop and spent some time setting it up. I have always wanted to share my process on how I set up a new machine but in the past I have lacked the patience to do so. This time around I waited patiently for a few days and live-streamed my setup.",[651,188915,188916,188917,188922],{},"I recently joined ",[812,188918,188921],{"href":188919,"rel":188920},"https://bsky.app/profile/danvega.dev",[816],"BlueSky"," and so far I have been enjoying less noise. I’m not in the camp of leaving Twitter because I do enjoy it there, but I thought I would check it out. If you’re over there please give me a follow and I will give you one back if I can. I will say that has been one annoying feature of the platform so far is I can’t find a way to silence notifications on my phone.",[651,188924,188925],{},"In this edition of the newsletter I will share with you the replay of that live stream and talk a little bit about my setup. During the setup I talked about one of my favorite applications for macOS, Raycast. Last week I was able to create a new extension for Raycast that allows you to create a Spring Boot app using the Spring Initializer. Finally, I made a few updates to website that I will share with you.",[4542,188927,165794],{"id":166021},[651,188929,188930],{},"There are few things in the world of tech that are as exciting as unboxing a new gadget that you’re excited to get your hands on. I purchased a new M1 back in 2021, and I have just been blown away by how great of a machine that it has been compared to the Intel versions of the MacBook.",[651,188932,188933],{},"When the new M4 Max was announced I knew that it was time to upgrade. The performance comparisons to the M1 were through the roof and I knew I had to get my hands on this one. If you’re curious about the specs I ended up getting the 14” MacBook Pro Space Black with the following configuration:",[5316,188935,188936,188939,188942,188944],{},[5332,188937,188938],{},"Apple M4 Max chip with 16‑core CPU, 40‑core GPU, 16‑core Neural Engine",[5332,188940,188941],{},"48GB unified memory",[5332,188943,82298],{},[5332,188945,188946],{},"Three Thunderbolt 5 ports, HDMI port, SDXC card slot, headphone jack, MagSafe 3 port",[651,188948,188949,188950,188955],{},"Before I went on and streamed my setup I put together another version of my ",[812,188951,188954],{"href":188952,"rel":188953},"https://github.com/danvega/new-macbook-setup/blob/master/2024/README.md",[816],"New MacBook Setup"," on GitHub. This document contains every application and configuration I did when I set up this new machine.",[5988,188957],{"id":188958},"t8ry7PkYe6M",[651,188960,188961],{},"I hope you enjoyed the live stream or the replay if you couldn’t join me live. I’m always curious to hear how others set up a new machine so if you have any must have apps or tips for me please let me know.",[4542,188963,188965],{"id":188964},"spring-initializer-raycast-extension","Spring Initializer Raycast Extension",[651,188967,188968],{},"During the live stream I mentioned how much I love Raycast. I find it to be one of those must have applications that really helps increase productivity in a number of ways. I have always wanted to create an extension for the Raycast store but I had a couple of things stopping me.",[651,188970,188971],{},"First, I wasn’t sure what type of extension I would create. Second, I’m not a React developer, so I wouldn’t even know where to start. I noticed that there wasn’t a Spring Initalizr extension so this would be a perfect one for me to create. Now with AI I could get Claude to help me create one.",[651,188973,188974,188975,188980],{},"I was able to get this working last week, and you can find the ",[812,188976,188979],{"href":188977,"rel":188978},"https://github.com/danvega/raycast-spring-initializr",[816],"code for it here",". I have submitted this to the Raycast Store and it hopefully it will be published this week. In the meantime you can pull the code and run it locally. This extension will allow you to create a new Spring Boot Application right from the Raycast Launcher.",[651,188982,188983],{},[660,188984],{"alt":188985,"src":188986},"Spring Raycast Extension","/images/newsletter/2024/11/18/spring_raycast_extension.png",[4542,188988,161336],{"id":161335},[651,188990,188991,188992,188997],{},"I spent a little time last week fixing up a couple of issues on my website. The first one was with the image loader on my home page. Previously I had some “Loading Images” text in place before the images were loaded. This caused the screen to jump when the actual images were loaded. Now I am using a cool technique called “skeleton loading” and it was super easy to do with Tailwind. If you want to learn how I did it you can check out the ",[812,188993,188996],{"href":188994,"rel":188995},"https://github.com/danvega/danvega-dev-nuxt/blob/main/components/home/Photos.vue",[816],"code here"," on line 56.",[651,188999,189000],{},"Here are a couple of other updates I made:",[5316,189002,189003,189011,189020],{},[5332,189004,189005,189006,189010],{},"I added a new ",[812,189007,24879],{"href":189008,"rel":189009},"https://www.danvega.dev/blog/no-lombok",[816]," on Why you might not need to use Lombok anymore. I often get asked in videos why I don’t use Lombok so I thought I would put this together.",[5332,189012,189013,189014,189019],{},"I updated my ",[812,189015,189018],{"href":189016,"rel":189017},"https://www.danvega.dev/uses",[816],"uses page"," with all of my current hardware and software. I have been updating my office lately and this list was getting behind.",[5332,189021,189022],{},"Finally, there is an easter egg on the home page if you use the keyboard shortcut ctrl+space. 🎉",[651,189024,189025],{},[2939,189026,187762],{},[651,189028,189029],{},"I hit a huge milestone crossing 20,000 followers on Twitter last week. Thanks to everyone (and the bots) for listening to me ramble on this platform over the years.",[187764,189031],{"id":189032},"1858319719616364694",[651,189034,189035],{},"AI Image generation is getting pretty crazy. I'm not sure why, but I love this version of me.",[187764,189037],{"id":189038},"1857068152036680149",[651,189040,189041],{},"7.5 million minutes of MY YouTube channel watched in a single month is mind-blowing 🤯",[187764,189043],{"id":189044},"1856691471665041419",[651,189046,189047],{},[2939,189048,188139],{},[651,189050,188142,189051,188450],{},[812,189052,51474],{"href":51472,"rel":189053},[816],[651,189055,41105,189056,69920,189058,189060],{},[41107,189057],{},[41107,189059],{},[812,189061,53869],{"href":53869,"rel":189062},[816],{"title":674,"searchDepth":790,"depth":790,"links":189064},[189065,189066,189067],{"id":166021,"depth":790,"text":165794},{"id":188964,"depth":790,"text":188965},{"id":161335,"depth":790,"text":161336},{"slug":189069,"date":189070},"new-macbook-pro-m4","2024-11-18T09:00:00.000Z","/newsletter/2024/11/18/new-macbook-pro-m4",{"title":188908,"description":188913},"newsletter/2024/11/18/new-macbook-pro-m4","YrRTE807yRpfm7hHdVj3IfYR5xIV0o7rWd_gHRohS-0",{"id":189076,"title":189077,"body":189078,"description":189082,"extension":793,"meta":189352,"navigation":797,"path":189355,"seo":189356,"stem":189357,"__hash__":189358},"content/newsletter/2024/11/25/spring-boot-3-4-0-released.md","Spring Boot 3.4, Spring AI M4 and AI Agents!",{"type":648,"value":189079,"toc":189347},[189080,189083,189086,189089,189092,189098,189101,189105,189112,189115,189118,189121,189124,189135,189138,189149,189152,189163,189166,189177,189180,189191,189194,189198,189203,189212,189215,189226,189229,189240,189243,189254,189257,189268,189271,189275,189280,189283,189286,189289,189303,189311,189319,189323,189326,189329,189333,189338],[651,189081,189082],{},"Happy Monday and welcome to another edition of the newsletter. It’s Thanksgiving here in the US this week so I thought I would start this off my saying what I’m thankful for. I’m first and foremost thankful for my family. They are why I wake up every day and they make everything worth it.",[651,189084,189085],{},"I’m thankful for all of you. I know we get caught up in this world of social media on how many followers we have (I’m guilty of this) but It’s the wrong way to look at it. I would much rather have 100 followers on any platform that wanted to learn from me or cared what I have to say as opposed to 100,000 followers who didn’t really care about me.",[651,189087,189088],{},"I love what I get to do every single day. I love to learn and teach what I know back to the community. This could be through my YouTube channel, newsletter, blog, presentation or any of the number of things I’m working on. The fact that I still get excited rolling into work every day is the gift of a lifetime and I don’t take it for granted.",[651,189090,189091],{},"So thank you to all of you who follow me here or on social media and allow me to ramble about the things I am passionate about ❤️",[651,189093,189094],{},[660,189095],{"alt":189096,"src":189097},"Thankful for you","/images/newsletter/2024/11/25/thankful-for-being-your-teacher-2.png",[651,189099,189100],{},"In this issue I want to talk about Spring Boot 3.4, Spring AI and AI Agents.",[4542,189102,189104],{"id":189103},"spring-boot-34","Spring Boot 3.4",[651,189106,189107,189108,664],{},"It’s a great time to be a Java and Spring developer. Every 6 months we get a new version of Java with the latest version 23 being released in September. Last week Spring Boot 3.4 was released and it came packed full of updates. If you want to learn more about what changed along with what’s new you can check out the ",[812,189109,95704],{"href":189110,"rel":189111},"https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.4-Release-Notes",[816],[651,189113,189114],{},"I wasn’t able to be a part of it last week but DaShaun and Phil Webb had a special edition of the Spring Office Hours Podcast had a release party. It’s always great hearing from Phil and the Spring Team about all of their hard work.",[5988,189116],{"id":189117},"ivKvlLJ_y4s",[651,189119,189120],{},"I will be working on some content around Spring Boot 3.4 over the next few weeks and here is what I plan on focusing on:",[651,189122,189123],{},"🔄 HTTP Client Enhancements",[5316,189125,189126,189129,189132],{},[5332,189127,189128],{},"Improved RestClient/RestTemplate auto-configuration",[5332,189130,189131],{},"Support for multiple HTTP clients (Apache, Jetty, Reactor Netty, JDK)",[5332,189133,189134],{},"Graceful shutdown now enabled by default",[651,189136,189137],{},"📊 Observability & Logging",[5316,189139,189140,189143,189146],{},[5332,189141,189142],{},"New structured logging support (ECS, GELF, Logstash)",[5332,189144,189145],{},"Enhanced OTLP tracing with gRPC transport",[5332,189147,189148],{},"Improved SSL monitoring and certificate health checks",[651,189150,189151],{},"🧪 Testing & Development",[5316,189153,189154,189157,189160],{},[5332,189155,189156],{},"AssertJ support for MockMvc",[5332,189158,189159],{},"Enhanced Testcontainers and Docker Compose support",[5332,189161,189162],{},"New @Fallback annotation for conditional beans",[651,189164,189165],{},"🛠️ Infrastructure Updates",[5316,189167,189168,189171,189174],{},[5332,189169,189170],{},"Smaller container images with new tiny builder default",[5332,189172,189173],{},"Virtual threads support in key components",[5332,189175,189176],{},"Improved bean validation and configuration properties",[651,189178,189179],{},"🔄 Framework Updates",[5316,189181,189182,189185,189188],{},[5332,189183,189184],{},"Spring Framework 6.2",[5332,189186,189187],{},"Spring Security 6.4",[5332,189189,189190],{},"Spring Data 2024.1",[651,189192,189193],{},"This release focuses on developer experience, observability, and performance while maintaining backward compatibility.",[4542,189195,189197],{"id":189196},"spring-ai-m4","Spring AI M4",[651,189199,189200],{},[660,189201],{"alt":123549,"src":189202},"/images/newsletter/2024/11/25/spring-ai---robots-writing-code-4.png",[651,189204,189205,189206,189211],{},"Last week also brought us the ",[812,189207,189210],{"href":189208,"rel":189209},"https://spring.io/blog/2024/11/20/spring-ai-1-0-0-m4-released",[816],"Spring AI 1.0.0 M4 release",", and it's packed with exciting new features that make AI integration even more accessible for Spring developers. Here are the highlights I'm particularly excited about:",[651,189213,189214],{},"🤖 Amazon Bedrock Converse",[5316,189216,189217,189220,189223],{},[5332,189218,189219],{},"New unified interface for Amazon's AI chat models",[5332,189221,189222],{},"Adds powerful features like Tool Calling and Visual capabilities",[5332,189224,189225],{},"If you're using Amazon's models, this is the way forward",[651,189227,189228],{},"⚡ Supercharged Function Calling",[5316,189230,189231,189234,189237],{},[5332,189232,189233],{},"Better support for Java functions (Function, Supplier, Consumer)",[5332,189235,189236],{},"New Tool Context for state management",[5332,189238,189239],{},"Setting up the foundation for @ToolMapping coming in M5",[651,189241,189242],{},"🎯 Kotlin First-Class Support",[5316,189244,189245,189248,189251],{},[5332,189246,189247],{},"Finally, proper Kotlin support with type-safe APIs",[5332,189249,189250],{},"Direct Kotlin function registration as AI tools",[5332,189252,189253],{},"Makes AI integration feel native for Kotlin developers",[651,189255,189256],{},"📚 Advanced RAG (Retrieval Augmented Generation)",[5316,189258,189259,189262,189265],{},[5332,189260,189261],{},"New experimental modular system",[5332,189263,189264],{},"Think LEGO blocks for building AI retrieval systems",[5332,189266,189267],{},"Flexible components for query transformation, routing, and document handling",[651,189269,189270],{},"The team is pushing hard toward GA, with M5 planned for December and the final release coming in January. If you're interested in AI development with Spring, now is a great time to start experimenting with these features.",[4542,189272,189274],{"id":189273},"ai-agents","AI Agents",[651,189276,189277],{},[660,189278],{"alt":189274,"src":189279},"/images/newsletter/2024/11/25/robots-acting-as-agents-or-virtual-assistants-5.jpeg",[651,189281,189282],{},"There's been a lot of excitement around AI Agents lately, and for good reason. In my latest ByteSized AI newsletter, I wanted to cut through the noise and explain why they're such a big deal.",[651,189284,189285],{},"Here's the key insight: Traditional AI is reactive - like a really smart assistant waiting for your questions. You prompt, it responds. But AI Agents? They're proactive players that can observe, decide, and act on their own. Think of them as AI systems that don't just answer questions, but can actually get things done.",[651,189287,189288],{},"🤖 What makes them special:",[5316,189290,189291,189294,189297,189300],{},[5332,189292,189293],{},"They understand their environment",[5332,189295,189296],{},"Make autonomous decisions",[5332,189298,189299],{},"Take actions to achieve goals",[5332,189301,189302],{},"Learn and adapt from experience",[651,189304,189305,189306,664],{},"Want to dive deeper? I break it all down in my ",[812,189307,189310],{"href":189308,"rel":189309},"https://www.bytesizedai.dev/p/ai-agents-the-next-evolution-in-automation",[816],"latest article",[651,189312,189313,189314,189318],{},"I really liked ",[812,189315,85142],{"href":189316,"rel":189317},"https://youtu.be/KrRD7r7y7NY",[816]," from Andrew Ng who is one the great minds we have in the AI space. In this presentation Andrew goes through his definition of AI agents and I feel like I learned a lot from this and definitely worth a watch.",[651,189320,189321],{},[2939,189322,187762],{},[651,189324,189325],{},"I don’t know what is going on with YouTube lately but one video seems to be getting a lot of love. 160,000 views in 28 days is pretty wild. If all of my videos were",[187764,189327],{"id":189328},"1860769389454184468",[651,189330,189331],{},[2939,189332,188139],{},[651,189334,188142,189335,47213],{},[812,189336,51472],{"href":51472,"rel":189337},[816],[651,189339,41105,189340,69920,189342,189344],{},[41107,189341],{},[41107,189343],{},[812,189345,53869],{"href":53869,"rel":189346},[816],{"title":674,"searchDepth":790,"depth":790,"links":189348},[189349,189350,189351],{"id":189103,"depth":790,"text":189104},{"id":189196,"depth":790,"text":189197},{"id":189273,"depth":790,"text":189274},{"slug":189353,"date":189354},"spring-boot-3-4-0-released","2024-11-25T09:00:00.000Z","/newsletter/2024/11/25/spring-boot-3-4-0-released",{"title":189077,"description":189082},"newsletter/2024/11/25/spring-boot-3-4-0-released","Yrzva4VGhrAnbHm4CsT8Kbpy4Cj_Gl2H9GqooivoMqA",{"id":189360,"title":189361,"body":189362,"description":189366,"extension":793,"meta":189532,"navigation":797,"path":189535,"seo":189536,"stem":189537,"__hash__":189538},"content/newsletter/2024/12/09/spring-init-bookmarks.md","Spring Initializr Bookmarks, GraphQL Query by Example and Open AI's 12 days of Shipmas",{"type":648,"value":189363,"toc":189527},[189364,189367,189373,189378,189392,189401,189420,189426,189430,189433,189436,189440,189447,189450,189467,189470,189473,189477,189480,189486,189489,189496,189500,189503,189506,189509,189513,189518],[651,189365,189366],{},"Happy Monday and welcome to another edition of the newsletter. This past weekened was a much needed one for me. My wife and I were able to go on a date, we got a bunch of decorating done around the house and we had brunch with Santa 🧑🎄 These girls make me so happy 🤩",[651,189368,189369],{},[660,189370],{"alt":189371,"src":189372},"Brunch with Santa","/images/newsletter/2024/12/09/bws_01.jpeg",[651,189374,189375],{},[660,189376],{"alt":189371,"src":189377},"/images/newsletter/2024/12/09/bws_02.jpeg",[651,189379,189380,189381,189386,189387,189391],{},"This week I’m giving a presentation on common mistakes that Spring Developers make. I made ",[812,189382,189385],{"href":189383,"rel":189384},"https://www.youtube.com/watch?v=PbkROQPTBao",[816],"a video"," earlier this year when I went over some of the most common mistakes I could think of. We also did a whole show on ",[812,189388,97345],{"href":189389,"rel":189390},"https://spring-office-hours.transistor.fm/episodes/spring-office-hours-s3e4-common-spring-developer-mistakes",[816]," about this topic.",[651,189393,189394,189395,189400],{},"This is going to be a 60-minute version of this talk but I quickly built out a ",[812,189396,189399],{"href":189397,"rel":189398},"https://github.com/danvega/scm-workshop",[816],"repo"," that has a few hours of content. I think my main goal with this talk is for developers to walk away and learning 1 or 2 new things that didn’t already know. Some of the demos I walk through might be pretty common to a lot of developers but if you can pick up 1 thing from this talk and then take it back and improve your codebase I will consider it a success.",[651,189402,189403,189404,189408,189409,189414,189415,189419],{},"Last week I was lucky to ",[812,189405,129981],{"href":189406,"rel":189407},"https://spring-office-hours.transistor.fm/episodes/s3e41-springs-hidden-powers-with-greg-turnquist",[816]," with my good friend Greg Turnquist on Spring Office Hours. We got to catch up on what Greg is doing over at ",[812,189410,189413],{"href":189411,"rel":189412},"https://www.cockroachlabs.com/",[816],"Cockroach Labs"," and I learned a few new things which is a common occurrence when I get to chat with Greg. This week we are sitting down with JetBrains Developer Advocate ",[812,189416,189417],{"href":189417,"rel":189418},"https://x.com/sivalabs?utm_source=dan-vega.beehiiv.com&utm_medium=referral&utm_campaign=spring-initializr-bookmarks-graphql-query-by-example-and-open-ai-s-12-days-of-shipmas",[816]," and I’m looking forward to that talk.",[651,189421,166085,189422,189425],{},[812,189423,97345],{"href":186873,"rel":189424},[816]," we only have a few more episodes and then season 3 will come to an end. Pretty crazy to think that we are heading into Season 4 in less than a month. This year has been a big year for the podcast and I can’t thank you all enough for the support.",[4542,189427,189429],{"id":189428},"spring-initializr-bookmarks","Spring Initializr Bookmarks",[651,189431,189432],{},"The Spring Initializer has introduced a helpful new bookmarking feature that lets developers save their frequently used project configurations. Users can now create a project setup with their preferred settings (like Java version, dependencies, and build tools), and save it as a named bookmark for future use. For example, you might save a configuration for Spring AI projects with Claude, including specific dependencies like Web, Anthropic Cloud, PGVector, Docker Compose, and actuator. These bookmarks are easily accessible through a \"starred\" section, where they can be managed, edited, or deleted. The tool also maintains a history of recent configurations. If you head over to Twitter using the link below you can check out a short video I did on this feature.",[187764,189434],{"id":189435},"1865063943095320781",[4542,189437,189439],{"id":189438},"graphql-repository-query-by-example","GraphQL Repository Query By Example",[651,189441,175600,189442,189446],{},[812,189443,24879],{"href":189444,"rel":189445},"https://www.danvega.dev/blog/spring-boot-graphql-query-by-example",[816]," and video that demonstrates how to build a GraphQL API using Spring's @GraphQLRepository annotation combined with Query by Example (QBE) functionality.I show you how to create a simple book management system that allows flexible querying without writing extensive boilerplate code. Instead of creating multiple custom repository methods for different search combinations (by title, author, year, etc.), QBE allows developers to create dynamic queries by example.",[651,189448,189449],{},"The key steps covered include:",[5316,189451,189452,189455,189458,189461,189464],{},[5332,189453,189454],{},"Setting up a Spring Boot project with GraphQL, JPA, and Postgres",[5332,189456,189457],{},"Creating a GraphQL schema for books with input types for flexible querying",[5332,189459,189460],{},"Implementing a JPA entity and repository that extends both JpaRepository and QueryByExampleExecutor",[5332,189462,189463],{},"Using the @GraphQLRepository annotation to automatically wire up GraphQL queries to repository methods",[5332,189465,189466],{},"Testing the API through GraphQL's playground interface",[651,189468,189469],{},"The main benefit highlighted is the significant reduction in boilerplate code - developers don't need to write individual controller methods or data fetchers, as the @GraphQLRepository annotation handles this automatically. The approach is particularly useful for rapid prototyping and building flexible search functionality.",[5988,189471],{"id":189472},"J8vC8RflPPY",[4542,189474,189476],{"id":189475},"openai-releases-gpt-o1","OpenAI Releases GPT-o1",[651,189478,189479],{},"OpenAI kicked off their \"12 Days of Shipmas\" with the release of GPT-o1, a groundbreaking AI model that prioritizes advanced reasoning capabilities over raw processing power. Building on their earlier o1-preview and o1-mini models, GPT-o1 shows remarkable improvements in mathematical and scientific reasoning, achieving 83.3% accuracy on Mathematics Olympiad problems and reaching the 89th percentile in Codeforces competitions.",[651,189481,189482],{},[660,189483],{"alt":189484,"src":189485},"Reasoning with Robots","/images/newsletter/2024/12/09/robots-reasoning-with-each-other-in-the-style-of-p.jpeg",[651,189487,189488],{},"The model introduces a sophisticated \"chain-of-thought\" methodology, allowing it to break down complex problems into manageable steps and think through solutions more thoroughly. Along with the model release, OpenAI launched ChatGPT Pro, a new $200 monthly subscription tier offering unlimited access to both GPT-o1 and GPT-4o, letting users leverage each model's unique strengths – GPT-4o for faster, general-purpose tasks and GPT-o1 for complex technical problem-solving requiring deeper reasoning.",[651,189490,189491,189492,664],{},"If you want to read more about It I wrote an article on my other newsletter, ",[812,189493,187509],{"href":189494,"rel":189495},"https://www.bytesizedai.dev/p/openai-s-gpt-o1-a-new-era-of-ai-reasoning",[816],[651,189497,189498],{},[2939,189499,187762],{},[651,189501,189502],{},"Are you looking for resources to learn Spring AI?",[187764,189504],{"id":189505},"1865118162770928114",[187764,189507],{"id":189508},"1863767002088694093",[651,189510,189511],{},[2939,189512,188139],{},[651,189514,188142,189515,47213],{},[812,189516,51472],{"href":51472,"rel":189517},[816],[651,189519,41105,189520,69920,189522,189524],{},[41107,189521],{},[41107,189523],{},[812,189525,53869],{"href":53869,"rel":189526},[816],{"title":674,"searchDepth":790,"depth":790,"links":189528},[189529,189530,189531],{"id":189428,"depth":790,"text":189429},{"id":189438,"depth":790,"text":189439},{"id":189475,"depth":790,"text":189476},{"slug":189533,"date":189534},"spring-init-bookmarks","2024-12-09T09:00:00.000Z","/newsletter/2024/12/09/spring-init-bookmarks",{"title":189361,"description":189366},"newsletter/2024/12/09/spring-init-bookmarks","ZlcFiNQgHd8wTj6E8tBT1FzqMKLuKtOuYBAl5JoNLE0",{"id":189540,"title":189541,"body":189542,"description":189546,"extension":793,"meta":189668,"navigation":797,"path":189671,"seo":189672,"stem":189673,"__hash__":189674},"content/newsletter/2024/12/16/scm-ai-productivity-hack.md","Spring Common Mistakes, AI Productivity Hack & IntelliJ Tips and Tricks",{"type":648,"value":189543,"toc":189662},[189544,189547,189552,189555,189559,189568,189571,189574,189578,189587,189590,189593,189596,189600,189608,189611,189615,189618,189621,189624,189630,189634,189637,189639,189642,189644,189648,189653],[651,189545,189546],{},"Happy Monday and welcome to another edition of the newsletter. It is really hard to believe that it is the middle of December. Well now that I think about it the weather has been extremely cold so it’s not that hard to believe. My family and I had a brunch with Santa even over the weekend and the girls absolutely loved it. I’m not a fan of this cold weather we have right now but I love seeing the girls happy so it makes everything worth it.",[651,189548,189549],{},[660,189550],{"alt":189371,"src":189551},"/images/newsletter/2024/12/16/santa_brunch.jpeg",[651,189553,189554],{},"In today’s newsletter I want to start off by talking about some of the common mistakes we make as Spring Developers. From there we will dive into a productivity hack that I have been using when working with Large Language Models that has been a real time saver. I’ll share with you a conversation I had with Siva Reddy last week on Spring Office hours and my move to Figma for YouTube Thumbnail designs. I hope you have an amazing week as we start to close out the year!",[4542,189556,189558],{"id":189557},"spring-common-mistakes","Spring Common Mistakes",[651,189560,189561,189562,189567],{},"Last week I gave a presentation for the ",[812,189563,189566],{"href":189564,"rel":189565},"https://www.linkedin.com/showcase/datev-software-craft-community",[816],"Software Craft Community at Datev"," on common mistakes Spring Developers make. I have talked about a few of these mistakes before but this is the first time that I put a bunch of them into presentation format. I actually have a ton of material on this and if we did a little more live coding this would probably be about a 2 hour presentation.",[651,189569,189570],{},"I want to thank Datev for having me and allowing me to ramble and for everyone who attended and asked so many great questions. If you’re interested in a deeper dive on this let me know, I’m considering making a series out of this for the YouTube channel.",[5988,189572],{"id":189573},"jAsxVPCOATA",[4542,189575,189577],{"id":189576},"the-best-ai-productivity-hack","The Best AI Productivity Hack",[651,189579,189580,189581,189586],{},"I want to share a productivity tip that's transformed my AI workflow: ",[812,189582,189585],{"href":189583,"rel":189584},"https://www.bytesizedai.dev/p/ai-productivity-hack",[816],"using voice dictation instead of typing",". Rather than spending time crafting elaborate written prompts for tools like ChatGPT and Claude, I use my computer's built-in dictation feature (activated with a simple keyboard shortcut) to speak naturally to AI models.",[651,189588,189589],{},"The setup is straightforward – enable dictation in your system settings, choose a microphone, and assign a keyboard shortcut. For best results, use a quality microphone and structure your thoughts before speaking. The real power comes from treating AI interactions like conversations, iterating through dialogue rather than trying to craft perfect prompts.",[651,189591,189592],{},"I find this especially useful for content creation, technical documentation, and problem-solving discussions. The best part? It works anywhere there's a text input box, making it a universal tool for faster, more natural AI interactions. This simple change has saved me countless hours and made working with AI feel as natural as talking to a colleague.",[5988,189594],{"id":189595},"bexmnv-l8ps",[4542,189597,189599],{"id":189598},"intellij-tips-and-tricks-with-siva","IntelliJ Tips and Tricks with Siva",[651,189601,189602,189603,189607],{},"It was great to chat with Siva Reddy, Developer Advocate at JetBrains last week on ",[812,189604,97345],{"href":189605,"rel":189606},"https://spring-office-hours.transistor.fm/episodes/s3e42-intellij-tips-tricks-with-siva-reddy",[816],". In this episode we talked with Siva about being a Developer Advocate, Spring & IntelliJ. I really enjoyed our conversation and I hope you will too.",[5988,189609],{"id":189610},"i0pokvrl4xc",[4542,189612,189614],{"id":189613},"youtube-thumbnail-designs","YouTube Thumbnail Designs",[651,189616,189617],{},"I have used Adobe XD for designing my YouTube thumbnails for as long as I can remember. I use it because I have a subscription to the Creative Cloud Applications and its always just worked for me. Well Adobe is really no longer updating this software and when I got my new laptop I decided it was time to install and learn Figma.",[651,189619,189620],{},"I am not a designer but I find tools like this are easy to learn and I enjoy using them. The thumbnails I create are nothing special but I do try and make them nice. In macOS you can go into your photos app and find a image and copy the subject so I get a nice headshot of myself without the background and then paste that into my thumbnail. From there I try to use big text with simple backgrounds and add any little touches I can to it.",[651,189622,189623],{},"Here is my current thumbnail design board for some upcoming videos.",[651,189625,189626],{},[660,189627],{"alt":189628,"src":189629},"Figma","/images/newsletter/2024/12/16/figma.png",[651,189631,189632],{},[2939,189633,187762],{},[651,189635,189636],{},"Chat GPT introduced projects last week 🤩",[187764,189638],{"id":189505},[651,189640,189641],{},"I am going to be working on more Spring AI and Google Gemini videos in the near future.",[187764,189643],{"id":189505},[651,189645,189646],{},[2939,189647,188139],{},[651,189649,188142,189650,664],{},[812,189651,51474],{"href":51472,"rel":189652},[816],[651,189654,187950,189655,69920,189657,189659],{},[41107,189656],{},[41107,189658],{},[812,189660,53869],{"href":53869,"rel":189661},[816],{"title":674,"searchDepth":790,"depth":790,"links":189663},[189664,189665,189666,189667],{"id":189557,"depth":790,"text":189558},{"id":189576,"depth":790,"text":189577},{"id":189598,"depth":790,"text":189599},{"id":189613,"depth":790,"text":189614},{"slug":189669,"date":189670},"scm-ai-productivity-hack","2024-12-16T09:00:00.000Z","/newsletter/2024/12/16/scm-ai-productivity-hack",{"title":189541,"description":189546},"newsletter/2024/12/16/scm-ai-productivity-hack","OQabXDeX6k7z5f-X1WcFTOQTGr8wOvqR2G6hzOBKHxY",{"id":189676,"title":189677,"body":189678,"description":189919,"extension":793,"meta":189920,"navigation":797,"path":189923,"seo":189924,"stem":189925,"__hash__":189926},"content/newsletter/2024/12/23/spring-grpc-preview.md","Spring Security 6.4 Released, JTE Production Guide, and Spring gRPC Preview",{"type":648,"value":189679,"toc":189914},[189680,189688,189692,189699,189702,189710,189713,189716,189719,189723,189726,189729,189734,189749,189754,189762,189770,189773,189776,189780,189787,189792,189795,189800,189817,189822,189836,189841,189859,189864,189878,189881,189885,189888,189891,189894,189897,189901,189905],[651,189681,189682,189683,189687],{},"Happy Monday and welcome to another edition of the newsletter. It’s hard to believe that Christmas is in a few days and then we turn the pages on 2024 and head into the new year. We are hosting a special Spring Office Hours today at 1 PM EDT over on the ",[812,189684,164910],{"href":189685,"rel":189686},"https://www.youtube.com/watch?v=moWjYv7j56s",[816],". In this episode we will review what happened in Java and Spring and why its an exciting time to be a Spring Developer.",[4542,189689,189691],{"id":189690},"spring-security-64-features-interview-with-rob-winch","Spring Security 6.4 Features Interview with Rob Winch",[651,189693,189694,189695,189698],{},"In this week's Spring Office Hours, I sat down with Rob Winch, Spring Security project lead, to explore the latest features in ",[812,189696,189187],{"href":177121,"rel":189697},[816]," (included with Spring Boot 3.4).",[651,189700,189701],{},"Two major highlights from this release:",[5316,189703,189704,189707],{},[5332,189705,189706],{},"One-time token login: A streamlined authentication method allowing users to log in via email-delivered tokens, perfect for applications prioritizing user convenience.",[5332,189708,189709],{},"Passkey support: A modern authentication approach using public-key cryptography and biometrics, offering enhanced security without password management headaches.",[651,189711,189712],{},"Rob also emphasized preparing for Spring Security 7.0 by adopting the Lambda DSL configuration style. While both configuration styles work in 6.4, only Lambda DSL will be supported in 7.0.",[651,189714,189715],{},"Catch the full discussion on Spring Developer YouTube channel or your favorite podcast platform to learn more about these features and implementation details.",[5988,189717],{"id":189718},"FxfrJbdQ0Bo",[4542,189720,189722],{"id":189721},"taking-jte-apps-to-production-with-spring-boot","Taking JTE Apps to Production with Spring Boot",[651,189724,189725],{},"In my latest video, I explore how to deploy Spring Boot applications using Java Template Engine (JTE) to production. If you haven't tried JTE yet, it's a great alternative to Thymeleaf or Mustache that offers pre-compiled templates and better IDE integration for working with Java objects. It's now available right from Spring Initializer!",[651,189727,189728],{},"I ran into some interesting challenges while taking my JTE apps to production, and I wanted to share two key solutions I discovered:",[651,189730,189731],{},[2939,189732,189733],{},"For JAR Deployments:",[5316,189735,189736,189746],{},[5332,189737,189738,189739,23212,189742,189745],{},"You'll need to set ",[676,189740,189741],{},"jte.development-mode=false",[676,189743,189744],{},"jte.use-precompiled-templates=true"," in your production properties",[5332,189747,189748],{},"I show how to use Spring profiles to keep your dev and prod configurations separate",[651,189750,189751],{},[2939,189752,189753],{},"For Native Images with GraalVM:",[5316,189755,189756,189759],{},[5332,189757,189758],{},"The trick is properly configuring runtime hints for template resolution",[5332,189760,189761],{},"I demonstrate the exact implementation needed to get your templates working in native images",[651,189763,189764,189765,664],{},"I walk through everything using a simple todo application, and you can find all the code on my GitHub at ",[812,189766,189769],{"href":189767,"rel":189768},"https://github.com/danvega/jte-production",[816],"github.com/danvega/jte-production",[651,189771,189772],{},"Whether you're just getting started with JTE or looking to deploy existing applications, these solutions should help you avoid the common pitfalls I encountered. Check out the full video for the step-by-step walkthrough!",[5988,189774],{"id":189775},"DuVxoVc_vD4",[4542,189777,189779],{"id":189778},"spring-grpc","Spring gRPC",[651,189781,189782,189783,664],{},"If you haven’t noticed there is a new starter (currently experimental) on the Spring Initializr for ",[812,189784,189779],{"href":189785,"rel":189786},"https://github.com/spring-projects-experimental/spring-grpc",[816],[651,189788,189789],{},[660,189790],{"alt":189779,"src":189791},"/images/newsletter/2024/12/22/spring-grpc.png",[651,189793,189794],{},"Spring gRPC is a new project that brings gRPC development into the Spring ecosystem, making it easier to build gRPC applications using familiar Spring patterns and conventions. Here are the key points:",[27665,189796,189797],{},[5332,189798,189799],{},"Core Features:",[5316,189801,189802,189805,189808,189811,189814],{},[5332,189803,189804],{},"Spring-friendly API and abstractions for gRPC development",[5332,189806,189807],{},"Spring Boot starter with Auto Configuration support",[5332,189809,189810],{},"Dependency injection integration",[5332,189812,189813],{},"Support for both client and server implementations",[5332,189815,189816],{},"Native image support for GraalVM",[27665,189818,189819],{"start":790},[5332,189820,189821],{},"Getting Started:",[5316,189823,189824,189827,189830,189833],{},[5332,189825,189826],{},"Can be initialized through Spring Initializr",[5332,189828,189829],{},"Uses standard protobuf definitions for service contracts",[5332,189831,189832],{},"Works with both Maven and Gradle build systems",[5332,189834,189835],{},"Default server port is 9090",[27665,189837,189838],{"start":892},[5332,189839,189840],{},"Key Components:",[5316,189842,189843,189849,189856],{},[5332,189844,189845,189846,189848],{},"Server side: Uses ",[676,189847,12308],{}," annotations with gRPC generated base classes",[5332,189850,189851,189852,189855],{},"Client side: Provides ",[676,189853,189854],{},"GrpcChannelFactory"," for creating client connections",[5332,189857,189858],{},"Configuration can be done through standard Spring properties files",[27665,189860,189861],{"start":901},[5332,189862,189863],{},"Notable Benefits:",[5316,189865,189866,189869,189872,189875],{},[5332,189867,189868],{},"Simplifies gRPC integration in Spring applications",[5332,189870,189871],{},"Provides Spring Boot-style autoconfiguration",[5332,189873,189874],{},"Supports Spring Boot 3.4.x",[5332,189876,189877],{},"Offers configuration externalization through properties",[651,189879,189880],{},"It essentially brings the \"Spring way\" of doing things to gRPC development, making it more accessible to Spring developers while maintaining gRPC's powerful features.",[651,189882,189883],{},[2939,189884,187762],{},[651,189886,189887],{},"The story about Google’s new chip Willow is wild and I feel like nobody is even talking about it 🤯",[187764,189889],{"id":189890},"1870174887794684092",[651,189892,189893],{},"I am currently working on the career management chapter of my upcoming book “Fundamentals of Software Engineering”. While I was putting all my thoughts together I did a little self reflection on my career. A good reminder that it’s hard to see a forest through the trees but everything on my path happened for a reason.",[187764,189895],{"id":189896},"1869578954706940131",[651,189898,189899],{},[2939,189900,188139],{},[651,189902,188142,189903,188450],{},[812,189904,51474],{"href":674},[651,189906,187950,189907,69920,189909,189911],{},[41107,189908],{},[41107,189910],{},[812,189912,53869],{"href":53869,"rel":189913},[816],{"title":674,"searchDepth":790,"depth":790,"links":189915},[189916,189917,189918],{"id":189690,"depth":790,"text":189691},{"id":189721,"depth":790,"text":189722},{"id":189778,"depth":790,"text":189779},"Happy Monday and welcome to another edition of the newsletter. It’s hard to believe that Christmas is in a few days and then we turn the pages on 2024 and head into the new year. We are hosting a special Spring Office Hours today at 1 PM EDT over on the Spring Developer YouTube Channel. In this episode we will review what happened in Java and Spring and why its an exciting time to be a Spring Developer.",{"slug":189921,"date":189922},"spring-grpc-preview","2024-12-23T07:00:00.000Z","/newsletter/2024/12/23/spring-grpc-preview",{"title":189677,"description":189919},"newsletter/2024/12/23/spring-grpc-preview","HFQl_XswIuqdLc4tfp4xFnQVrrHa8sbjZt2Go2gkmuk",{"id":189928,"title":189929,"body":189930,"description":189934,"extension":793,"meta":190027,"navigation":797,"path":190030,"seo":190031,"stem":190032,"__hash__":190033},"content/newsletter/2025/01/13/codemash-2025-preview.md","CodeMash 2025 and a new way to bootstrap your Spring applications 🔥",{"type":648,"value":189931,"toc":190022},[189932,189935,189944,189951,189954,189956,189961,189964,189967,189970,189973,189977,189983,189986,189994,189997,190006,190008,190013],[651,189933,189934],{},"Happy Monday and welcome to another edition of the newsletter. I’m attending and speaking at my first conference of the new year this week and I’m excited to kick off 2025. This week marks 3 years at VMware / Broadcom for me which means I am heading into year number 4 of my dream job.",[651,189936,189937,189938,189943],{},"I ",[812,189939,189942],{"href":189940,"rel":189941},"https://www.danvega.dev/blog/im-joining-vmware",[816],"published a blog post"," 3 years ago about my journey to VMware and how excited I was to be joining this amazing team. A lot has changed since then but I’m so grateful to still be here and doing what I love every single day. I’m currently writing a chapter on career management for my upcoming book “Fundamentals of Software Engineering” and I put I quote in there that I love",[1004,189945,189946,189947],{},"\n\"The only way to do great work is to love what you do. If you haven't found it yet, keep looking. Don't settle.\"\n",[5316,189948,189949],{},[5332,189950,187810],{},[651,189952,189953],{},"I wholeheartedly believe this: while we sometimes need to take jobs out of necessity, we should never lose sight of our dreams. If you have a career goal, make sure you're working toward it every single day. I'm truly blessed to be doing what I love, and I'm looking forward to starting my fourth year in this position in 2025.",[4542,189955,97312],{"id":177852},[651,189957,189958],{},[660,189959],{"alt":97312,"src":189960},"/images/newsletter/2025/01/13/codemash.jpeg",[651,189962,189963],{},"CodeMash 2025 in beautiful Sandusky Ohio is taking place this week, and I’m looking forward to being back there. This is my 3rd time attending and speaking at this conference and it should be another great event.",[651,189965,189966],{},"This year I am speaking on building GraphQL APIs in Java but I’m putting a bit of a wrinkle on this years presentation. I have given many versions of this presentation that have all included a large number of slides. This year I have decided to cut my slides down to 3. We are going to spend the full hour live coding building out GraphQL APIs in Java and Spring and exploring all of the features of Spring for GraphQL.",[651,189968,189969],{},"This conference has a wide variety of tracks / languages and while it isn’t a huge Java conference there are going to be some really good Java talks. I will check out as many as I can and tell you about them next week. I’m also really excited to catch up with my friends Chris Judd and Nate Schutta.",[651,189971,189972],{},"It should be a great week and if you will be there please say hi 👋🏻",[4542,189974,189976],{"id":189975},"spring-initializr-on-raycast","Spring Initializr on Raycast",[651,189978,189979],{},[660,189980],{"alt":189981,"src":189982},"Raycast Extension","/images/newsletter/2025/01/13/spring_init_raycast_extension.png",[651,189984,189985],{},"One of my favorite Mac apps just got better—thanks to a little help from me! If you're on a Mac and haven't heard of Raycast, it's an application that replaces Spotlight and serves as your shortcut for everything. I use it to quickly launch apps, and it has an amazing set of extensions for doing pretty much anything.",[651,189987,189988,189989,189993],{},"Anyone can build these extensions by checking out the Raycast ",[812,189990,40844],{"href":189991,"rel":189992},"https://developers.raycast.com/basics/create-your-first-extension",[816],". Despite my limited experience with React, I figured I could build my first extension with the help of AI.",[651,189995,189996],{},"I wanted to create an extension that would let you build a new Spring Boot Application directly from Raycast. The web interface and IDE extensions all pull their metadata from a single endpoint, which meant I could fetch the API data without needing to constantly update the extension.",[651,189998,189999,190000,190005],{},"I'm happy to announce that this extension is now available in the ",[812,190001,190004],{"href":190002,"rel":190003},"https://www.raycast.com/danvega/spring-initializr",[816],"Raycast Store",". Please give it a try—I'd love your feedback.",[4542,190007,188139],{"id":157703},[651,190009,188142,190010,188450],{},[812,190011,51474],{"href":188145,"rel":190012},[816],[651,190014,187950,190015,69920,190017,190019],{},[41107,190016],{},[41107,190018],{},[812,190020,53869],{"href":53869,"rel":190021},[816],{"title":674,"searchDepth":790,"depth":790,"links":190023},[190024,190025,190026],{"id":177852,"depth":790,"text":97312},{"id":189975,"depth":790,"text":189976},{"id":157703,"depth":790,"text":188139},{"slug":190028,"date":190029},"codemash-2025-preview","2025-01-13T07:00:00.000Z","/newsletter/2025/01/13/codemash-2025-preview",{"title":189929,"description":189934},"newsletter/2025/01/13/codemash-2025-preview","twi79SGwvQTXTxDuuHd_m9hNo_68oS_djo7NL1ngBso",{"id":190035,"title":190036,"body":190037,"description":190171,"extension":793,"meta":190172,"navigation":797,"path":190174,"seo":190175,"stem":190176,"__hash__":190177},"content/newsletter/2025/01/21/java-champions-conference.md","Java Champions Conference, CodeMash 2025 Recap, and AI Agents in Spring",{"type":648,"value":190038,"toc":190166},[190039,190046,190049,190052,190055,190057,190064,190067,190090,190094,190097,190100,190103,190108,190116,190120,190127,190136,190145,190148,190152,190157],[651,190040,190041,190042,190045],{},"Happy ",[104514,190043,190044],{},"Monday"," Tuesday and welcome to another edition of the newsletter. I was off yesterday as we observed Martin Luther King Jr so this is coming to you a day later this week.",[651,190047,190048],{},"If you don’t know I live outside of Cleveland OH and this area of the world is experiencing some real cold weather right now. As I am writing this on Monday afternoon it is currently 10º Fahrenheit with a low of 5º. It is so cold around here that they have preemptively canceled school for the kids for the next 2 days.",[651,190050,190051],{},"Given that I will be staying in and catching up on a lot of things. It’s been a busy start to the new year but I feel like I am finally in a place to start crossing some things off my list. To start I have a backlog of about 10 videos that I’m ready to record and I am going to start tackling those this week.",[651,190053,190054],{},"I also am beginning to plan out a couple of courses for the 1st part of the year including a full course on Spring AI. If there is anything you would like to see specifically from me I’m always welcome to feedback and suggestions.",[4542,190056,168305],{"id":168304},[651,190058,190059,190060,190063],{},"This week on Thursday 1/23/25 the ",[812,190061,168305],{"href":168310,"rel":190062},[816]," kicks off for free virtually. This is a 4-day conference where all talks are given by Java Champions. I have had a chance to attend (virtually) past conferences, and they were always packed full of amazing content.",[651,190065,190066],{},"This year's lineup is no different and I’m really looking forward to it. Here are a few of the talks I will be locked in on.",[5316,190068,190069,190076,190083],{},[5332,190070,190071],{},[812,190072,190075],{"href":190073,"rel":190074},"https://www.youtube.com/watch?v=eiFnSevxAdk",[816],"It Takes Two to Tango – Designing Module Interactions in Modulithic Spring Applications - Oliver Drotbohm",[5332,190077,190078],{},[812,190079,190082],{"href":190080,"rel":190081},"https://www.youtube.com/watch?v=fmToimoSF4s",[816],"A Data-Oriented Programming Approach to REST APIs - Ken Kousen",[5332,190084,190085],{},[812,190086,190089],{"href":190087,"rel":190088},"https://www.youtube.com/watch?v=ChEBO4d2U0o",[816],"How my views on teaching Java have changed - Kenneth Fogel",[4542,190091,190093],{"id":190092},"codemash-2025-recap","CodeMash 2025 Recap",[651,190095,190096],{},"Last week was CodeMash right up the road from me in Sandusky OH. I believe this is my 3rd time attending and speaking at this wonderful conference. I usually take the family with me so the kids can enjoy the water park. We decided to skip that this year though because they were already sick for a week and didn’t want them to get sick again and miss more school.",[651,190098,190099],{},"It was another amazing conference and I particularly enjoyed catching up with my good friends Nate Schutta, Todd Sharp and Chris Judd. We had some time to chat about work, life and tech in general, and it is always great seeing them.",[651,190101,190102],{},"I gave a talk on building your first GraphQL API in Java using Spring for GraphQL. I have given a similar talk before and in fact I gave this talk last year at CodeMash. Knowing that I decided to switch it up this year and only came to the conference with 4 slides and decided I would do an hour of live coding.",[651,190104,190105],{},[660,190106],{"alt":187912,"src":190107},"/images/newsletter/2025/01/21/codemash_2025.jpeg",[651,190109,190110,190111,664],{},"I thought it went really well, and it was pretty well attended. So thank you everyone who decided to spend an hour with me and I hope you learned something new. If you want to see the code we went through you can check it out on this ",[812,190112,190115],{"href":190113,"rel":190114},"https://github.com/danvega/graphql-books",[816],"GitHub Repo",[4542,190117,190119],{"id":190118},"building-effective-agents-with-spring-ai","Building Effective Agents with Spring AI",[651,190121,190122,190123,190126],{},"If you have been paying attention to the world of AI all the buzz heading into 2025 is about AI Agents. If you’re curious about the basics of agents I wrote an article for my other newsletter ",[812,190124,187509],{"href":189308,"rel":190125},[816]," where I cover the fundamentals of AI agents.",[651,190128,190129,190130,190135],{},"In December 2024 Anthropic came out with a really great blog post on ",[812,190131,190134],{"href":190132,"rel":190133},"https://www.anthropic.com/research/building-effective-agents",[816],"how to build effective agents",". In this post, they share what they have learned from working with their customers and building agents themselves, and give practical advice for developers on how to build effective agents.",[651,190137,190138,190139,190144],{},"As a bit of a follow-up to this article Christian Tzolov (from the Spring AI team) just released an article “",[812,190140,190143],{"href":190141,"rel":190142},"https://spring.io/blog/2025/01/21/spring-ai-agentic-patterns",[816],"Building Effective Agents with Spring AI - Part 1","” where he takes the lessons learned from the Anthropic blog post and applies them to Spring AI.",[651,190146,190147],{},"If you’re interested in building AI Agents in Java, and why wouldn’t you be this is a really great place to start. I’m going to be diving into voice assistants, Model Context Protocol (MCP) and AI agents in depth in 2025 so if that is something you’re interested in, stay tuned!",[651,190149,190150],{},[2939,190151,188139],{},[651,190153,188142,190154,188450],{},[812,190155,51474],{"href":51472,"rel":190156},[816],[651,190158,187950,190159,69920,190161,190163],{},[41107,190160],{},[41107,190162],{},[812,190164,53869],{"href":82688,"rel":190165},[816],{"title":674,"searchDepth":790,"depth":790,"links":190167},[190168,190169,190170],{"id":168304,"depth":790,"text":168305},{"id":190092,"depth":790,"text":190093},{"id":190118,"depth":790,"text":190119},"Happy Monday Tuesday and welcome to another edition of the newsletter. I was off yesterday as we observed Martin Luther King Jr so this is coming to you a day later this week.",{"slug":168304,"date":190173},"2025-01-21T07:00:00.000Z","/newsletter/2025/01/21/java-champions-conference",{"title":190036,"description":190171},"newsletter/2025/01/21/java-champions-conference","qzV_WRRRp5467v-sUV_op_F_8thCilVNW3UpfV66NKg",{"id":190179,"title":190180,"body":190181,"description":190185,"extension":793,"meta":190404,"navigation":797,"path":190407,"seo":190408,"stem":190409,"__hash__":190410},"content/newsletter/2025/01/27/google-ai-tools.md","Making sense of Google's AI Tools and new YouTube videos",{"type":648,"value":190182,"toc":190394},[190183,190186,190189,190192,190195,190198,190202,190210,190213,190224,190227,190238,190241,190244,190248,190251,190255,190258,190261,190264,190267,190271,190274,190277,190280,190283,190287,190290,190293,190296,190299,190302,190306,190309,190312,190315,190318,190321,190325,190328,190331,190334,190337,190340,190344,190347,190350,190364,190367,190370,190373,190376,190380,190385,190387,190389],[651,190184,190185],{},"Happy Monday and welcome to another edition of the newsletter. I had such a busy month of December and beginning of January that I’m feeling like my new year is finally underway. I was able to get back to creating videos for YouTube last week. I was able to record 7 and publish 6 which has to be some sort of record for me. No, I can not keep up that pace but it’s nice to know that once in awhile I can create a bunch of them.",[651,190187,190188],{},"I was tinkering around with the idea of hiring an editor because that would help me produce more videos but after doing a little looking around I realized that I just can’t afford one right now. Don’t get me wrong, they are totally worth it but I just can’t justify it right now when the channel doesn’t really bring in any revenue.",[651,190190,190191],{},"After seeing some examples of edits to educational content I realized I wanted to try and make my videos a little bit more engaging. I started playing around with some different effects and transitions last week and I hope they are helping with your viewing experience.",[651,190193,190194],{},"In this week's newsletter I want to tell you about some of the videos I made last week. I also want to discuss an article I wrote last week for the ByteSized AI Newsletter on Google Gemini. This week I will focused on making more videos and this week I will be getting back into the AI space.",[651,190196,190197],{},"I hope you had a wonderful weekend and are ready to take on the new week.",[4542,190199,190201],{"id":190200},"google-ai-tools-explained","Google AI Tools Explained",[651,190203,40060,190204,190209],{},[812,190205,190208],{"href":190206,"rel":190207},"https://www.bytesizedai.dev/p/googles-ai-tools",[816],"article explains"," how Google has evolved beyond a search engine to offer three main AI tools powered by their Gemini language model: Google AI Studio, Vertex AI, and Google Gemini. You introduce Gemini as Google DeepMind's multimodal LLM released in December 2023, highlighting its ability to handle text, images, audio, and video while maintaining context over long conversations.",[651,190211,190212],{},"For each tool, you break down their distinct purposes:",[27665,190214,190215,190218,190221],{},[5332,190216,190217],{},"Google AI Studio: Positioned as a free \"AI laboratory\" for experimentation, featuring a user-friendly interface and example prompts. It's ideal for developers, students, and researchers who want to test AI capabilities.",[5332,190219,190220],{},"Vertex AI: Described as the \"enterprise powerhouse\" offering comprehensive AI solution building and deployment at scale. It includes advanced model training, enterprise-grade security, and Google Cloud integration.",[5332,190222,190223],{},"Google Gemini: The consumer-facing version that integrates AI capabilities into Google Workspace products, making it accessible for everyday use.",[651,190225,190226],{},"You effectively help readers choose between these tools by providing a comparison framework and a simple decision tree:",[5316,190228,190229,190232,190235],{},[5332,190230,190231],{},"Beginners should start with AI Studio",[5332,190233,190234],{},"Enterprise needs point to Vertex AI",[5332,190236,190237],{},"Those wanting integrated AI features should use Gemini",[651,190239,190240],{},"The article strikes a good balance between technical information and accessibility, using clear analogies and \"Quick Tips\" to help readers understand complex concepts. You also include a relevant call-to-action at the end, encouraging readers to start with small projects and consider how AI could benefit their work.",[651,190242,190243],{},"The article successfully achieves its goal of demystifying Google's AI offerings for a general audience while providing enough technical detail to be useful for more advanced readers.",[4542,190245,190247],{"id":190246},"new-youtube-videos","New YouTube Videos",[651,190249,190250],{},"Here are the videos I published last week:",[4542,190252,190254],{"id":190253},"building-a-spring-initializer-extension-for-raycast-a-developers-journey","Building a Spring Initializer Extension for Raycast: A Developer's Journey\"",[651,190256,190257],{},"I recently shared one of my favorite Mac productivity tools, Raycast, and a new extension I built for it. As a developer who values keyboard-driven workflows, I've found Raycast to be an incredible replacement for Spotlight, offering everything from clipboard history to window management.",[651,190259,190260],{},"The exciting part? I just launched my own Raycast extension for Spring Initializer. Now you can create Spring Boot projects right from your launcher - no browser needed. What's particularly interesting is how I built it: despite my limited React experience, I leveraged AI to help translate my TypeScript knowledge into a working React extension in just a few days.",[651,190262,190263],{},"For Mac users interested in supercharging their workflow, you can find my extension in the Raycast store under \"spring-init.\" And yes, Windows users, Raycast is coming your way soon!",[5988,190265],{"id":190266},"NIVBxoK-APU",[4542,190268,190270],{"id":190269},"spring-configuration-mixing-xml-annotation-configuration","Spring Configuration: Mixing XML & Annotation Configuration",[651,190272,190273],{},"In this video, I tackled a great question from our Spring Office Hours podcast about mixing XML and annotation-based configuration in Spring Boot applications.",[651,190275,190276],{},"While I personally prefer annotation-based configuration for new projects (it's cleaner and more intuitive), I wanted to demonstrate why there's nothing wrong with using both approaches in the same application. Through a practical example, I showed how to configure beans using both XML and annotations, and how they can peacefully coexist in the same project.",[651,190278,190279],{},"The key takeaway? If you're working with a legacy Spring application that uses XML configuration, there's no need to rush into converting everything to annotations. Focus your time on delivering value rather than rewriting working code just for the sake of modernization.",[5988,190281],{"id":190282},"6arSdLciC_k",[4542,190284,190286],{"id":190285},"graphql-performance-improvements","GraphQL Performance Improvements",[651,190288,190289],{},"In a recent video, I shared crucial performance optimizations for GraphQL APIs in Java and Spring, focusing on two key issues I recently presented at CodeMash 2025.",[651,190291,190292],{},"The first is the notorious N+1 problem - when you fetch a list of authors and then make separate database calls for each author's books. I demonstrated two solutions: using a JPA join fetch query and leveraging Spring for GraphQL's @BatchMapping annotation to consolidate multiple calls into one efficient request.",[651,190294,190295],{},"The second optimization involves virtual threads in Spring Boot 3.2 and JDK21. I showed how enabling virtual threads can significantly improve performance when making multiple concurrent calls. I also emphasized the importance of observability tools like Zipkin for identifying these performance bottlenecks in production environments.",[651,190297,190298],{},"These optimizations made a huge difference - turning what could be hundreds of separate database calls into a single efficient query, while maintaining clean, maintainable code.",[5988,190300],{"id":190301},"6oOKpVn4Qqg",[4542,190303,190305],{"id":190304},"debugging-spring-boot-api-integration-issues","Debugging Spring Boot API Integration Issues",[651,190307,190308],{},"In a recent video, I addressed a common challenge developers face when integrating with external APIs - handling JSON responses and converting them into usable Java objects.",[651,190310,190311],{},"Using a real-world example with the Polygon.io API, I demonstrated a systematic approach to troubleshooting integration issues. First, I showed how to properly model the JSON response using Java records, using @JsonProperty annotations to map abbreviated JSON field names (like \"T\" and \"C\") to meaningful Java variables (like \"ticker\" and \"close\").",[651,190313,190314],{},"The key insight came when tackling the HTTP client error many developers encounter: \"no suitable HTTP message converter found.\" The solution wasn't in the JSON mapping, but in properly setting the Accept header to expect JSON responses. By walking through both the data modeling and HTTP client configuration, I showed how to build robust API integrations in Spring Boot applications.",[651,190316,190317],{},"This tutorial highlighted important practices like writing focused unit tests for JSON deserialization and using Spring Boot's REST client effectively, skills that are valuable for any developer working with external APIs.",[5988,190319],{"id":190320},"-iO9HkuSelo",[4542,190322,190324],{"id":190323},"sending-email-in-java-with-sendgrid","Sending email in Java with SendGrid",[651,190326,190327],{},"In a this video, I walked through integrating SendGrid's email service into Java applications - a powerful alternative to the traditional Java Mail API.",[651,190329,190330],{},"I demonstrated how SendGrid offers several key advantages: better email deliverability rates, comprehensive analytics, and a modern REST API with robust SDKs. The implementation is straightforward - after adding the SendGrid dependency to your Maven project, you can create an email service class that handles all the sending logic.",[651,190332,190333],{},"Using practical code examples, I showed how to set up the SendGrid client, construct email requests, and handle responses. The real power comes from SendGrid's additional features like delivery tracking, spam handling, and email templates, making it a more complete solution for production applications.",[651,190335,190336],{},"While SendGrid is a paid service, they offer a free tier that's perfect for testing. This makes it an excellent option for developers who need reliable email delivery and detailed analytics in their Java applications.",[5988,190338],{"id":190339},"i8Hvvo4ZITg",[4542,190341,190343],{"id":190342},"how-i-built-a-tool-to-automatically-generate-documentation","How I built a tool to automatically generate documentation",[651,190345,190346],{},"In my final video of the week, I shared a practical tool I built to solve a common developer pain point - generating documentation for GitHub repositories. The tool, called \"repo-content-generator,\" streamlines the process of creating README files by combining repository analysis with AI assistance.",[651,190348,190349],{},"Here's how it works:",[27665,190351,190352,190355,190358,190361],{},[5332,190353,190354],{},"The application (built with Spring Boot) scans either a GitHub repository or local directory",[5332,190356,190357],{},"It intelligently filters content based on configurable include/exclude patterns",[5332,190359,190360],{},"It generates a single consolidated file containing relevant code and configuration",[5332,190362,190363],{},"This content can then be fed to an LLM (I use Claude) with specific documentation generation instructions",[651,190365,190366],{},"The key innovation is the workflow - rather than manually writing documentation or asking an LLM to directly analyze a repository, this tool creates a curated snapshot that can be used to generate accurate, well-structured documentation.",[651,190368,190369],{},"I demonstrated both the practical usage and technical implementation, including handling GitHub's API, recursive file traversal, and configurable content filtering. While I currently use it with Claude, the output can be used with any LLM that supports structured documentation generation.",[651,190371,190372],{},"The solution isn't just about automation - it's about creating consistent, comprehensive documentation even for small demo projects, saving valuable development time while maintaining quality.",[5988,190374],{"id":190375},"QYchuz6nBR8",[651,190377,190378],{},[2939,190379,188139],{},[651,190381,188142,190382,188450],{},[812,190383,51474],{"href":51472,"rel":190384},[816],[651,190386,187950],{},[651,190388,798],{},[651,190390,190391],{},[812,190392,53869],{"href":82688,"rel":190393},[816],{"title":674,"searchDepth":790,"depth":790,"links":190395},[190396,190397,190398,190399,190400,190401,190402,190403],{"id":190200,"depth":790,"text":190201},{"id":190246,"depth":790,"text":190247},{"id":190253,"depth":790,"text":190254},{"id":190269,"depth":790,"text":190270},{"id":190285,"depth":790,"text":190286},{"id":190304,"depth":790,"text":190305},{"id":190323,"depth":790,"text":190324},{"id":190342,"depth":790,"text":190343},{"slug":190405,"date":190406},"google-ai-tools","2025-01-27T07:00:00.000Z","/newsletter/2025/01/27/google-ai-tools",{"title":190180,"description":190185},"newsletter/2025/01/27/google-ai-tools","3UUlus34xWBDVBBnBArWIE8jodlvTyNlNRrtjJN5SKg",{"id":190412,"title":190413,"body":190414,"description":190418,"extension":793,"meta":190648,"navigation":797,"path":190651,"seo":190652,"stem":190653,"__hash__":190654},"content/newsletter/2025/02/03/deepseek-java-challengers.md","DeepSeek takes the internet by storm, Java Challengers Podcast & Conference Season",{"type":648,"value":190415,"toc":190635},[190416,190419,190422,190425,190429,190432,190436,190439,190453,190456,190460,190463,190466,190469,190473,190476,190479,190490,190493,190504,190507,190518,190521,190532,190535,190549,190552,190555,190557,190564,190566,190569,190585,190588,190595,190598,190606,190610,190617,190621,190626],[651,190417,190418],{},"Happy Monday and welcome to another edition of the newsletter. I sat down to start writing this on Friday which happens to be the last day of January. I sent out a tweet congratulating people on making it to January 87th because that what it feels like. January just feel like the longest month ever when you’re in the dead of winter. We have now turned the calendar to February, the shortest month of the year as we make the march towards Spring!",[651,190420,190421],{},"Last week you couldn’t throw a rock without hitting something that was talking about DeepSeek. This is the new model from the Chinese Artificial Intelligence company that was reportedly trained at a fraction of a cost as some of the frontier models. That very claim has come with some scrutiny but regardless DeepSeek tok over the world last week.",[651,190423,190424],{},"In this episode of the newsletter I will tell you a little bit more about DeepSeek and some of the videos I published on the topic last week. I was also a guest on the Java Challengers podcast and can’t wait to tell you about it.",[4542,190426,190428],{"id":190427},"deepseek","DeepSeek",[651,190430,190431],{},"DeepSeek is a Chinese AI company founded in 2023 by Liang Wenfeng and backed by the High-Flyer hedge fund. It focuses on developing open‐source large language models that are both cost‑efficient and high‑performance. Notably, its flagship model, DeepSeek‑R1, emphasizes advanced reasoning and logical inference, and its rapid rise—quickly overtaking ChatGPT as the top free app on the U.S. iOS App Store—has stirred both the global AI community and financial markets. In essence, DeepSeek is reshaping the AI landscape by delivering powerful, open‑source solutions at a fraction of the cost of many Western rivals.",[5909,190433,190435],{"id":190434},"deepseek-introduction","DeepSeek Introduction",[651,190437,190438],{},"DeepSeek unveiled its R1 model in January 2025, achieving performance comparable to leading language models while requiring significantly less computing power and cost. The model is fully open source under an MIT license, allowing unrestricted commercial use and modification. Benchmarks demonstrate R1's capabilities across mathematical reasoning, coding, and scientific knowledge, matching or exceeding GPT-4's performance on standard tests like MMLU and SWE Bench.",[651,190440,190441,190442,190447,190448],{},"The implementation offers multiple deployment options: a hosted API service with competitive pricing (14¢/million input tokens), chat interface at ",[812,190443,190446],{"href":190444,"rel":190445},"https://chat.deepseek.com/",[816],"chat.deepseek.com",", and local installation through Ollama. For developers wanting to run R1 locally, DeepSeek provides distilled versions ranging from 1.5B to 70B parameters. The video demonstrates setting up a local environment using Ollama and Open WebUI, creating a ChatGPT-like interface for the model while keeping all data and processing on your machine. GitHub: ",[812,190449,190452],{"href":190450,"rel":190451},"https://github.com/deepseek-ai/DeepSeek-LLM",[816],"github.com/deepseek-ai/DeepSeek-LLM",[5988,190454],{"id":190455},"DDjHLQKtV-k",[5909,190457,190459],{"id":190458},"deepseek-integration-with-java-spring","DeepSeek Integration with Java & Spring",[651,190461,190462],{},"DeepSeek's R1 reasoning model can now be integrated into Java and Spring applications through two implementation approaches. The first uses Java's native HTTP client to interact with DeepSeek's REST API, which follows OpenAI's API specification. The second leverages Spring AI's framework, offering a unified abstraction layer that simplifies AI integration and supports both cloud API and local model deployment.",[651,190464,190465],{},"The Spring implementation introduces key advantages like streaming responses and multi-provider support through Spring AI's ChatClient interface. Notably, developers can run smaller R1 models (7B parameters) locally using Ollama integration, offering faster response times compared to the cloud API. The tutorial demonstrates proper configuration for both remote and local deployments, including API authentication, model selection, and streaming response handling in a production environment.",[5988,190467],{"id":190468},"TWlBGA3x3cQ",[4542,190470,190472],{"id":190471},"java-challengers","Java Challengers",[651,190474,190475],{},"I was grateful to be invited on The Java Challengers podcast to speak with Rafael about my 20+ year journey in software development. Our wide-ranging conversation covered several key themes:",[651,190477,190478],{},"Career Development:",[5316,190480,190481,190484,190487],{},[5332,190482,190483],{},"Started at a two-year technical college, moved to San Francisco for a startup, and progressed through various roles including curriculum developer and Spring Developer Advocate",[5332,190485,190486],{},"Emphasized the importance of community involvement and networking in career growth",[5332,190488,190489],{},"Discussed the path to becoming a Java Champion through consistent community contributions",[651,190491,190492],{},"Technical Leadership:",[5316,190494,190495,190498,190501],{},[5332,190496,190497],{},"Defined characteristics of senior developers: strong problem-solving skills, deep technical knowledge, and mentorship abilities",[5332,190499,190500],{},"Highlighted the importance of reading code vs writing code",[5332,190502,190503],{},"Stressed understanding core Java fundamentals, concurrency, and distributed systems",[651,190505,190506],{},"Learning and Growth:",[5316,190508,190509,190512,190515],{},[5332,190510,190511],{},"Recommended prioritizing learning based on community needs and personal interests",[5332,190513,190514],{},"Suggested blocking dedicated time for learning, even during work hours when appropriate",[5332,190516,190517],{},"Emphasized consistency over intensity in learning new technologies",[651,190519,190520],{},"Professional Development:",[5316,190522,190523,190526,190529],{},[5332,190524,190525],{},"Discussed strategies for handling imposter syndrome",[5332,190527,190528],{},"Highlighted the value of embracing failure as a learning opportunity",[5332,190530,190531],{},"Emphasized the importance of empathy and collaboration in software development",[651,190533,190534],{},"Content Creation:",[5316,190536,190537,190540,190543],{},[5332,190538,190539],{},"Shared experiences running a successful YouTube channel focused on Spring",[5332,190541,190542],{},"Discussed the Spring Office Hours podcast",[5332,190544,190545,190546],{},"Talked about our upcoming book ",[812,190547,187488],{"href":187486,"rel":190548},[816],[5988,190550],{"id":190551},"XNZF-kgEMG4",[651,190553,190554],{},"The conversation provided practical advice for developers at all stages of their careers, emphasizing the importance of continuous learning, community involvement, and maintaining a growth mindset.",[4542,190556,183433],{"id":183432},[651,190558,190559,190560,190563],{},"We are getting closer to conference season and I’m getting really excited about some of the events I get to attend. As always if you want to see where I will be speaking next you can check out the ",[812,190561,120451],{"href":120449,"rel":190562},[816]," on my website for an up to date list.",[5909,190565,185177],{"id":185176},[651,190567,190568],{},"This conference is happening at the end of the month in Montreal CA. I am really looking forward to returning to this wonderful conference after last year which was my first time there. I will be giving 2 talks on building intell",[5316,190570,190571,190578],{},[5332,190572,190573],{},[812,190574,190577],{"href":190575,"rel":190576},"https://confoo.ca/en/2025/session/code-smarter-not-harder-ai-powered-dev-hacks-for-all",[816],"Code Smarter, Not Harder: AI-Powered Dev Hacks for All",[5332,190579,190580],{},[812,190581,190584],{"href":190582,"rel":190583},"https://confoo.ca/en/2025/session/spring-into-ai-building-intelligent-apps-with-spring-ai",[816],"Spring into AI: Building Intelligent apps with Spring AI",[5909,190586,97321],{"id":190587},"devnexus",[651,190589,190590,190591,664],{},"I missed last years DevNexus so I am extra excited about this years event. I’ll be speaking, working the booth, helping to run a happy hour and whatever else I can get involved in. I put together a little video talking about my session at DevNexus that you can check out ",[812,190592,18263],{"href":190593,"rel":190594},"https://www.linkedin.com/posts/vmware-tanzu_devnexus-activity-7291189292171698176-DNFy",[816],[5909,190596,188104],{"id":190597},"javaone",[651,190599,190600,190601,190605],{},"I’m so excited to attend and speak at my very first JavaOne conference in March. The early bird pricing is going up soon so make sure you register for ",[812,190602,190604],{"href":188102,"rel":190603},[816],"this amazing conference"," ASAP.",[5909,190607,190609],{"id":190608},"microsoft-jdconf","Microsoft jDConf",[651,190611,190612,190613,664],{},"I just found out that I was selected to speak at Microsoft jDConf and this will be another first for me. This is a virtual conference that you can ",[812,190614,177994],{"href":190615,"rel":190616},"https://jdconf.com/",[816],[651,190618,190619],{},[2939,190620,188139],{},[651,190622,188142,190623,188450],{},[812,190624,51474],{"href":51472,"rel":190625},[816],[651,190627,187950,190628,69920,190630,190632],{},[41107,190629],{},[41107,190631],{},[812,190633,53869],{"href":82688,"rel":190634},[816],{"title":674,"searchDepth":790,"depth":790,"links":190636},[190637,190641,190642],{"id":190427,"depth":790,"text":190428,"children":190638},[190639,190640],{"id":190434,"depth":892,"text":190435},{"id":190458,"depth":892,"text":190459},{"id":190471,"depth":790,"text":190472},{"id":183432,"depth":790,"text":183433,"children":190643},[190644,190645,190646,190647],{"id":185176,"depth":892,"text":185177},{"id":190587,"depth":892,"text":97321},{"id":190597,"depth":892,"text":188104},{"id":190608,"depth":892,"text":190609},{"slug":190649,"date":190650},"deepseek-java-challengers","2025-02-03T07:00:00.000Z","/newsletter/2025/02/03/deepseek-java-challengers",{"title":190413,"description":190418},"newsletter/2025/02/03/deepseek-java-challengers","d7sUbSmWfjTIAdvhamb5qQnka6_f4CcalrFXJwTIN1o",{"id":190656,"title":190657,"body":190658,"description":190662,"extension":793,"meta":190730,"navigation":797,"path":190733,"seo":190734,"stem":190735,"__hash__":190736},"content/newsletter/2025/02/10/google-gemini-flash-2.md","Google Gemini Flash 2.0, Spring Office Hours Q&A and CloudFoundry Weekly",{"type":648,"value":190659,"toc":190724},[190660,190663,190666,190670,190673,190676,190679,190683,190686,190689,190692,190695,190698,190701,190705,190708,190711,190713,190715],[651,190661,190662],{},"Happy Monday and welcome to another edition of the newsletter. The Super Bowl was last night and I feel like we have this conversation every year but why do keep doing this on a Sunday night. Adults have to work the next day, kids have to go to school and this could all be avoided if we just moved the game to Saturday night.",[651,190664,190665],{},"I am going to keep this pretty short today as I am pretty swamped getting ready for 3 conferences and many other customer talks over the next month. If you're going to be at ConFoo, DevNexus or JavaOne please say hello 👋🏻",[4542,190667,190669],{"id":190668},"googles-flash-20","Google's Flash 2.0",[651,190671,190672],{},"Google Gemini Flash 2.0 represents a significant advancement in the LLM landscape, particularly for production applications requiring high throughput. The model family stands out for its impressive combination of speed, quality, and cost-effectiveness – with pricing at just 10 cents per million tokens for input and 40 cents per million tokens for output. Through the Spring AI project, developers can now easily integrate Gemini Flash 2.0 using Google's Vertex AI platform, though it requires some initial setup with Google Cloud CLI and project configuration.",[651,190674,190675],{},"The implementation details showcase practical integration paths through three main access points: Google AI Studio for experimentation, Vertex AI for enterprise-grade applications, and the consumer-facing Gemini app for everyday use. For Java developers, while direct API access isn't yet available, the Spring AI project provides a clean abstraction over Vertex AI, enabling straightforward integration with just a few lines of configuration and code. The demo application demonstrates how to set up a basic chat client using Spring AI's ChatClient builder, making it accessible for developers to start building with this powerful new model.",[5988,190677],{"id":190678},"mMLaWFx2SN8",[4542,190680,190682],{"id":190681},"spring-office-hours-s4","Spring Office Hours S4",[651,190684,190685],{},"In our first Spring Office Hours episode of February, DaShaun and I dived into some exciting developments in the Spring ecosystem. One of the biggest announcements we covered was the upcoming changes in Spring Boot 3.5, particularly the removal of the spring-boot-starter-parent. While this change will streamline dependency management and make upgrades smoother, we recognize it'll require some adaptation for teams using parent POM wrappers.",[651,190687,190688],{},"We spent time discussing the rapid evolution of Spring AI, including its new support for DeepSeek's models and the framework's capabilities for building AI agents. I'm particularly excited about how quickly our Spring AI team has been adding support for new models, keeping pace with the fast-moving AI landscape.",[651,190690,190691],{},"A significant portion of our discussion focused on something I'm particularly passionate about - community involvement and career development in the Spring ecosystem. We emphasized that you don't need official permission to contribute to Spring, and highlighted the many \"good first issue\" labels in our repositories that are perfect for new contributors.",[651,190693,190694],{},"Looking ahead, we're gearing up for an exciting conference season. I'll be at ConFoo in Montreal, DevNexus in Atlanta (where DaShaun and I will be doing a live Spring Office Hours!), JavaOne in San Francisco and Spring I.O in Barcelona. If you're planning to submit a talk for Spring I.O, don't forget the CFP closes on February 13th.",[651,190696,190697],{},"The episode really drove home something I believe strongly: there are many paths to growth in the Spring ecosystem, whether through code contributions, blog posts, or local meetups. Everyone can contribute in their own way, and that's what makes our community so vibrant.",[5988,190699],{"id":190700},"AL2HL3zRWwI",[4542,190702,190704],{"id":190703},"cloud-foundry-weekly","Cloud Foundry Weekly",[651,190706,190707],{},"Last week I was lucky to be a guest on the Cloud Foundry Weekly Podcast. DaShaun and I were actually on the very first episode and we returned for last weeks 44th episode and it was a lot of fun. We talked a lot about AI and What's new in Spring. You can check this episode of the podcast below as well as an archive of previous episodes.",[5988,190709],{"id":190710},"EDAjySENoM4",[4542,190712,188139],{"id":157703},[651,190714,188297],{},[651,190716,187950,190717,69920,190719,190721],{},[41107,190718],{},[41107,190720],{},[812,190722,53869],{"href":82688,"rel":190723},[816],{"title":674,"searchDepth":790,"depth":790,"links":190725},[190726,190727,190728,190729],{"id":190668,"depth":790,"text":190669},{"id":190681,"depth":790,"text":190682},{"id":190703,"depth":790,"text":190704},{"id":157703,"depth":790,"text":188139},{"slug":190731,"date":190732},"google-gemini-flash-2","2025-02-10T07:00:00.000Z","/newsletter/2025/02/10/google-gemini-flash-2",{"title":190657,"description":190662},"newsletter/2025/02/10/google-gemini-flash-2","-pEEwjKEbrsivGIOB3q5B0QyuBJkKNs9qAYFOIZG-CA",{"id":190738,"title":190739,"body":190740,"description":190744,"extension":793,"meta":190855,"navigation":797,"path":190858,"seo":190859,"stem":190860,"__hash__":190861},"content/newsletter/2025/02/24/confoo-2025.md","ConFoo, Moving Java forward with Shar, Spring AI releases and Speaking Updates",{"type":648,"value":190741,"toc":190849},[190742,190745,190748,190751,190754,190756,190764,190767,190773,190776,190779,190783,190786,190789,190791,190806,190809,190815,190819,190822,190825,190828,190831,190835,190840,190842,190844],[651,190743,190744],{},"Happy Monday and welcome to another edition of the newsletter. We are entering the last week of February and I am all for it. I don’t mind the cold or the snow but when we get into late February I am just over it. I’m hitting the road for 3 out of the next 4 weeks and I’m excited to connect with the community.",[651,190746,190747],{},"I have been pretty swamped lately preparing for all my upcoming talks, so I haven’t released any videos on the YouTube channel but I will get back to that and answering your questions as soon as I can.",[651,190749,190750],{},"I’m also preparing for a 3-hour workshop on Spring AI and I’m going to use a lot of that material to create a big introduction for YouTube. I’m expecting that video to be in the 3-4 hour range and I’ll let you know more about that as soon as I start working on it.",[651,190752,190753],{},"“Living in a state of gratitude is the gateway to grace” Arianna Huffington",[4542,190755,185177],{"id":185176},[651,190757,190758,190759,190763],{},"This week I’m heading to Montreal, CA for ",[812,190760,185177],{"href":190761,"rel":190762},"https://confoo.ca/en/2025",[816],". Last year was my first year at the conference and I really enjoyed it. The people were awesome, the talks were great and the food was amazing. It was also my first time in Montreal and I really enjoyed seeing the city and the unground cities underneath the city.",[651,190765,190766],{},"I’m giving 2 talks at the conference and the first one is all about using AI in your day-to-day workflows. This is a brand-new talk, and I am really excited about this. This talk isn’t just how to use ChatGPT or IDE extensions to write code. I go into some practical use cases that I’m using to be more productive every day.",[651,190768,190769],{},[660,190770],{"alt":190771,"src":190772},"Code Smarter Not Harder","/images/newsletter/2025/02/24/code-smarter-not-harder.png",[651,190774,190775],{},"The 2nd talk is about using Generative AI in your Java applications with Spring AI. I have given this talk before, but I updated it for the conference with less slides and all new examples to go through. I am packing in a lot of great content in 45 minutes and I’m looking forward to this presentation.",[651,190777,190778],{},"If you’re going to be at ConFoo please say hello 👋🏻",[4542,190780,190782],{"id":190781},"moving-java-forward","Moving Java Forward",[651,190784,190785],{},"Last week was a special Spring Office Hours as we got to sit and talk with my good friend Sharat Chander. We talked about all things Java, Community and upcoming conferences that we will all be at like DevNexus and JavaOne. If you didn’t get a chance to catch this live, the replay of the podcast you can check it out below.",[5988,190787],{"id":190788},"cqCQUjzRit4",[4542,190790,123549],{"id":140820},[651,190792,190793,190794,190799,190800,190805],{},"Last week was a huge week for the Spring AI team. They announced the availability of ",[812,190795,190798],{"href":190796,"rel":190797},"https://spring.io/blog/2025/02/14/spring-ai-1-0-0-m6-released",[816],"Spring AI 1.0.0 M6"," along with the official ",[812,190801,190804],{"href":190802,"rel":190803},"https://spring.io/blog/2025/02/14/mcp-java-sdk-released-2",[816],"SDK Model Context Protocol (MCP) for Java",". The Spring AI team worked closely with the folks at Anthropic to make this happen and this is really exciting to see. If you haven’t had a chance to check out MCP and what it can do you for this is a really good time to jump on in.",[4542,190807,190808],{"id":183440},"Speaking",[651,190810,190811,190812,184565],{},"Conference season is in full swing over here. I’m going to ConFoo this week, DevNexus next week and JavaOne on March 18th. I was also able to submit some proposals to Spring I/O in Barcelona and KCDC so I will be crossing my fingers for those. If you want to keep up to date with my speaking engagements you can check out the ",[812,190813,120451],{"href":120449,"rel":190814},[816],[651,190816,190817],{},[2939,190818,187762],{},[651,190820,190821],{},"Grok 3 was released last week and so far I have been pretty happy with it",[187764,190823],{"id":190824},"1892647616460001337",[651,190826,190827],{},"This had to be the coolest announcement of last week.",[187764,190829],{"id":190830},"1892633380556009967",[651,190832,190833],{},[2939,190834,188139],{},[651,190836,188142,190837,188450],{},[812,190838,51474],{"href":51472,"rel":190839},[816],[651,190841,187950],{},[651,190843,798],{},[651,190845,190846],{},[812,190847,53869],{"href":82688,"rel":190848},[816],{"title":674,"searchDepth":790,"depth":790,"links":190850},[190851,190852,190853,190854],{"id":185176,"depth":790,"text":185177},{"id":190781,"depth":790,"text":190782},{"id":140820,"depth":790,"text":123549},{"id":183440,"depth":790,"text":190808},{"slug":190856,"date":190857},"confoo-2025","2025-02-24T07:00:00.000Z","/newsletter/2025/02/24/confoo-2025",{"title":190739,"description":190744},"newsletter/2025/02/24/confoo-2025","Z8J-zTzsx_av4ayQloTopdaxwJlvwWQM0UrPoxZkGrY",{"id":190863,"title":190864,"body":190865,"description":190869,"extension":793,"meta":191018,"navigation":797,"path":191021,"seo":191022,"stem":191023,"__hash__":191024},"content/newsletter/2025/03/10/devnexus-2025.md","ConFoo, DevNexus, Upcoming Presentations and a Notion Spring Boot Starter",{"type":648,"value":190866,"toc":191011},[190867,190870,190872,190878,190886,190892,190895,190897,190900,190903,190908,190911,190914,190923,190925,190942,190949,190953,190956,190964,190967,190973,190979,190983,190986,190989,190992,190995,190997,191002],[651,190868,190869],{},"Happy Monday and welcome to another edition of the newsletter. It has been a busy few weeks for me giving presentations at conferences, for customers and some online meetups.",[4542,190871,185177],{"id":185176},[651,190873,190874],{},[660,190875],{"alt":190876,"src":190877},"ConFoo Conference","/images/newsletter/2025/03/10/confoo_01.png",[651,190879,190880,190881,188081],{},"This was my 2nd year attending and speaking at ConFoo and I absolutely love this conference. The food, sessions, conversations and people were all amazing. I was lucky to give 2 presentations at this conference and I thought they both went really well. The first was on AI-Powered Dev Hacks and it was an opportunity to share how I am using AI in my day-to-day workflows. If you want to check out the slides or a quick overview of the talk you can check out ",[812,190882,190885],{"href":190883,"rel":190884},"https://www.danvega.dev/blog/ai-powered-dev-hacks-confoo-2025",[816],"my blog post",[651,190887,190888],{},[660,190889],{"alt":190890,"src":190891},"AI-Powered Dev Hacks","/images/newsletter/2025/03/10/confoo_02.png",[651,190893,190894],{},"The 2nd talk was an introduction to Spring AI and I absolutely love giving this talk. I gave a bit of an introduction to Artificial Intelligence and then went in to showing the value of Spring AI. If you're interested in this presentation keep reading because I will be giving a version of it later this week.",[4542,190896,97321],{"id":190587},[651,190898,190899],{},"Last week I had the opportunity to head to Atlanta and attend DevNexus. This is one of my favorite conferences for Java developers. The vendors, speakers, sessions and hallway tracks are all so great. I love catching up with so many developers from the Java community and this for me is one of those can't miss conferences.",[651,190901,190902],{},"I had the opportunity to give a talk on how to navigate the frontend landscape. This was an overview for Java developers who all of a sudden need to create a frontend for an application and are curious as to what your options are. The frontend development space is fast paced and always changing so this was my chance to give an overview of what technologies you should focus on in 2025. I had some really good feedback on this talk and I really appreciate everyone who attended my talk.",[651,190904,190905],{},[660,190906],{"alt":121448,"src":190907},"/images/newsletter/2025/03/10/frontend_landscape.png",[651,190909,190910],{},"While we were there DaShaun and I had an opportunity to record an episode of Spring Office Hours live.",[5988,190912],{"id":190913},"yCXrUjsKy_A",[651,190915,190916,190917,190922],{},"While I was on the road I had an opportunity to give a couple of a presentations. One was a workshop on Spring AI and this ended up being a really great talk, well attended and some amazing questions. As I got ready for this talk I took the opportunity to update my ",[812,190918,190921],{"href":190919,"rel":190920},"https://github.com/danvega/spring-ai-workshop",[816],"Spring AI Workshop Repository"," to the latest milestone and added a bunch of really great examples.",[4542,190924,185930],{"id":185929},[5316,190926,190927,190935],{},[5332,190928,190929,190930,190934],{},"This week I will be giving a ",[812,190931,186697],{"href":190932,"rel":190933},"https://www.meetup.com/virtualjug/events/306414120/",[816]," to the Virtual Java User Group (vJUG) on Spring AI. In this talk I will give you a quick introduction to AI and where Spring AI helps you solve some of the problems that will come up when you start building real world applications.",[5332,190936,190937,190938,190941],{},"Next week I will be in California for ",[812,190939,188104],{"href":188102,"rel":190940},[816]," and I will be giving my talk on navigating the frontend landscape for Java Developers.",[651,190943,190944,190945,190948],{},"As always if you're interested in where I am speaking next you can always check out the ",[812,190946,120451],{"href":120449,"rel":190947},[816]," on my website for upcoming and past presentations.",[4542,190950,190952],{"id":190951},"notion-spring-boot-starter","Notion Spring Boot Starter",[651,190954,190955],{},"I am a huge fan of Notion and use it for almost everything in both my personal and professional life. I have put together some tutorials in the past on integrating with Notion but it was very much a manual process.",[651,190957,190958,190959,190963],{},"I decided to sit down with my robot friends and build out a Spring Boot Starter that I could use in any application that need to interact with Notion. What we came up with was the first version of a ",[812,190960,190952],{"href":190961,"rel":190962},"https://github.com/danvega/notion-spring-boot-starter",[816],". There is some information in the readme on how to get started with it but I will build this out more as I start to put together some samples.",[651,190965,190966],{},"I'm starting with the idea of a content calendar and if you want to pull this information into your Spring Application you could pull in this Notion Starter and it would be fairly easy read and write to that database. If this sounds like something you're interested in please let me know. Also if you could let me know what types of integrations you're working on it will help me out when putting some samples together.",[651,190968,190969],{},[660,190970],{"alt":190971,"src":190972},"Notion Starter Code","/images/newsletter/2025/03/10/content_cal.png",[651,190974,190975],{},[660,190976],{"alt":190977,"src":190978},"Notion Content Calendar","/images/newsletter/2025/03/10/notion_content_cal.png",[651,190980,190981],{},[2939,190982,187762],{},[651,190984,190985],{},"These prices are starting to get out of hand 🤦♂️",[187764,190987],{"id":190988},"1895300659949510727",[651,190990,190991],{},"I had a chance to check out Claude Code",[187764,190993],{"id":190994},"1894127619035033722",[4542,190996,188139],{"id":157703},[651,190998,188142,190999,188147],{},[812,191000,51474],{"href":51472,"rel":191001},[816],[651,191003,187950,191004,69920,191006,191008],{},[41107,191005],{},[41107,191007],{},[812,191009,53869],{"href":82688,"rel":191010},[816],{"title":674,"searchDepth":790,"depth":790,"links":191012},[191013,191014,191015,191016,191017],{"id":185176,"depth":790,"text":185177},{"id":190587,"depth":790,"text":97321},{"id":185929,"depth":790,"text":185930},{"id":190951,"depth":790,"text":190952},{"id":157703,"depth":790,"text":188139},{"slug":191019,"date":191020},"devnexus-2025","2025-03-10T00:00:00.000Z","/newsletter/2025/03/10/devnexus-2025",{"title":190864,"description":190869},"newsletter/2025/03/10/devnexus-2025","ERSUIazpPqFbbITA4rBrczePKk4_UmrRS6XrtW_f1CE",{"id":191026,"title":191027,"body":191028,"description":191032,"extension":793,"meta":191417,"navigation":797,"path":191420,"seo":191421,"stem":191422,"__hash__":191423},"content/newsletter/2025/03/17/jdk-24-javaone.md","JDK 24, JavaOne, The Virtual Java User Group (vJUG) and my return to YouTube!",{"type":648,"value":191029,"toc":191400},[191030,191033,191037,191040,191043,191046,191050,191243,191247,191254,191257,191261,191264,191267,191271,191274,191277,191280,191283,191287,191290,191298,191301,191305,191308,191311,191314,191318,191321,191329,191332,191336,191339,191346,191349,191353,191356,191367,191370,191374,191377,191384,191387,191389,191391],[651,191031,191032],{},"Are you ready for Java's biggest upgrade of the year? JDK 24 lands on Tuesday, bringing 24 powerful JEPs that will transform how we build applications. As Java celebrates its 30th birthday, I'm packing my bags for JavaOne in San Francisco, where I'll be speaking about bridging the gap between Java and frontend development. This newsletter is packed with technical insights you won't want to miss—from unlocking AI potential with Spring AI to monitoring token usage in your LLM applications. Between my return to YouTube after a month-long hiatus and fresh tutorials on Google Gemini integration, we've got plenty to explore. Whether you're curious about quantum-resistant cryptography or struggling with structured LLM outputs, this edition has something to elevate your development workflow.",[4542,191034,191036],{"id":191035},"jdk-24-javas-most-feature-packed-release-yet","JDK 24: Java's Most Feature-Packed Release Yet!",[651,191038,191039],{},"Java developers, get ready for a productivity revolution! JDK 24 arrives on March 18 with an impressive lineup of 24 JEPs that push Java's capabilities into exciting new territory. The new Class-File API (JEP 484) transforms how framework developers and bytecode manipulation tools operate, offering a standard, fully-supported API for reading, writing, and transforming Java class files without resorting to third-party libraries. This opens up countless possibilities for code generation, runtime analysis, and custom language features that were previously difficult to implement reliably.",[651,191041,191042],{},"Stream enthusiasts will fall in love with Stream Gatherers (JEP 485), which supercharges Java's functional programming model by enabling complex multi-element transformations within streams. Unlike traditional map/filter operations that process elements individually, gatherers can correlate and transform groups of elements, making operations like windowing, batch processing, and complex aggregations dramatically more elegant.",[651,191044,191045],{},"Developers working with concurrent applications will appreciate the maturing Structured Concurrency API (JEP 499) and enhanced Scoped Values (JEP 487), which together solve thorny context-propagation problems in multi-threaded environments. And if you're building for the future, the quantum-resistant cryptography implementations (JEPs 496 & 497) ensure your security-critical applications will stay protected even in the post-quantum era.",[5909,191047,191049],{"id":191048},"complete-jdk-24-feature-list","Complete JDK 24 Feature List",[5316,191051,191052,191060,191068,191076,191084,191092,191100,191108,191116,191124,191132,191140,191148,191156,191164,191171,191179,191187,191195,191203,191211,191219,191227,191235],{},[5332,191053,191054,191059],{},[812,191055,191058],{"href":191056,"rel":191057},"https://openjdk.org/jeps/404",[816],"JEP 404",": Generational Shenandoah (Experimental)",[5332,191061,191062,191067],{},[812,191063,191066],{"href":191064,"rel":191065},"https://openjdk.org/jeps/450",[816],"JEP 450",": Compact Object Headers (Experimental)",[5332,191069,191070,191075],{},[812,191071,191074],{"href":191072,"rel":191073},"https://openjdk.org/jeps/472",[816],"JEP 472",": Prepare to Restrict the Use of JNI",[5332,191077,191078,191083],{},[812,191079,191082],{"href":191080,"rel":191081},"https://openjdk.org/jeps/475",[816],"JEP 475",": Late Barrier Expansion for G1",[5332,191085,191086,191091],{},[812,191087,191090],{"href":191088,"rel":191089},"https://openjdk.org/jeps/478",[816],"JEP 478",": Key Derivation Function API (Preview)",[5332,191093,191094,191099],{},[812,191095,191098],{"href":191096,"rel":191097},"https://openjdk.org/jeps/479",[816],"JEP 479",": Remove the Windows 32-bit x86 Port",[5332,191101,191102,191107],{},[812,191103,191106],{"href":191104,"rel":191105},"https://openjdk.org/jeps/483",[816],"JEP 483",": Ahead-of-Time Class Loading & Linking",[5332,191109,191110,191115],{},[812,191111,191114],{"href":191112,"rel":191113},"https://openjdk.org/jeps/484",[816],"JEP 484",": Class-File API",[5332,191117,191118,191123],{},[812,191119,191122],{"href":191120,"rel":191121},"https://openjdk.org/jeps/485",[816],"JEP 485",": Stream Gatherers",[5332,191125,191126,191131],{},[812,191127,191130],{"href":191128,"rel":191129},"https://openjdk.org/jeps/486",[816],"JEP 486",": Permanently Disable the Security Manager",[5332,191133,191134,191139],{},[812,191135,191138],{"href":191136,"rel":191137},"https://openjdk.org/jeps/487",[816],"JEP 487",": Scoped Values (Fourth Preview)",[5332,191141,191142,191147],{},[812,191143,191146],{"href":191144,"rel":191145},"https://openjdk.org/jeps/488",[816],"JEP 488",": Primitive Types in Patterns, instanceof, and switch (Second Preview)",[5332,191149,191150,191155],{},[812,191151,191154],{"href":191152,"rel":191153},"https://openjdk.org/jeps/489",[816],"JEP 489",": Vector API (Ninth Incubator)",[5332,191157,191158,191163],{},[812,191159,191162],{"href":191160,"rel":191161},"https://openjdk.org/jeps/490",[816],"JEP 490",": ZGC: Remove the Non-Generational Mode",[5332,191165,191166,191170],{},[812,191167,191169],{"href":156680,"rel":191168},[816],"JEP 491",": Synchronize Virtual Threads without Pinning",[5332,191172,191173,191178],{},[812,191174,191177],{"href":191175,"rel":191176},"https://openjdk.org/jeps/492",[816],"JEP 492",": Flexible Constructor Bodies (Third Preview)",[5332,191180,191181,191186],{},[812,191182,191185],{"href":191183,"rel":191184},"https://openjdk.org/jeps/493",[816],"JEP 493",": Linking Run-Time Images without JMODs",[5332,191188,191189,191194],{},[812,191190,191193],{"href":191191,"rel":191192},"https://openjdk.org/jeps/494",[816],"JEP 494",": Module Import Declarations (Second Preview)",[5332,191196,191197,191202],{},[812,191198,191201],{"href":191199,"rel":191200},"https://openjdk.org/jeps/495",[816],"JEP 495",": Simple Source Files and Instance Main Methods (Fourth Preview)",[5332,191204,191205,191210],{},[812,191206,191209],{"href":191207,"rel":191208},"https://openjdk.org/jeps/496",[816],"JEP 496",": Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism",[5332,191212,191213,191218],{},[812,191214,191217],{"href":191215,"rel":191216},"https://openjdk.org/jeps/497",[816],"JEP 497",": Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm",[5332,191220,191221,191226],{},[812,191222,191225],{"href":191223,"rel":191224},"https://openjdk.org/jeps/498",[816],"JEP 498",": Warn upon Use of Memory-Access Methods in sun.misc.Unsafe",[5332,191228,191229,191234],{},[812,191230,191233],{"href":191231,"rel":191232},"https://openjdk.org/jeps/499",[816],"JEP 499",": Structured Concurrency (Fourth Preview)",[5332,191236,191237,191242],{},[812,191238,191241],{"href":191239,"rel":191240},"https://openjdk.org/jeps/501",[816],"JEP 501",": Deprecate the 32-bit x86 Port for Removal",[4542,191244,191246],{"id":191245},"join-me-at-javaone-2025-where-javas-future-unfolds","Join Me at JavaOne 2025: Where Java's Future Unfolds",[651,191248,191249,191250,191253],{},"As we celebrate Java's 30th birthday, I'm thrilled to announce that I'll be speaking at JavaOne 2025! My session, \"A Java Developer's Guide to Navigating the Frontend Landscape ",[679,191251,191252],{},"SES1487",",\" will explore how Java developers can leverage their existing skills while embracing modern frontend technologies.",[651,191255,191256],{},"In a world where full-stack development has become increasingly important, many Java developers find themselves stepping into unfamiliar frontend territory. My talk will bridge this gap by providing practical strategies for tackling JavaScript frameworks, build tools, and UI libraries with a Java developer's mindset. You'll walk away with concrete approaches to apply your Java expertise to frontend challenges, making the transition smoother and more intuitive.",[5909,191258,191260],{"id":191259},"why-this-session-matters","Why This Session Matters",[651,191262,191263],{},"The boundary between backend and frontend continues to blur, creating both challenges and opportunities for Java developers. Rather than treating these as separate worlds, we'll explore how your Java expertise provides a solid foundation for frontend work. From state management patterns that mirror Java concepts to leveraging type safety with TypeScript, you'll discover that your backend knowledge is more transferable than you might think.",[651,191265,191266],{},"We'll also dive into the cultural and workflow differences between these ecosystems, and how to navigate them successfully. If you've ever felt overwhelmed by the rapidly evolving frontend landscape, this session will provide the compass you need to chart your course forward.",[5909,191268,191270],{"id":191269},"lets-connect-at-javaone","Let's Connect at JavaOne!",[651,191272,191273],{},"If you're planning to attend, I'd love to continue the conversation after my session. Whether you're facing specific frontend challenges in your Java projects or just want to share experiences, I'm looking forward to connecting with fellow developers who are bridging these worlds.",[651,191275,191276],{},"The conference has an incredible lineup this year, from the Java 24 launch to the special 30th birthday celebrations. Between the world-class learning opportunities, networking events, and even the Wednesday night party at Devil's Canyon Brewing Company, it's shaping up to be an unmissable event for the Java community.",[651,191278,191279],{},"My session is scheduled for Tuesday, Mar 182:30 PM - 3:15 PM PDT in Room 105, and I hope to see you there! Drop by to say hello, or reach out beforehand if you have specific topics you'd like me to address during the Q&A portion.",[651,191281,191282],{},"See you in Redwood Shores!",[4542,191284,191286],{"id":191285},"building-intelligent-applications-with-spring-ai","Building Intelligent Applications with Spring AI",[651,191288,191289],{},"I was deeply honored to speak at the virtual Java User Group (vJUG) last week, presenting to what is the largest Java User Group on the planet. My session focused on Spring AI, where I introduced the fundamentals of AI and large language models before exploring how Java developers can integrate these technologies into their applications.",[651,191291,191292,191293,191297],{},"I began by covering key AI concepts before diving into Spring AI's architecture, demonstrating how it provides a portable API supporting multiple AI providers and modalities. Through live coding examples, I showcased Spring AI's practical features including structured output, chat memory, multimodal capabilities, and tools integration. I emphasized how Spring AI simplifies LLM integration by abstracting away provider-specific implementations, allowing developers to switch between models like OpenAI's GPT and Google's Gemini with minimal code changes. I also addressed important considerations around tokens, context windows, and pricing when building production applications with LLMs. For those interested in exploring further, I've created a GitHub repository (",[812,191294,191296],{"href":190919,"rel":191295},[816],"github.com/danvega/spring-ai-workshop",") with additional examples and resources.",[5988,191299],{"id":191300},"XjfWyc6xmSA",[4542,191302,191304],{"id":191303},"back-in-action-breaking-the-content-creation-hiatus","Back in Action: Breaking the Content Creation Hiatus",[651,191306,191307],{},"After a 33-day break from YouTube, I'm excited to share that I've returned with four fresh technical tutorials this week! Between client projects and conference preparations, finding time for content creation has been challenging—but your messages asking when new videos would arrive kept me motivated.",[651,191309,191310],{},"Each video tackles a specific challenge I've personally encountered while building AI-powered applications. Whether you're looking to extend LLM capabilities with external data sources, need consistent response formats from unpredictable models, or want to avoid surprise bills at month-end, there's something here to enhance your development workflow.",[651,191312,191313],{},"What AI challenges are you currently facing in your projects? Reply to this email with your questions—your feedback directly shapes my upcoming content. And if you found these tutorials helpful, sharing them with a colleague goes a long way toward growing our community.",[5909,191315,191317],{"id":191316},"unleashing-llm-potential-with-model-context-protocol-mcp","Unleashing LLM Potential with Model Context Protocol (MCP)",[651,191319,191320],{},"Model Context Protocol (MCP) addresses a critical limitation of large language models: their inability to access real-time data, personal documents, or external systems. This standardized protocol enables LLMs to communicate with external data sources and tools through a client-server architecture. Similar to how USB standardized device connections, MCP establishes a unified way for models to interact with various data sources without requiring custom integrations for each service.",[651,191322,191323,191324],{},"The implementation utilizes MCP servers (like file system access, Brave search, GitHub integration) that can be plugged into MCP-compatible clients such as Claude Desktop, Claude Code, Cursor, and WinSurf. By following this protocol, developers can create reusable tools that extend LLM capabilities without duplicating integration efforts across teams or applications. The video demonstrates this by configuring Claude Desktop to access local files and perform web searches for real-time information about recent shows like Daredevil Born Again. ",[812,191325,191328],{"href":191326,"rel":191327},"https://github.com/anthropics/model-context-protocol",[816],"GitHub Documentation",[5988,191330],{"id":191331},"nNLshWCoe0o",[5909,191333,191335],{"id":191334},"streamlining-google-gemini-flash-20-integration-with-api-keys","Streamlining Google Gemini Flash 2.0 Integration with API Keys",[651,191337,191338],{},"Google Gemini Flash 2.0 offers exceptional performance for AI applications with its speed, accuracy, and cost-efficiency. While previous implementations required using Google Vertex API with Google credentials, this tutorial demonstrates a simpler approach using just API keys through OpenAI compatibility. The technique leverages Spring AI's existing OpenAI integration by configuring two critical properties: changing the base URL to Google's Generative Language API endpoint and using Google Gemini API keys in place of OpenAI keys.",[651,191340,191341,191342],{},"The implementation includes a sample Spring Boot application that connects to Gemini models using this compatibility layer, plus a bonus endpoint that lists all available Google Gemini models. This approach creates a path forward until the official Google Gemini Java SDK matures and gets integrated into Spring AI directly. For developers concerned with API costs and performance, Gemini Flash 2.0 provides an excellent alternative with straightforward integration into existing Spring applications. ",[812,191343,89158],{"href":191344,"rel":191345},"https://github.com/danvega/gemini-flash-api",[816],[5988,191347],{"id":191348},"5zhNfPH-jps",[5909,191350,191352],{"id":191351},"controlling-ai-responses-with-structured-output-in-spring-ai","Controlling AI Responses with Structured Output in Spring AI",[651,191354,191355],{},"Structured output solves a critical challenge when working with LLMs in applications: unpredictable response formats. While consumers might be satisfied with any format when asking questions conversationally, applications need consistent, typed responses that can be deserialized into objects. The tutorial demonstrates how different LLMs implement structured output through their own JSON schema specifications, making standardization difficult for developers working with multiple models.",[651,191357,191358,191359,191362,191363],{},"Spring AI elegantly solves this complexity by handling format conversion behind the scenes. By simply defining a Java record and using the ",[676,191360,191361],{},"entity()"," method with a parameterized type reference, Spring AI automatically generates the appropriate schema for the target LLM. The implementation shows a REST controller that transforms an unstructured list of NBA teams into a structured collection of objects with consistent properties. This approach eliminates the need to understand each LLM's schema details while ensuring applications receive predictable, strongly-typed responses that integrate seamlessly with Java's type system. ",[812,191364,89158],{"href":191365,"rel":191366},"https://github.com/danvega/structured-output",[816],[5988,191368],{"id":191369},"8buYgH3T8XA",[5909,191371,191373],{"id":191372},"building-ai-application-observability-with-spring-boot-prometheus-and-grafana","Building AI Application Observability with Spring Boot, Prometheus, and Grafana",[651,191375,191376],{},"For developers paying per token in AI applications, operating without visibility into usage metrics is like running blindfolded toward an inevitable billing surprise. This technical solution implements comprehensive monitoring using a stack of complementary technologies: Spring Boot Actuator exposes application metrics through HTTP endpoints, Prometheus collects and stores these metrics as time-series data, and Grafana transforms this data into customizable dashboards for real-time monitoring.",[651,191378,191379,191380],{},"The implementation provides detailed visibility into critical AI metrics including token usage (both prompt and completion tokens), response times, error rates, and system resource consumption. Using Docker Compose for simplified deployment, the solution includes pre-configured dashboards that developers can immediately use or customize. By tracking these metrics, teams can implement appropriate guardrails around token consumption, optimize response times by identifying requests that would benefit from streaming responses, and avoid unexpected billing surprises. The complete monitoring solution requires minimal code changes to existing Spring AI applications while providing maximum visibility into performance and cost metrics. ",[812,191381,89158],{"href":191382,"rel":191383},"https://github.com/danvega/spring-ai-metrics",[816],[5988,191385],{"id":191386},"pBVKkcBhw6I",[4542,191388,188139],{"id":157703},[651,191390,188297],{},[651,191392,187950,191393,69920,191395,191397],{},[41107,191394],{},[41107,191396],{},[812,191398,53869],{"href":82688,"rel":191399},[816],{"title":674,"searchDepth":790,"depth":790,"links":191401},[191402,191405,191409,191410,191416],{"id":191035,"depth":790,"text":191036,"children":191403},[191404],{"id":191048,"depth":892,"text":191049},{"id":191245,"depth":790,"text":191246,"children":191406},[191407,191408],{"id":191259,"depth":892,"text":191260},{"id":191269,"depth":892,"text":191270},{"id":191285,"depth":790,"text":191286},{"id":191303,"depth":790,"text":191304,"children":191411},[191412,191413,191414,191415],{"id":191316,"depth":892,"text":191317},{"id":191334,"depth":892,"text":191335},{"id":191351,"depth":892,"text":191352},{"id":191372,"depth":892,"text":191373},{"id":157703,"depth":790,"text":188139},{"slug":191418,"date":191419},"jdk-24-javaone","2025-03-17T00:00:00.000Z","/newsletter/2025/03/17/jdk-24-javaone",{"title":191027,"description":191032},"newsletter/2025/03/17/jdk-24-javaone","UXqtFZIMESsUMogKommAxZxwdPlfqFOB7q2bJ42AA2Q",{"id":191425,"title":191426,"body":191427,"description":191431,"extension":793,"meta":191594,"navigation":797,"path":191597,"seo":191598,"stem":191599,"__hash__":191600},"content/newsletter/2025/03/24/javaone-2025.md","JavaOne Wrap Up, JDK 24 Stream Gatherers and a tutorial on few shot prompting",{"type":648,"value":191428,"toc":191586},[191429,191432,191435,191437,191440,191446,191453,191459,191466,191469,191475,191478,191484,191487,191493,191496,191500,191503,191507,191510,191513,191519,191522,191525,191532,191536,191539,191542,191545,191548,191551,191555,191558,191561,191564,191567,191570,191573,191575,191577],[651,191430,191431],{},"Happy Monday and welcome to another edition of the newsletter. I'm back at my desk this week after an amazing trip to JavaOne in San Francisco and I can't wait to tell you all about it.",[651,191433,191434],{},"It has been a busy month that had me traveling 3 out of the last 4 weeks to ConFoo, DevNexus and JavaOne. I'm really excited to get back to making some videos this week. I was so inspired by being at so many great conferences and so many developers thanking me for my contributions to the community. It go me fired up to get home and do more and that is what I will be working on this week.",[4542,191436,188104],{"id":190597},[651,191438,191439],{},"Last week I had the opportunity to attend and speak at my very first JavaOne. The conference was really amazing on so many levels. First off it was so exciting to be in the keynote watching it live because I have watched so many of these keynotes over the years from my desk.",[651,191441,191442],{},[660,191443],{"alt":191444,"src":191445},"JavaOne Keynote","/images/newsletter/2025/03/24/keynote.jpeg",[651,191447,191448,191449,191452],{},"I thought the keynote was wall planned out. I really appreciated ",[2939,191450,191451],{},"Mark Reinhold"," did an amazing job of debunking some of Java's myths. These are things you might hear people complain about from time to time but sure enough Java has an answer for them.",[651,191454,191455],{},[660,191456],{"alt":191457,"src":191458},"Java Myths","/images/newsletter/2025/03/24/java-myths.png",[651,191460,191461,191462,664],{},"If you didn't get a chance to catch the keynote and want to watch a replay of it you can check it out ",[812,191463,18263],{"href":191464,"rel":191465},"https://www.youtube.com/live/mk_2MIWxLI0",[816],[651,191467,191468],{},"There were so many great talks throughout the conference. I really enjoyed Adam Bien's talk and finally getting the chance to meet him in person. He is so knowledgeable and I really enjoy his humor and presentation style. If you ever get a chance to watch him in person, take it.",[651,191470,191471],{},[660,191472],{"alt":191473,"src":191474},"Adam Bien's Talk","/images/newsletter/2025/03/24/adam-bien-talk.png",[651,191476,191477],{},"I also go the opportunity to meet Brian Goetz and watch his talk on where Java is going. Brian is just one of those people who when they are talking, you listen. He is so knowledgeable on past, present and future of Java and it was really great to hear his insights.",[651,191479,191480],{},[660,191481],{"alt":191482,"src":191483},"Brian Goetz","/images/newsletter/2025/03/24/brian-goetz.jpeg",[651,191485,191486],{},"I got to take a picture with Duke!",[651,191488,191489],{},[660,191490],{"alt":191491,"src":191492},"Duke","/images/newsletter/2025/03/24/duke-photo.jpeg",[651,191494,191495],{},"It was so great talking and hanging out with so many great people at JavaOne. This was a conference I won't forget and can't wait to get back to the next one. This week inspired me to do more for this great community and that is exactly what I will do ❤️",[4542,191497,191499],{"id":191498},"streamlining-your-code-with-jdk-24s-stream-gatherers","Streamlining Your Code with JDK 24's Stream Gatherers",[651,191501,191502],{},"Last week at JavaOne everyone was talking about JDK 24. I put together an article on one of my favorite new features: Stream Gatherers.",[5909,191504,191506],{"id":191505},"what-are-stream-gatherers","What Are Stream Gatherers?",[651,191508,191509],{},"JDK 24's Stream Gatherers (JEP 485) revolutionize how we build intermediate operations in Java's Stream API. This powerful addition allows developers to create custom, reusable transformations that make complex data processing elegantly simple.",[651,191511,191512],{},"In my latest tutorial, I demonstrate how Stream Gatherers solve a common challenge: building a \"Recent Posts By Category\" feature for blog applications. The traditional approach requires nested collectors and multiple stream operations, resulting in verbose, difficult-to-follow code. Stream Gatherers transform this complexity into a concise, natural flow that's both more readable and maintainable.",[651,191514,191515],{},[660,191516],{"alt":191517,"src":191518},"Stream Gatherers Example","/images/newsletter/2025/03/24/stream-gatherers.png",[651,191520,191521],{},"What makes Gatherers special is their four-component design: an initializer to create state, an integrator to process elements, a combiner for parallel execution, and a finisher to emit results. This architecture provides exceptional flexibility while maintaining the Stream API's lazy evaluation and parallelism capabilities.",[651,191523,191524],{},"Beyond basic grouping operations, Stream Gatherers shine when implementing sophisticated processing like sliding window analysis, word frequency counting, sentiment analysis, and dynamic pagination. They bridge the gap between intermediate and terminal operations, enabling elegant solutions to problems that previously required cumbersome workarounds.",[651,191526,191527,191528],{},"Check out the complete tutorial with working examples at: ",[812,191529,191530],{"href":191530,"rel":191531},"https://www.danvega.dev/blog/stream-gatherers",[816],[4542,191533,191535],{"id":191534},"few-shot-prompting","Few Shot Prompting",[651,191537,191538],{},"This week, I tackled Few-Shot prompting in Spring AI after receiving a viewer question about sentiment analysis implementation. If you've been following my tutorials, you know I'm passionate about making AI concepts accessible to everyone.",[651,191540,191541],{},"I demonstrated how I approach Few-Shot prompting by showing you exactly how to provide example input-output pairs that guide large language models toward more accurate responses. I built a complete Spring Boot application that classifies tweet sentiment, highlighting a critical implementation detail many developers miss - placing examples in the user message rather than the system or assistant messages.",[651,191543,191544],{},"My implementation combines Spring AI with OpenAI's GPT-4, setting temperature to 0.0 for deterministic classification results. I walked through everything from initial configuration to final execution, sharing insights about message structure that will immediately improve your own AI implementations.",[651,191546,191547],{},"What I wanted to emphasize most in this tutorial is that \"prompt engineering\" isn't some gatekeeping skill - it's something we can all learn and improve with practice. Check out the video link below, and if you've been experimenting with Few-Shot prompting in your own projects, I'd love to hear about your experiences.",[5988,191549],{"id":191550},"QZyYFyx_4RY",[651,191552,191553],{},[2939,191554,187762],{},[651,191556,191557],{},"I started watching the Lex Friedman interview with The Primeagen and it's so good!",[187764,191559],{"id":191560},"1903620751904633096",[651,191562,191563],{},"Next up on the speaking circuit is Microsoft's JDConf.",[187764,191565],{"id":191566},"1903139292043428148",[651,191568,191569],{},"Is AI making us dumber?",[187764,191571],{"id":191572},"1902476353011585066",[4542,191574,188139],{"id":157703},[651,191576,188297],{},[651,191578,187950,191579,69920,191581,191583],{},[41107,191580],{},[41107,191582],{},[812,191584,53869],{"href":82688,"rel":191585},[816],{"title":674,"searchDepth":790,"depth":790,"links":191587},[191588,191589,191592,191593],{"id":190597,"depth":790,"text":188104},{"id":191498,"depth":790,"text":191499,"children":191590},[191591],{"id":191505,"depth":892,"text":191506},{"id":191534,"depth":790,"text":191535},{"id":157703,"depth":790,"text":188139},{"slug":191595,"date":191596},"javaone-2025","2025-03-24T00:00:00.000Z","/newsletter/2025/03/24/javaone-2025",{"title":191426,"description":191431},"newsletter/2025/03/24/javaone-2025","blCTtbs54LLqxDTwJOjgILs3SKautNqOdyVZPcXOzY8",{"id":191602,"title":191603,"body":191604,"description":191608,"extension":793,"meta":191837,"navigation":797,"path":191840,"seo":191841,"stem":191842,"__hash__":191843},"content/newsletter/2025/03/31/mcp-server-build.md","Building your own MCP Server, JDK 24 with Billy Korando and OpenAI's new Image Generation",{"type":648,"value":191605,"toc":191827},[191606,191609,191612,191615,191619,191636,191639,191643,191652,191656,191659,191666,191669,191672,191676,191679,191692,191695,191706,191710,191721,191726,191741,191776,191782,191787,191790,191793,191796,191799,191802,191805,191809,191812,191815,191817],[651,191607,191608],{},"Happy Monday! This week's edition is packed with AI advancements and Java updates. Learn how to build a custom MCP server from scratch with Spring AI to connect your tools to models like Claude, and hear about MCP's momentum with OpenAI's support. We'll also share highlights from our chat with Billy Korando about exciting JDK 24 features, and dive into the fun (and impressive results) of OpenAI's new built-in image generator in ChatGPT.",[4542,191610,150571],{"id":191611},"mcp",[651,191613,191614],{},"In this week's tutorial video, I demonstrated how to build a custom Model Context Protocol (MCP) server from scratch using Spring AI's powerful starter packages. The video walks through creating a simple MCP server that exposes information about my courses, which can then be accessed by AI assistants like Claude Desktop.",[5909,191616,191618],{"id":191617},"what-youll-learn-in-the-video","What You'll Learn in the Video",[5316,191620,191621,191624,191627,191630,191633],{},[5332,191622,191623],{},"How to set up a Spring Boot application with the MCP server starter",[5332,191625,191626],{},"Creating custom tools that expose functionality to AI models",[5332,191628,191629],{},"Configuring the server to work with standard input/output transport",[5332,191631,191632],{},"Installing and connecting your MCP server to Claude Desktop",[5332,191634,191635],{},"Testing the integration with real queries",[5988,191637],{"id":191638},"w5YVHG1j3Co",[5909,191640,191642],{"id":191641},"mcp-taking-off-rapidly","MCP Taking Off Rapidly",[651,191644,191645,191646,191651],{},"The Model Context Protocol ecosystem is expanding at an impressive pace! Just last week, ",[812,191647,191650],{"href":191648,"rel":191649},"https://x.com/sama/status/1904957253456941061",[816],"Sam Altman announced"," that OpenAI will be supporting MCP, representing a major milestone for this emerging standard. This means we'll likely see broader adoption and interoperability across different AI platforms in the near future.",[5909,191653,191655],{"id":191654},"spring-ai-team-leading-the-way","Spring AI Team Leading the Way",[651,191657,191658],{},"While I used Spring Boot in my example, it's worth highlighting that the Spring AI team is behind the official MCP Server SDK for Java. Even if you're not using Spring in your projects, you can take advantage of this SDK to build MCP-compatible servers with pure Java:",[651,191660,191661],{},[812,191662,191665],{"href":191663,"rel":191664},"https://github.com/modelcontextprotocol/java-sdk",[816],"Java SDK for Model Context Protocol",[651,191667,191668],{},"This standardized approach to connecting AI models with external tools and data sources opens up exciting possibilities for creating workflows where AI assistants can access your applications, APIs, and databases through a consistent interface.",[651,191670,191671],{},"What MCP servers would you like to see built next? Let me know your thoughts and ideas!",[4542,191673,191675],{"id":191674},"spring-office-hours-with-billy-korando","Spring Office Hours with Billy Korando",[651,191677,191678],{},"Last week on Spring Office Hours we had the opportunity to sit down with Oracle Java Developer Advocate Billy Korando to discuss the recent release of JDK 24 and highlights from Java One conference. Billy shares insights on significant improvements in JDK 24, including synchronized virtual threads without pinning (JEP 451) and Stream Gatherers (JEP 485), both popular features that excited many developers at Java One. The conversation explores how Project Amber continues to enhance Java with data-oriented programming concepts, and touches on upcoming improvements to structured concurrency in JDK 25.",[651,191680,191681,191682,23212,191686,191691],{},"We also discussed the performance benefits of updating to newer JDK versions, with Netflix reporting significant improvements after upgrading. Billy provides guidance on how developers can contribute to Java's evolution and test early access builds of upcoming versions. The episode wraps up with recommendations to check out the complete JDK 24 ",[812,191683,95704],{"href":191684,"rel":191685},"https://www.oracle.com/java/technologies/javase/24-relnote-issues.html",[816],[812,191687,191690],{"href":191688,"rel":191689},"https://www.youtube.com/@java",[816],"Java YouTube"," channel for comprehensive information on all the new features.",[5988,191693],{"id":191694},"ZsKJHGgahuA",[651,191696,191697,191698,103273,191702,191705],{},"Speaking of JDK one of my favorite new features is Stream Gatherers. I put together a ",[812,191699,67564],{"href":191700,"rel":191701},"https://youtu.be/hIbCu1slooE",[816],[812,191703,24879],{"href":191530,"rel":191704},[816]," on this last week if you're interested in learning about them.",[4542,191707,191709],{"id":191708},"open-ais-new-native-image-generator","Open AI's new native Image Generator",[651,191711,191712,191713,191716,191717,191720],{},"Did your social feeds suddenly explode with amazing, weird, and wonderful AI-generated images last week? Blame OpenAI! They just dropped their super-powered new GPT-4o image generation ",[7300,191714,191715],{},"directly inside ChatGPT",", and let's just say people have been having ",[7300,191718,191719],{},"a lot"," of fun experimenting with all sorts of styles and ideas. The creativity online has been wild!",[651,191722,191723],{},[2939,191724,191725],{},"So, What's This New Image Sorcery All About?",[651,191727,191728,191729,191732,191733,191736,191737,191740],{},"This isn't just plugging into a separate DALL-E window anymore. Oh no, this is ",[2939,191730,191731],{},"native magic!"," GPT-4o now has its ",[7300,191734,191735],{},"own"," built-in pixel pixies working overtime right within your chat. Forget context switching – you can brainstorm wild ideas and ",[7300,191738,191739],{},"poof"," conjure images right there! They're promising some seriously cool upgrades:",[5316,191742,191743,191749,191755,191764,191770],{},[5332,191744,191745,191748],{},[2939,191746,191747],{},"Mind-Blowing Quality:"," Get ready for images so sharp and realistic they might fool your grandma.",[5332,191750,191751,191754],{},[2939,191752,191753],{},"Hands That Look... Like Hands?!"," The AI is apparently getting better at drawing those tricky finger-noodles. Fewer nightmare Cthulhu hands, we hope!",[5332,191756,191757,191760,191761,191763],{},[2939,191758,191759],{},"Text That Doesn't Look Like Alien Scribbles:"," Need words ",[7300,191762,1472],{}," your image? GPT-4o is flexing its improved text-rendering muscles. Spellcheck included? Maybe!",[5332,191765,191766,191769],{},[2939,191767,191768],{},"Your Weird Ideas Welcome:"," It's supposedly much better at understanding complex, off-the-wall prompts. Go on, ask for a \"cyberpunk squirrel riding a taco through a Van Gogh painting.\" See what happens!",[5332,191771,191772,191775],{},[2939,191773,191774],{},"All-in-One Creative Chaos:"," Generate, tweak styles, change dimensions – all without leaving your ChatGPT conversation.",[651,191777,191778,191781],{},[2939,191779,191780],{},"Important Note:"," Currently, this shiny new image-making superpower is available exclusively for paying ChatGPT subscribers. So if you're on the free tier, you'll have to enjoy the artistic explosion from the sidelines for a bit longer!",[651,191783,191784],{},[2939,191785,191786],{},"My Own Adventures in AI Art Land!",[651,191788,191789],{},"Naturally, I had to dive headfirst into this digital paint bucket! Below are a few wacky, weird, or wonderfully artistic things I managed to coax out of the AI. Don't judge my prompt skills too harshly – we're all mad scientists in this new lab!",[187764,191791],{"id":191792},"1904890856512925730",[651,191794,191795],{},"And I was even able to take this a step further and animate one of the images I created and posted it on YouTube as a short.",[5988,191797],{"id":191798},"Uz5vDHzn59s",[651,191800,191801],{},"Who's watching the new Daredevil series?",[187764,191803],{"id":191804},"1905434515960725833",[651,191806,191807],{},[2939,191808,187762],{},[651,191810,191811],{},"If you haven't had a chance to check out Google Gemini Pro 2.5 it's time to give a spin!",[187764,191813],{"id":191814},"1905252626159010191",[4542,191816,188139],{"id":157703},[651,191818,191819,191820,69920,191822,191824],{},"I hope you enjoyed this newsletter installment, and I will talk to you in the next one. If you have any questions for me or topics you would like me to cover please feel free to reply to this email or reach out to me on Twitter (I'm not calling it X).\nHappy Coding,",[41107,191821],{},[41107,191823],{},[812,191825,53869],{"href":82688,"rel":191826},[816],{"title":674,"searchDepth":790,"depth":790,"links":191828},[191829,191834,191835,191836],{"id":191611,"depth":790,"text":150571,"children":191830},[191831,191832,191833],{"id":191617,"depth":892,"text":191618},{"id":191641,"depth":892,"text":191642},{"id":191654,"depth":892,"text":191655},{"id":191674,"depth":790,"text":191675},{"id":191708,"depth":790,"text":191709},{"id":157703,"depth":790,"text":188139},{"slug":191838,"date":191839},"mcp-server-build","2025-03-31T00:00:00.000Z","/newsletter/2025/03/31/mcp-server-build",{"title":191603,"description":191608},"newsletter/2025/03/31/mcp-server-build","eyQil-fcYlKjx_Uvbrq2-NcJ8e91I_1fbu0Ic5ekWuY",{"id":191845,"title":191846,"body":191847,"description":191851,"extension":793,"meta":191994,"navigation":797,"path":191997,"seo":191998,"stem":191999,"__hash__":192000},"content/newsletter/2025/04/07/local-ai-power.md","Local AI Power: Docker's Model Runner, MCP Revolution, and My JDConf Sessions",{"type":648,"value":191848,"toc":191986},[191849,191852,191855,191858,191862,191865,191868,191871,191874,191878,191886,191889,191892,191895,191898,191901,191904,191918,191922,191930,191934,191947,191950,191953,191961,191964,191967,191970,191973,191975,191977],[651,191850,191851],{},"Happy Monday and welcome to another edition of The Beehive! This week is particularly exciting as I'll be speaking at Microsoft's JDConf 2025 with two sessions – one live for our night owl friends in the Asia-Pacific region, and another on-demand covering the latest Spring innovations.",[651,191853,191854],{},"If you've been looking to break free from cloud AI dependencies, you're in luck. Last week's video demonstration of Docker Desktop's new Model Runner feature shows how to run powerful AI models like Google's Gemma 3 completely locally – zero API keys, zero data sharing, and zero monthly fees. Plus, I dive into the Model Context Protocol (MCP) that's revolutionizing how AI systems access external tools (think of it as the USB-C standardization of AI integrations).",[651,191856,191857],{},"As always, I've packed this newsletter with practical insights, code examples, and upcoming events to help you stay at the cutting edge of Java and AI development. Let's dive in!",[4542,191859,191861],{"id":191860},"run-ai-models-locally-docker-desktops-game-changing-model-runner-feature","Run AI Models Locally: Docker Desktop's Game-Changing Model Runner Feature",[651,191863,191864],{},"Tired of cloud API dependencies for your AI applications? In last week's video, I demonstrate Docker Desktop's powerful new Model Runner feature that lets you run AI models completely locally on your machine. I show you how to run Google's Gemma 3 open-source model with zero API keys, zero data sharing, and zero monthly fees—all while maintaining impressive performance on Apple Silicon.",[651,191866,191867],{},"The video provides a step-by-step walkthrough of configuring Docker Desktop's Model Runner, pulling AI models from Docker Hub, and integrating them with Spring Boot applications using Spring AI. You'll see how to create a fully functional AI-powered Java application in just 15 minutes, complete with code examples that show how easily you can switch from cloud-based models to local ones without changing your application architecture.",[651,191869,191870],{},"Click the video below to discover this developer-friendly approach to AI implementation that gives you complete control over your data while eliminating ongoing costs!",[5988,191872],{"id":191873},"6E6JFLMHcoQ",[4542,191875,191877],{"id":191876},"model-context-protocol-unleashing-ais-full-potential","Model Context Protocol: Unleashing AI's Full Potential",[651,191879,52301,191880,191885],{},[812,191881,191884],{"href":191882,"rel":191883},"https://spring-office-hours.transistor.fm/episodes/s4e8-model-context-protocol-an-introduction-for-java-spring-developers",[816],"Spring Office Hours podcast"," this week (Season 4, Episode 8), Deshawn Carter and I explored the rapidly evolving Model Context Protocol (MCP) - a new approach to extending AI capabilities that's being widely adopted across the industry.",[651,191887,191888],{},"MCP addresses fundamental limitations of large language models by providing them with tools to access real-time data, interact with external APIs, and connect with your local machine or organization's documents. This protocol, introduced by Anthropic in November, has quickly gained industry-wide adoption with OpenAI and Google now supporting it.",[651,191890,191891],{},"The core value of MCP lies in its client-server architecture that standardizes how AI systems access external tools. I compared it to USB-C standardization - instead of creating custom integrations for each application, developers can create reusable MCP servers that any compatible client can leverage.",[651,191893,191894],{},"During the episode, I demonstrated how I use multiple MCP servers together to automate workflows, such as transforming my Beehive newsletter content into markdown for my website - eliminating previously manual conversion processes.",[651,191896,191897],{},"My team at Spring has made significant contributions to the MCP ecosystem. We donated the official Java SDK for MCP that was adopted by Anthropic. This makes building MCP servers with Spring Boot straightforward, but also supports pure Java implementations without requiring a web framework.",[651,191899,191900],{},"The full episode is available now on the Spring Developer YouTube channel and wherever you get your podcasts.",[5988,191902],{"id":191903},"laJyrPIxSWc",[651,191905,191906,191907,191912,191913,664],{},"If you found that interesting this week we welcome ",[812,191908,191911],{"href":191909,"rel":191910},"https://x.com/JamesWard",[816],"James Ward",", Principal Developer Advocate at AWS. In this episode we explore James' career journey, his podcast work and his expertise in Java. The conversation dives into Amazon's Nova model and James' recent article on ",[812,191914,191917],{"href":191915,"rel":191916},"https://community.aws/content/2v8AETAkyvPp9RVKC4YChncaEbs/running-mcp-based-agents-clients-servers-on-aws",[816],"running MCP-based agents on AWS",[4542,191919,191921],{"id":191920},"join-me-at-jdconf-2025-this-week","Join Me at JDConf 2025 This Week!",[651,191923,191924,191925,191929],{},"I'm excited to share that I'll be speaking at ",[812,191926,191928],{"href":190615,"rel":191927},[816],"Microsoft's JDConf 2025"," this week with not one but two sessions! This conference brings together the brightest minds in Java development to explore the future of Java in an AI-driven, cloud-native world.",[5909,191931,191933],{"id":191932},"my-sessions","My Sessions:",[27665,191935,191936,191941],{},[5332,191937,191938,191940],{},[2939,191939,188392],{}," - Live on April 9th at 10:30 PM EDT (I know, crazy time - but perfect for our Asia-Pacific audience!)\nIn this session, I'll be covering essential AI concepts for Java developers and demonstrating practical implementations using modern frameworks and tools.",[5332,191942,191943,191946],{},[2939,191944,191945],{},"What's New in Spring"," - Available On-Demand\nThis session explores the latest innovations in the Spring ecosystem, covering new features, improvements, and best practices that every Spring developer should know about.",[651,191948,191949],{},"JDConf 2025 features an impressive lineup of speakers from Microsoft, Broadcom, Red Hat, IBM, Oracle and more, with sessions covering everything from AI integration and cloud-native development to performance optimization and security.",[651,191951,191952],{},"The event is organized across three regional broadcasts (Americas, Asia-Pacific, and EMEA) to accommodate global attendees, plus there's a rich catalog of on-demand sessions available anytime.",[651,191954,191955,191956,191960],{},"Best of all, registration is free! Head over to the ",[812,191957,191959],{"href":190615,"rel":191958},[816],"JDConf"," website to RSVP and check out the full agenda. I hope to see you there!",[651,191962,191963],{},"Here is an interesting use case for the new native image generation in ChatGPT",[187764,191965],{"id":191966},"1907805519680524762",[651,191968,191969],{},"I'm trying to put together a tutorial on the new feature in JDK 24 that doesn't pin virtual threads in synchronized methods or blocks and I'm having trouble showing the performance benefits. Can anyone point me in the right direction?",[187764,191971],{"id":191972},"1908141714243572092",[4542,191974,188139],{"id":157703},[651,191976,188297],{},[651,191978,187950,191979,69920,191981,191983],{},[41107,191980],{},[41107,191982],{},[812,191984,53869],{"href":82688,"rel":191985},[816],{"title":674,"searchDepth":790,"depth":790,"links":191987},[191988,191989,191990,191993],{"id":191860,"depth":790,"text":191861},{"id":191876,"depth":790,"text":191877},{"id":191920,"depth":790,"text":191921,"children":191991},[191992],{"id":191932,"depth":892,"text":191933},{"id":157703,"depth":790,"text":188139},{"slug":191995,"date":191996},"local-ai-power","2025-04-06T00:00:00.000Z","/newsletter/2025/04/07/local-ai-power",{"title":191846,"description":191851},"newsletter/2025/04/07/local-ai-power","C36dFyi_aGumBrLFm2tgW6Z1_Hz8znR_SgzD26A82tE",{"id":192002,"title":192003,"body":192004,"description":192225,"extension":793,"meta":192226,"navigation":797,"path":192229,"seo":192230,"stem":192231,"__hash__":192232},"content/newsletter/2025/04/14/spring-ai-m7.md","Spring AI's Final Countdown: Virtual Threads, AWS Integration & Developer Productivity",{"type":648,"value":192005,"toc":192220},[192006,192014,192017,192021,192030,192033,192036,192039,192044,192048,192051,192054,192057,192060,192063,192066,192070,192073,192076,192084,192087,192090,192094,192107,192111,192117,192120,192134,192137,192142,192171,192174,192180,192184,192187,192190,192193,192196,192199,192202,192206,192211],[651,192007,192008,192009,192013],{},"Happy Monday and welcome to another edition of the newsletter! Last week, I tackled an RSS feed issue through good old-fashioned debugging rather than defaulting to AI. After a few minutes of investigation, I discovered that my feed plugin had a default limit on the number of posts to include. With a quick configuration update, the feed was back in business. I've also added a feed link to the ",[812,192010,192012],{"href":165294,"rel":192011},[816],"blog home page"," for those who weren't aware of this option.",[651,192015,192016],{},"This week is packed with exciting developments you won't want to miss. I'm thrilled to share critical news about Spring AI's final milestone release before GA, with significant enhancements for local model deployment. I've created a fresh video demonstrating JDK 24's breakthrough Virtual Threads without Pinning feature that dramatically improves application performance. I'll recap our latest Spring Office Hours episode featuring James Ward from AWS discussing MCP-based agents. Additionally, I'll share highlights from my recent AI for Java Developers talk at Microsoft's JDConf 2025, and announce my upcoming live stream with IntelliJ where I'll show you AI-powered productivity hacks for developers of all skill levels. Let's dive in!",[4542,192018,192020],{"id":192019},"final-countdown-spring-ai-100-m7-marks-last-milestone-before-ga","Final Countdown: Spring AI 1.0.0-M7 Marks Last Milestone Before GA",[651,192022,192023,192024,192029],{},"Spring AI has reached a critical inflection point with the release of version 1.0.0-M7, officially ",[812,192025,192028],{"href":192026,"rel":192027},"https://spring.io/blog/2025/04/10/spring-ai-1-0-0-m7-released",[816],"announced"," on April 10th. This milestone marks the final pre-release before the project moves to Release Candidate status next month, followed quickly by General Availability in time for the Spring I/O conference in Barcelona in May. For teams building AI-powered applications with Spring, now is the perfect time to begin migration planning.",[651,192031,192032],{},"The M7 release introduces significant enhancements focused on local model deployment and improved architecture. Docker Desktop 4.40's Model Runner integration provides a seamless OpenAI-compatible API for local model execution, particularly beneficial for Apple Silicon users who can now run models locally without sending data to external services. Additionally, the release incorporates the latest Model Context Protocol (MCP) reference implementation Java SDK version 0.9.0, featuring session-based architecture and exchange-based interactions that improve handling of multiple concurrent client connections.",[651,192034,192035],{},"Developers should note that the release includes breaking changes to artifact IDs and package structures. Vector store starters have been renamed from spring-ai-{store}-store-spring-boot-starter to spring-ai-starter-vector-store-{store}, while MCP starters have changed from spring-ai-mcp-{type}-spring-boot-starter to spring-ai-starter-mcp-{type}. The Spring AI auto-configuration has also evolved from a monolithic artifact to individual auto-configuration artifacts per model, vector store, and other components—a change designed to minimize dependency conflicts.",[651,192037,192038],{},"If you're developing with Spring AI, I highly recommend upgrading to this milestone release now to ensure a smooth transition to the upcoming GA version. The comprehensive documentation provides detailed migration guidance, and getting ahead of these architectural changes will position your projects for success with the stable 1.0 release next month.",[651,192040,192041],{},[7300,192042,192043],{},"What are you building with Spring AI? Share your projects and experiences in our community forums—we'd love to feature innovative implementations in an upcoming issue!",[4542,192045,192047],{"id":192046},"spring-office-hours-recap-running-mcp-based-agents-on-aws","Spring Office Hours Recap: Running MCP-based Agents on AWS",[651,192049,192050],{},"In our latest episode of Spring Office Hours, my co-host Deshawn Carter and I welcomed James Ward, Principal Developer Advocate at AWS, to discuss the exciting world of Model Context Protocol (MCP) and its implementation on AWS.",[651,192052,192053],{},"James shared how MCP is transforming backend development by making AI integration more accessible to Java developers like us. I was particularly impressed by how Spring AI dramatically simplifies what would require thousands of lines of Python code into just 20 lines of Java. We explored AWS Bedrock service for model inference, including Amazon's Nova models and how they support tool calls essential for MCP integration.",[651,192055,192056],{},"Our conversation covered architectural considerations for deploying MCP servers on AWS using container services like ECS, and James highlighted current protocol limitations with server-sent events and the upcoming streamable HTTP protocol that will enable serverless implementations. I found his insights on how MCP enables dynamic workflows particularly valuable - where LLMs can determine which data sources to query based on natural language prompts, eliminating the need for static dashboards.",[651,192058,192059],{},"If you're a Java developer looking to integrate AI capabilities into your enterprise applications using familiar Spring tools and AWS infrastructure, I highly recommend watching the full episode.",[651,192061,192062],{},"Watch the replay here",[5988,192064],{"id":192065},"U5JDfNvusP0",[11859,192067,192069],{"id":192068},"virtual-threads-without-pinning-in-jdk-24-performance-breakthrough","Virtual Threads Without Pinning in JDK 24: Performance Breakthrough",[651,192071,192072],{},"JDK 24 introduces a significant enhancement to virtual threads that eliminates the \"pinning\" problem that previously limited their scalability. In your demonstration, you showcase how virtual threads in JDK 21 would get pinned to carrier threads when entering synchronized blocks, causing severe performance bottlenecks. With synchronized blocks in JDK 24, virtual threads can now yield their carrier thread during blocking operations, dramatically improving throughput. Your examples show impressive performance gains: execution time dropping from 32 seconds to just 0.46 seconds in a pure Java example, and from 21 seconds to 2.3 seconds in a Spring application under high load.",[651,192074,192075],{},"This improvement is particularly valuable for applications with legacy code relying on synchronized constructs and those performing frequent blocking operations like database access or service calls. The restaurant analogy effectively illustrates how JDK 24 allows \"waiters\" (carrier threads) to serve multiple \"customers\" (tasks) efficiently instead of being tied to a single customer during waiting periods.",[651,192077,192078,192079],{},"Read the full blog post: ",[812,192080,192083],{"href":192081,"rel":192082},"https://www.danvega.dev/blog/jdk-24-virtual-threads-without-pinning",[816],"JDK 24 Virtual Threads Without Pinning",[651,192085,192086],{},"Watch the video:",[5988,192088],{"id":192089},"V4gsffMge7E",[4542,192091,192093],{"id":192092},"microsoft-jdconf-2025","Microsoft JDConf 2025",[651,192095,192096,192097,192101,192102,664],{},"I was honored to be a part of Microsoft's ",[812,192098,192100],{"href":190615,"rel":192099},[816],"Java Developer Conference"," last week. I gave a talk on AI for Java Developers and packed a lot of information into a 30 minute session. Thank you to Microsoft for allowing me to present and a huge thank you to everyone that attended my session. If you want to catch the replay of all the sessions you can check out ",[812,192103,192106],{"href":192104,"rel":192105},"https://www.youtube.com/playlist?list=PLmsFUfdnGr3w0fHizYfyBY7E0r97LULRZ",[816],"this playlist",[11859,192108,192110],{"id":192109},"code-smarter-not-harder-ai-powered-dev-hacks-for-all","\"Code Smarter, Not Harder: AI-Powered Dev Hacks for All\"",[651,192112,192113],{},[660,192114],{"alt":192115,"src":192116},"IntelliJ Livestream","/images/newsletter/2025/04/14/intellij_livestream.png",[651,192118,192119],{},"This practical presentation will demonstrate how AI tools can enhance developer productivity across various aspects of software development. The session will feature real-world examples and live demonstrations showing how AI can improve your workflow through:",[5316,192121,192122,192125,192128,192131],{},[5332,192123,192124],{},"Code completion and bug detection",[5332,192126,192127],{},"Automated refactoring and test generation",[5332,192129,192130],{},"Documentation writing and code generation",[5332,192132,192133],{},"Collaborative programming with AI assistants",[651,192135,192136],{},"The presentation is designed to be accessible for developers of all skill levels and will provide concrete strategies for integrating AI tools into your development routine, regardless of your preferred programming language or environment.",[651,192138,192139],{},[2939,192140,192141],{},"Event Details:",[5316,192143,192144,192150,192156,192162],{},[5332,192145,192146,192149],{},[2939,192147,192148],{},"When:"," April 17, 2025",[5332,192151,192152,192155],{},[2939,192153,192154],{},"Platform:"," IntelliJ Livestream",[5332,192157,192158,192161],{},[2939,192159,192160],{},"Registration:"," Free",[5332,192163,192164,13517,192167],{},[2939,192165,192166],{},"Registration Link:",[812,192168,192169],{"href":192169,"rel":192170},"https://info.jetbrains.com/idea-livestream-april17-2025.html",[816],[651,192172,192173],{},"If you're looking to enhance your coding efficiency with AI-powered tools, this presentation should provide valuable insights and practical techniques you can implement immediately.",[651,192175,192176,192177,184565],{},"As always if you want to find out when and where I will be speaking next you can check out the ",[812,192178,120451],{"href":120449,"rel":192179},[816],[651,192181,192182],{},[2939,192183,187762],{},[651,192185,192186],{},"I'm heading back to KCDC!",[187764,192188],{"id":192189},"1911743914781950282",[651,192191,192192],{},"SpringOne at VMware Explore is taking place in Las Vegas later this year!",[187764,192194],{"id":192195},"1910038229887184974",[651,192197,192198],{},"Really excited to share this video I worked on for Spring AI",[187764,192200],{"id":192201},"1909998057963274388",[651,192203,192204],{},[2939,192205,188139],{},[651,192207,188142,192208,188147],{},[812,192209,51474],{"href":51472,"rel":192210},[816],[651,192212,187950,192213,69920,192215,192217],{},[41107,192214],{},[41107,192216],{},[812,192218,53869],{"href":82688,"rel":192219},[816],{"title":674,"searchDepth":790,"depth":790,"links":192221},[192222,192223,192224],{"id":192019,"depth":790,"text":192020},{"id":192046,"depth":790,"text":192047},{"id":192092,"depth":790,"text":192093},"Happy Monday and welcome to another edition of the newsletter! Last week, I tackled an RSS feed issue through good old-fashioned debugging rather than defaulting to AI. After a few minutes of investigation, I discovered that my feed plugin had a default limit on the number of posts to include. With a quick configuration update, the feed was back in business. I've also added a feed link to the blog home page for those who weren't aware of this option.",{"slug":192227,"date":192228},"spring-ai-m7","2025-04-14T00:00:00.000Z","/newsletter/2025/04/14/spring-ai-m7",{"title":192003,"description":192225},"newsletter/2025/04/14/spring-ai-m7","I9wjpECwl67_h-zE5PojoGBLSqCJP-vzyPpz65f9dOs",{"id":192234,"title":192235,"body":192236,"description":192240,"extension":793,"meta":192438,"navigation":797,"path":192440,"seo":192441,"stem":192442,"__hash__":192443},"content/newsletter/2025/04/27/jetbrains-junie-goes-public.md","JetBrains Junie goes public, Building MCP Servers in Java & Presentation Recordings",{"type":648,"value":192237,"toc":192430},[192238,192241,192244,192250,192258,192261,192265,192274,192277,192281,192284,192287,192294,192297,192301,192304,192308,192311,192314,192320,192326,192332,192338,192344,192347,192350,192353,192355,192358,192361,192364,192367,192393,192396,192399,192402,192406,192409,192412,192416,192421],[651,192239,192240],{},"Happy Monday and welcome to another edition of the newsletter. It's hard to believe that we will flip the calendars to May later this week, but I am all for it. The weather is starting to turn the corner, and I am able to get outside and enjoy the sunshine. I don't know about you, but I work from my basement and I feel like I have been \"locked up\" for the winter, and it's definitely done a number on me mentally.",[651,192242,192243],{},"I missed last week's newsletter because I have been swamped trying to get caught up on the book that I'm writing. It's been a lot of hard work, and we are nearing the production finish line, not the actual finish line but a huge milestone nonetheless. The plan if for this book to be released early fall if everything comes together and I couldn't be more excited about it. I don't think I have shared this with anyone yet, but we have a color copy of what will be the cover of our book.",[651,192245,192246],{},[660,192247],{"alt":192248,"src":192249},"Fundamentals of Spring 6 book cover","/images/newsletter/2025/04/27/FOSE_cover.png",[651,192251,192252,192253,192257],{},"If you're on the ",[812,192254,192256],{"href":78058,"rel":192255},[816],"O'Reilly Platform"," you can get early access to the book right now!",[651,192259,192260],{},"This week I want to tell you about a couple of videos I made on JetBrains Junie and Building an MCP server with pure Java. I also gave some presentations over the last 2 weeks and I want to share those with you.",[4542,192262,192264],{"id":192263},"jetbrains-junie-goes-public","JetBrains Junie goes Public",[651,192266,192267,192268,192273],{},"Last week, JetBrains released their AI coding agent \"",[812,192269,192272],{"href":192270,"rel":192271},"https://www.jetbrains.com/junie/",[816],"Junie","\" to the public by removing it from early access. I decided to sit down and create an introduction to Junie for Java/Spring developers by going through a couple of quick examples. First I took a look at how you can use the chat feature to learn more about working with existing codebases. Next I had it create me CRUD REST API in Spring and asked it to do retrieve data from a public API. After a pretty decent application I showed off how you can create a guidelines doc to help guide the AI agent to your coding standards and specifications. Overall I thought this was a pretty good tutorial and I hope you enjoy it.",[5988,192275],{"id":192276},"fcbSG8lm7So",[4542,192278,192280],{"id":192279},"building-a-lightweight-mcp-server-with-pure-java-sdk","Building a Lightweight MCP Server with Pure Java SDK",[651,192282,192283],{},"In this week's tutorial, I show you how to implement a Model Context Protocol (MCP) server using only the core Java SDK — no Spring Framework required! This streamlined approach gives you more flexibility with lighter dependencies for projects where Spring isn't necessary.",[651,192285,192286],{},"The video walks through the complete process: setting up core MCP Java dependencies, implementing a server using standard Java SDK, working with STDIO transport implementations, and testing with the MCP Inspector tool. I build a simple service that provides JavaOne presentations data to demonstrate how easily you can create custom tools that LLMs like Claude can access. Perfect for developers who want to understand the fundamentals of MCP server implementation without framework overhead.",[651,192288,192289,192290,1223],{},"If you've been following my MCP series, this tutorial expands your toolkit with a more flexible, lightweight alternative. Check out the complete walkthrough below and grab the source code from our ",[812,192291,110300],{"href":192292,"rel":192293},"https://github.com/danvega/javaone-mcp",[816],[5988,192295],{"id":192296},"Y_Rk6QgWUbE",[4542,192298,192300],{"id":192299},"presentation-recordings","Presentation Recordings",[651,192302,192303],{},"I gave a couple of presentations over the last 2 weeks, and they happen to have been recorded. Thanks to both of these groups for inviting me to speak. If you would like me to speak at your meetup group or conference please feel free to reach out to me.",[5909,192305,192307],{"id":192306},"ai-powered-developer-hacks-work-smarter-not-harder","AI-Powered Developer Hacks: Work Smarter, Not Harder",[651,192309,192310],{},"In my recent JetBrains livestream presentation, I shared practical ways developers can leverage AI tools to enhance productivity without replacing core skills. While many worry AI will replace developers, I firmly believe that \"developers who use AI will replace those who don't\" - finding the right balance is key to staying competitive.",[651,192312,192313],{},"I demonstrated how AI can transform everyday development workflows through several powerful applications:",[651,192315,192316,192319],{},[2939,192317,192318],{},"Effective Prompting Strategies",": I showed how structured, detailed prompts with clear context yield dramatically better results than vague requests. Using voice dictation for complex prompts can also save significant time compared to typing lengthy instructions.",[651,192321,192322,192325],{},[2939,192323,192324],{},"Learning Acceleration",": AI excels as a learning companion, helping both junior developers break down complex concepts and senior developers explore architectural patterns or emerging technologies. Creating personalized learning paths for new technologies has become one of my favorite applications.",[651,192327,192328,192331],{},[2939,192329,192330],{},"Code Comprehension",": With developers spending roughly 10x more time reading code than writing it, AI can dramatically improve onboarding to unfamiliar codebases by explaining design patterns, identifying dependencies, and suggesting refactoring opportunities.",[651,192333,192334,192337],{},[2939,192335,192336],{},"Documentation & Technical Writing",": AI significantly streamlines creating class documentation, API endpoint descriptions, and README files - mundane but essential tasks that often get neglected.",[651,192339,192340,192343],{},[2939,192341,192342],{},"Custom Tool Building",": I demonstrated how AI enables building developer tools that would previously require significant learning investment, sharing how I created a Raycast extension for Spring Boot project initialization despite limited React experience.",[651,192345,192346],{},"The presentation concluded with guidance on running AI models locally for security-sensitive work and how to effectively guide AI coding assistants with personalized guidelines files - showing how Junie can adapt to team coding conventions when properly instructed.",[651,192348,192349],{},"Remember - you are the pilot, not the passenger. Use AI to enhance your capabilities while maintaining critical oversight and understanding of the code it produces.",[5988,192351],{"id":192352},"85tXw0uUN04",[5909,192354,191286],{"id":191285},[651,192356,192357],{},"In my recent session for Huawei's AI Echoes series, I demonstrated how Java developers can leverage Spring AI to build intelligent applications without needing to switch to Python. This powerful framework provides a consistent abstraction layer for working with various AI providers.",[651,192359,192360],{},"I walked through several key capabilities of Spring AI, demonstrating with working code samples:",[651,192362,192363],{},"First, I showed how easily a Spring Boot app can connect to large language models using a simple REST controller and the ChatClient interface. With just a few lines of configuration for your chosen AI provider (OpenAI in our example), you can start making chat completions or stream responses back to users.",[651,192365,192366],{},"The framework excels at making complex AI patterns accessible, including:",[5316,192368,192369,192375,192381,192387],{},[5332,192370,192371,192374],{},[2939,192372,192373],{},"Structured Output:"," Rather than receiving unstructured text, Spring AI can map responses directly to Java types, making integration with your domain model seamless.",[5332,192376,192377,192380],{},[2939,192378,192379],{},"Multi-modal Support:"," Beyond text, I demonstrated sending images to the LLM and receiving detailed descriptions in return.",[5332,192382,192383,192386],{},[2939,192384,192385],{},"Memory Management:"," Adding conversation memory through the ChatMemoryAdvisor makes it trivial to maintain context across stateless HTTP requests.",[5332,192388,192389,192392],{},[2939,192390,192391],{},"Tool Integration:"," The @Tool annotation lets you extend AI capabilities by connecting to external systems like databases, APIs, or even simple utilities like getting the current date.",[651,192394,192395],{},"Version 1.0.0-M7 is the last milestone release, with GA expected in May before Spring I/O Barcelona. If you're building enterprise applications and need to incorporate AI capabilities, Spring AI provides a familiar, flexible approach that leverages your existing Java expertise.",[651,192397,192398],{},"The full code examples are available in my Spring AI workshop repository on GitHub.",[5988,192400],{"id":192401},"omn3SrDSPJg",[651,192403,192404],{},[2939,192405,187762],{},[187764,192407],{"id":192408},"1913226370982740262",[187764,192410],{"id":192411},"1912593645926244787",[651,192413,192414],{},[2939,192415,188139],{},[651,192417,188142,192418,188147],{},[812,192419,51474],{"href":51472,"rel":192420},[816],[651,192422,187950,192423,69920,192425,192427],{},[41107,192424],{},[41107,192426],{},[812,192428,53869],{"href":82688,"rel":192429},[816],{"title":674,"searchDepth":790,"depth":790,"links":192431},[192432,192433,192434],{"id":192263,"depth":790,"text":192264},{"id":192279,"depth":790,"text":192280},{"id":192299,"depth":790,"text":192300,"children":192435},[192436,192437],{"id":192306,"depth":892,"text":192307},{"id":191285,"depth":892,"text":191286},{"slug":192263,"date":192439},"2025-04-27T00:00:00.000Z","/newsletter/2025/04/27/jetbrains-junie-goes-public",{"title":192235,"description":192240},"newsletter/2025/04/27/jetbrains-junie-goes-public","OmwMDZuX5Mi0Y5KXw20ET0U86tAfEHJd5jw-aVOuVo0",{"id":192445,"title":192446,"body":192447,"description":192451,"extension":793,"meta":192756,"navigation":797,"path":192759,"seo":192760,"stem":192761,"__hash__":192762},"content/newsletter/2025/05/05/spring-boot-3-last-dance.md","The Last Dance of Spring Boot 3.x: Windsurf AI, Testcontainers, and a Keynote Announcement",{"type":648,"value":192448,"toc":192745},[192449,192452,192455,192461,192475,192478,192482,192485,192488,192491,192523,192526,192529,192532,192535,192539,192542,192545,192550,192564,192569,192583,192588,192622,192626,192629,192640,192644,192647,192650,192652,192682,192685,192688,192691,192694,192697,192701,192709,192712,192717,192720,192724,192727,192731,192736],[651,192450,192451],{},"Happy Monday and welcome to another edition of the newsletter. I'm writing this on a Sunday as I'm in the airport heading out to Charleston, SC to visit a customer. One of the many perks of my job is that i get to travel to conferences and customers all around the world. Customers offer an opportunity to get in the room with the developers using various projects in the Spring ecosystem.",[651,192453,192454],{},"One off the presentations I'm giving tomorrow is a \"State of Spring\" where we will talk about where we are and where we are heading. It's hard to believe that Spring Boot 3.0 was released in November of 2022. I bring this up because later this month Spring Boot 3.5 will be released and this will be the last release in the 3.x as we prepare for Spring Boot 4.0 in November.",[651,192456,192457],{},[660,192458],{"alt":192459,"src":192460},"Spring Boot Release Timeline","/images/newsletter/2025/05/05/springboot3.png",[651,192462,192463,192464,192468,192469,192474],{},"I also had some time to think about a lot of my favorite features in the 3.x line and decided to submit a talk about it for ",[812,192465,82113],{"href":192466,"rel":192467},"https://www.vmware.com/explore/us/springone",[816],". I ended up submitting ",[812,192470,192473],{"href":192471,"rel":192472},"https://github.com/danvega/abstracts/blob/main/spring-one-2025.md",[816],"8 different talks"," on a variety of subjects so fingers 🤞🏻 crossed that one of them get selected.",[651,192476,192477],{},"There are a lot of really great AI powered tools for developers on the market today. In today's edition of the newsletter I will continue my exploration of them by introducing you to Windsurf. I also want to share a conversation I had with Eddú Meléndez over at Docker on Testcontainers and Docker Model Runner. Finally, I'll share a presentation I did at JavaOne and some big conference news!",[4542,192479,192481],{"id":192480},"windsurf-the-agentic-code-editor","Windsurf: The Agentic Code Editor",[651,192483,192484],{},"Last week I mentioned my video on JetBrains Junie, and this week I'm continuing my AI Coding Agents series with a first look at Windsurf. In this video, I explore what makes Windsurf stand out in the crowded AI coding tools market.",[651,192486,192487],{},"Windsurf represents the latest evolution in AI-assisted development - what I call \"agentic AI IDE environments.\" Unlike traditional chatbots (ChatGPT, Claude) or inline IDE assistants (GitHub Copilot, JetBrains AI), Windsurf acts as a proactive coding partner that can manage multi-step tasks autonomously. It's like having a smart junior developer who takes initiative, suggests refactoring, and coordinates changes across your codebase.",[651,192489,192490],{},"In the video, I demonstrate building a Spring Boot CRUD REST API that connects to the JSONPlaceholder service. Here are the highlights:",[5316,192492,192493,192499,192505,192511,192517],{},[5332,192494,192495,192498],{},[2939,192496,192497],{},"Fast and Responsive",": Windsurf is noticeably quick at making edits and changes compared to other tools",[5332,192500,192501,192504],{},[2939,192502,192503],{},"Model Flexibility",": You can choose between different AI models (GPT-4.0, Claude, etc.) based on your needs",[5332,192506,192507,192510],{},[2939,192508,192509],{},"Customizable Rules",": The ability to set global and workspace rules to match your coding style is a game-changer. For example, I prefer Java records over classes for immutability",[5332,192512,192513,192516],{},[2939,192514,192515],{},"Contextual Understanding",": Windsurf understands your entire project context, making it excellent for established codebases",[5332,192518,192519,192522],{},[2939,192520,192521],{},"Memory Feature",": It learns from your preferences over time, becoming more aligned with your coding style",[651,192524,192525],{},"The demo includes implementing caching with Spring's built-in cache abstraction and shows how Windsurf handles complex tasks like refactoring from RestTemplate to RestClient.",[651,192527,192528],{},"There's also exciting news that OpenAI might be acquiring Windsurf for around $3 billion, which could signal big changes ahead for this tool. If you're a Visual Studio Code user looking for a powerful AI coding assistant, Windsurf is definitely worth checking out. Remember, though - you're the pilot, not the passenger. Use these tools to enhance your productivity while maintaining control of your code.",[651,192530,192531],{},"You can watch the full video below and use my referral link in the description if you'd like to try Windsurf yourself.",[5988,192533],{"id":192534},"XkRM4eO885I",[4542,192536,192538],{"id":192537},"spring-office-hours-testcontainers-and-docker-with-eddú-meléndez","Spring Office Hours- Testcontainers and Docker with Eddú Meléndez",[651,192540,192541],{},"Last week on Spring Office Hours, we had the pleasure of interviewing Eddu Melendez, a Software Engineer at Docker who joined through the Atomic Jar acquisition. We dove deep into Testcontainers, Docker Model Runner, and how these tools are revolutionizing both testing and AI development.",[5909,192543,192544],{"id":187412},"Key Takeaways:",[651,192546,192547],{},[2939,192548,192549],{},"Test Containers Evolution",[5316,192551,192552,192555,192561],{},[5332,192553,192554],{},"Test containers started as a solution for managing containers programmatically in tests",[5332,192556,192557,192558,192560],{},"Now integrated seamlessly with Spring Boot 3.1+ using the ",[676,192559,179934],{}," annotation (no more manual configuration!)",[5332,192562,192563],{},"Can be used for both testing AND local development with Spring Boot's test run feature",[651,192565,192566],{},[2939,192567,192568],{},"Docker Model Runner",[5316,192570,192571,192574,192577,192580],{},[5332,192572,192573],{},"Docker's new feature for running AI models locally",[5332,192575,192576],{},"Provides an OpenAI-compatible API, making it easy to integrate with Spring AI",[5332,192578,192579],{},"Available on macOS (Apple Silicon) and now Windows with NVIDIA GPUs",[5332,192581,192582],{},"Supports tool calling for models that have this capability",[651,192584,192585],{},[2939,192586,192587],{},"Key Features Discussed:",[27665,192589,192590,192596,192606,192612],{},[5332,192591,192592,192595],{},[2939,192593,192594],{},"Dev-time Test Containers",": You can now use the same test container configuration for both testing and local development, eliminating the need to maintain separate Docker Compose files",[5332,192597,192598,192601,192602,192605],{},[2939,192599,192600],{},"New Elasticsearch SSL Support",": Spring Boot 3.5 introduces an ",[676,192603,192604],{},"@SSL"," annotation specifically for secure Elasticsearch connections",[5332,192607,192608,192611],{},[2939,192609,192610],{},"Docker Model Runner Integration",": Test containers now has built-in support for Docker Model Runner, simplifying AI model testing with just one line of code",[5332,192613,192614,192617,192618,192621],{},[2939,192615,192616],{},"Docker Compose Updates",": New ",[676,192619,192620],{},"provider"," type in Docker Compose for AI models, making it easier to share AI-powered applications",[5909,192623,192625],{"id":192624},"practical-example","Practical Example",[651,192627,192628],{},"Eddu demonstrated how to evaluate AI models using test containers:",[5316,192630,192631,192634,192637],{},[5332,192632,192633],{},"Use Docker Model Runner container in your tests",[5332,192635,192636],{},"Automatically configure Spring AI to use local models",[5332,192638,192639],{},"Run evaluations and sentiment analysis tests against your models",[5909,192641,192643],{"id":192642},"notable-quotes","Notable Quotes",[651,192645,192646],{},"When I asked about using microservices as test containers, Eddu wisely noted: \"We don't advocate for that very often... because the microservice B depends on other services, maybe a database, maybe Kafka... it could be really heavy for your machine.\"",[651,192648,192649],{},"On the Java ecosystem: \"I feel really lucky because we have a great ecosystem, really good tools, build tools, a great framework, testing frameworks as well.\"",[5909,192651,21931],{"id":21930},[5316,192653,192654,192661,192668,192675],{},[5332,192655,192656],{},[812,192657,192660],{"href":192658,"rel":192659},"https://spring.io/blog/2025/01/23/spring-ai-with-docker",[816],"Spring AI and Docker Model Runner Article",[5332,192662,192663],{},[812,192664,192667],{"href":192665,"rel":192666},"https://hub.docker.com/r/ai",[816],"Docker Hub AI Models",[5332,192669,192670],{},[812,192671,192674],{"href":192672,"rel":192673},"https://java.testcontainers.org/",[816],"Test Containers for Java",[5332,192676,192677],{},[812,192678,192681],{"href":192679,"rel":192680},"https://docs.docker.com/model-runner/",[816],"Docker Model Runner in Docker Desktop 4.40+",[651,192683,192684],{},"You can watch the full episode here:",[5988,192686],{"id":192687},"p3NfPM1SKLg",[4542,192689,192690],{"id":191595},"JavaOne 2025",[651,192692,192693],{},"I was lucky to attend and speak at my first JavaOne Conference back in March. Just last week the recording of my presentation was posted on their YouTube channel. If you want to learn more about frontend development as a Java development please consider checking it out.",[5988,192695],{"id":192696},"CLgWfNnsDiM",[4542,192698,192700],{"id":192699},"dev2next-2025","dev2Next 2025",[651,192702,192703,192704,192708],{},"I am beyond excited to announce that I will be attending and speaking at ",[812,192705,192707],{"href":185608,"rel":192706},[816],"dev2next"," this year. I was lucky to have 4 submissions accepted to this conference including the keynote. That's right, alongside my good friend Nate Schutta I will be giving a keynote talk at the conference. Nate and I have a book coming out titled \"The Fundamentals of Software Engineering\".",[651,192710,192711],{},"I hear a lot of people saying that with AI taking over software development the fundamentals no longer matter. Nate and I have a much different perspective on this and think the fundamentals are more important than ever. Here is the title and the abstract for the keynote and we hope to see you in Colorado Springs in the fall.",[651,192713,192714],{},[2939,192715,192716],{},"Fundamentals of Software Engineering In the age of AI",[651,192718,192719],{},"Agentic coding assistants and editor based AI chat interfaces are altering the development workflow leading some to proclaim the end of software engineering. Is it time to explore other careers? Not so fast, the rumors of our demise are greatly exaggerated! While these tools can boost productivity, to be used effectively, developers still need to master the fundamentals of the software craft. Modern software development demands more than just coding proficiency—it requires navigating an increasingly AI-augmented landscape.",[651,192721,192722],{},[2939,192723,187762],{},[187764,192725],{"id":192726},"1917744566008176914",[651,192728,192729],{},[2939,192730,188139],{},[651,192732,188142,192733,188147],{},[812,192734,51474],{"href":51472,"rel":192735},[816],[651,192737,187950,192738,69920,192740,192742],{},[41107,192739],{},[41107,192741],{},[812,192743,53869],{"href":82688,"rel":192744},[816],{"title":674,"searchDepth":790,"depth":790,"links":192746},[192747,192748,192754,192755],{"id":192480,"depth":790,"text":192481},{"id":192537,"depth":790,"text":192538,"children":192749},[192750,192751,192752,192753],{"id":187412,"depth":892,"text":192544},{"id":192624,"depth":892,"text":192625},{"id":192642,"depth":892,"text":192643},{"id":21930,"depth":892,"text":21931},{"id":191595,"depth":790,"text":192690},{"id":192699,"depth":790,"text":192700},{"slug":192757,"date":192758},"spring-boot-3-last-dance","2025-05-05T00:00:00.000Z","/newsletter/2025/05/05/spring-boot-3-last-dance",{"title":192446,"description":192451},"newsletter/2025/05/05/spring-boot-3-last-dance","nmqX5ZetLAKOyRMdD6yNhC9Z1jzMxMIuIMbzePMsznY",{"id":192764,"title":192765,"body":192766,"description":192770,"extension":793,"meta":192935,"navigation":797,"path":192938,"seo":192939,"stem":192940,"__hash__":192941},"content/newsletter/2025/05/12/spring-forward-ai-milestones-barcelona-bound-and-cloud-evolution.md","Spring Forward: AI Milestones, Barcelona Bound, and Cloud Evolution",{"type":648,"value":192767,"toc":192926},[192768,192771,192774,192777,192781,192784,192792,192795,192799,192802,192805,192808,192811,192814,192818,192827,192830,192834,192837,192841,192847,192853,192879,192885,192891,192894,192897,192900,192904,192907,192910,192912,192917],[651,192769,192770],{},"Happy Monday and welcome to another edition of the newsletter. You know that feeling when everything in your tech world suddenly aligns? That's my week. Just back from Charleston's charming King Street where I witnessed firsthand how Tanzu Platform is transforming deployments (\"Here's my code, run it for me\" - music to a developer's ears!), I'm already packing for Barcelona and Spring I/O 2025.",[651,192772,192773],{},"With Spring AI hitting its final milestone before 1.0, Spring Cloud 2025 shaping the future of distributed systems, and AI-powered development becoming our daily reality, we're living through one of the most exciting transformations in the Java ecosystem. Whether you're curious about S3 configuration support, Spring Framework 7's upcoming API versioning, or how to actually use AI to code smarter (not harder), I've got you covered this week.",[651,192775,192776],{},"Plus, did you catch that OpenAI-Windsurf acquisition news? Three billion reasons why the AI development landscape just got more interesting. Let's dive in!",[4542,192778,192780],{"id":192779},"southern-hospitality-meets-spring-technology","Southern Hospitality Meets Spring Technology",[651,192782,192783],{},"Last week I had a quick trip to Charleston, SC to visit a customer. I have had the pleasure of visiting the great state of South Carolina before, in fact my family and I love visiting Hilton Head and are planning to head there this summer. I have never had the opportunity until now to visit Charleston and I have to say for the 2 days I was there it was a wonderful place to visit. I arrived on Sunday and stayed down on King Street which is a really cool area lined with shopping and restaurants. The streets were packed on Sunday with everyone dressed in their Southern Sunday best and I really enjoyed it there.",[651,192785,192786,192787,664],{},"The customer visit went great where I had the opportunity to give 2 talks. The first was on the current state of Spring. With Spring 3.5 coming later this month, and the last minor release in the 3.x line it was an opportunity to reflect on all of the wonderful features Spring Boot 3.x has given us. I also had the chance to take a look at what's coming in Spring Boot 4 and Spring Framework 7 later this year. While this session wasn't recorded I did give a similar talk at Microsoft's JDConf last month and you can find a recording of that ",[812,192788,192791],{"href":192789,"rel":192790},"https://youtu.be/uwjwBUsnVhY",[816],"video here",[651,192793,192794],{},"I also gave an introduction to Spring AI which was a lot of fun. This customer, like most I get to talk to these days is heavily invested in AI and I can't wait to see what they do with Spring AI. This customer was also a huge user of Tanzu Platform and I loved hearing how much they love the cf push experience. Here is my code, run it for me. They kept telling us how they couldn't imagine deployments without it.",[4542,192796,192798],{"id":192797},"spring-io-2025","Spring I/O 2025",[651,192800,192801],{},"Next week I'm heading to Europe for a week and I'm really looking forward to this trip. I'm stopping in Amsterdam for a day for a customer visit and then I'm off to the great city of Barcelona for Spring I/O 2025. On Monday May 12 at 10 AM EDT I will be interviewing the great Sergi Almar, the man behind the conference. If you miss the livestream you can always catch the replay or listen to it wherever you get your podcasts.",[5988,192803],{"id":192804},"kxYMqcC0HV4",[651,192806,192807],{},"Last year was my first time at Spring I/O and as a Spring Developer I was in awe of the conference. Usually when I go to conferences I can seek out my fellow Java / Spring developers but there they are everywhere. The conference is packed with amazing content and the hallway tracks are filled with great conversations.",[651,192809,192810],{},"I will be giving a talk titled \"Code Smarter, Not Harder: AI-Powered Dev Hacks for All\". In this talk you'll discover how to harness AI-powered tools for everyday tasks like code generation, documentation writing, and collaborative programming with AI assistants. Through practical demonstrations, we'll show how to integrate these technologies seamlessly into your development routine, no matter which programming language or development environment you use.",[651,192812,192813],{},"I will also have a small part on the mainstage prior to the keynote. If you're going to be at the conference I would really love to meetup at chat. I'm taking all of my camera equipment and I'm going to try and record a bunch of content while I'm there.",[4542,192815,192817],{"id":192816},"spring-ai-m8","Spring AI M8",[651,192819,192820,192821,192826],{},"Last week ",[812,192822,192825],{"href":192823,"rel":192824},"https://spring.io/blog/2025/04/30/spring-ai-1-0-0-m8-released",[816],"Spring AI 1.0.0 M8"," was released and this will now be the final milestone release before the release candidate and GA release later this month. While M7 was going to the final milestone the team realized that instead of introducing a breaking change in the release candidate it would be better to get this into M8.",[651,192828,192829],{},"There is going to be a bunch of exciting talks, blog posts and videos coming out around the release of Spring AI which is nearly 2 years in the making. This project is amazing and I'm so excited for to hit 1.0. I'm working on a full course that will teach you everything you need to know to get up and running with Spring AI and my plan is to release that the week after Spring I/O. I'm doing this so I can record all of the videos using the brand new 1.0 release.",[4542,192831,192833],{"id":192832},"spring-cloud-2025-new-features-future-direction","Spring Cloud 2025: New Features & Future Direction",[651,192835,192836],{},"I caught up with Spring Cloud veterans Ryan Baxter and Spencer Gibb to discuss what's new in the upcoming 2025.0 release and where the project is heading.",[5909,192838,192840],{"id":192839},"key-highlights","Key Highlights",[651,192842,192843,192846],{},[2939,192844,192845],{},"Spring Cloud Explained Simply",": Spencer shared a perfect mental model - Spring Boot handles single processes, Spring Cloud manages multiple processes. It's the abstraction layer for distributed systems, letting you choose between Eureka/Consul for service discovery or Git/S3/Vault for configuration without vendor lock-in.",[651,192848,192849,192852],{},[2939,192850,192851],{},"Major Features in 2025.0 RC1"," (GA release targeted for May):",[5316,192854,192855,192861,192867,192873],{},[5332,192856,192857,192860],{},[2939,192858,192859],{},"S3 Configuration Support",": Store config directly in AWS S3 buckets",[5332,192862,192863,192866],{},[2939,192864,192865],{},"Composite Configurations",": Pull from multiple sources (Git, S3, config maps) through a single config server",[5332,192868,192869,192872],{},[2939,192870,192871],{},"Gateway Gets Messaging",": Route HTTP requests directly to message brokers via Spring Cloud Stream/Function integration",[5332,192874,192875,192878],{},[2939,192876,192877],{},"Clearer Gateway Naming",": Four distinct variants now clearly named (Server WebFlux/MVC, Proxy Exchange WebFlux/MVC)",[651,192880,192881,192884],{},[2939,192882,192883],{},"Spring Framework 7 Prep",": The new API versioning features coming in Framework 7 will integrate seamlessly with Gateway for version-based routing and load balancing.",[651,192886,192887,192890],{},[2939,192888,192889],{},"Community Feedback Needed",": With a small team, the Spring Cloud team relies heavily on community input. They specifically want feedback on the new gateway messaging features and configuration repository preferences.",[651,192892,192893],{},"This marks the first time Spring Cloud will have two release trains in one year - preparing for the major changes coming with Spring Framework 7 and Boot 4 in November while delivering important features now.",[651,192895,192896],{},"Watch the full replay below for technical details, live Q&A, and deeper insights into Spring Cloud's evolution.",[5988,192898],{"id":192899},"xQTke50Nvcc",[651,192901,192902],{},[2939,192903,187762],{},[651,192905,192906],{},"I teased this last week but it was made official. OpenAI is acquiring Windsurf for 3 billion",[187764,192908],{"id":192909},"1919735284133962063",[4542,192911,188139],{"id":157703},[651,192913,188142,192914,188147],{},[812,192915,51474],{"href":51472,"rel":192916},[816],[651,192918,187950,192919,69920,192921,192923],{},[41107,192920],{},[41107,192922],{},[812,192924,53869],{"href":82688,"rel":192925},[816],{"title":674,"searchDepth":790,"depth":790,"links":192927},[192928,192929,192930,192931,192934],{"id":192779,"depth":790,"text":192780},{"id":192797,"depth":790,"text":192798},{"id":192816,"depth":790,"text":192817},{"id":192832,"depth":790,"text":192833,"children":192932},[192933],{"id":192839,"depth":892,"text":192840},{"id":157703,"depth":790,"text":188139},{"slug":192936,"date":192937},"spring-forward-ai-milestones-barcelona-bound-and-cloud-evolution","2025-05-12T00:00:00.000Z","/newsletter/2025/05/12/spring-forward-ai-milestones-barcelona-bound-and-cloud-evolution",{"title":192765,"description":192770},"newsletter/2025/05/12/spring-forward-ai-milestones-barcelona-bound-and-cloud-evolution","my6-S64rCzRk1cyt8BfQpTP03gIBnEItileJERB_NSs",{"id":192943,"title":192944,"body":192945,"description":192949,"extension":793,"meta":193121,"navigation":797,"path":193123,"seo":193124,"stem":193125,"__hash__":193126},"content/newsletter/2025/05/27/spring-io-2025-recap.md","Spring I/O 2025 Recap, Spring Boot 3.5 and Spring AI hits 1.0 GA",{"type":648,"value":192946,"toc":193112},[192947,192950,192953,192956,192959,192969,192972,192976,192979,192982,192985,192989,192992,192998,193001,193004,193007,193010,193014,193017,193020,193030,193033,193037,193040,193050,193053,193059,193063,193069,193072,193075,193083,193087,193090,193093,193096,193099,193101,193103],[651,192948,192949],{},"Happy Monday and welcome to another edition of the newsletter. Last week I had the pleasure of making a trip over to Europe to visit Amsterdam and Barcelona. I'm very grateful that my position allows me to visit some of the most amazing places in the world and talk about the things I'm genuinely passionate about.",[651,192951,192952],{},"It was a long week but I made so many wonderful memories and it was well worth it. My trip started out in Amsterdam where I was able to visit with a customer of ours and give 2 presentations. I was able to start off with a what's new in Spring talk where I took a look back at all the cool things we got in the Spring Boot 3.x line. This is because Spring Boot 3.5 was released last week and this will be the last minor release in the 3.x line. While I was away DaShaun hosted a special Spring Office Hours with Phil Webb to talk about everything in Spring Boot 3.5.",[5988,192954],{"id":192955},"4fHPTERqtEA",[651,192957,192958],{},"Next up I gave a nice introduction to Spring AI. This talk went really well and I got a lot of really great questions about the project. I think I mention this every time I talk about AI but I really love geeking out over it. This was my first time in Amsterdam and while my trip was short it was definitely a good one. I was really tired after only getting about 4.5 hours of interrupted sleep on the plane but I couldn't pass up this chance to see the city. I was able to head into Amsterdam and check out the city and have a nice dinner with coworkers. I hope I get the opportunity to come back one day and explore the city more.",[651,192960,192961,192965],{},[660,192962],{"alt":192963,"src":192964},"Amsterdam Photo 1","/images/newsletter/2025/05/27/amsterdam-1.jpeg",[660,192966],{"alt":192967,"src":192968},"Amsterdam Photo 2","/images/newsletter/2025/05/27/amsterdam-2.jpeg",[651,192970,192971],{},"In this edition of the newsletter I want to tell you about my trip to Barcelona for my 2nd Spring I/O, and I'll also talk about the release of Spring AI 1.0.",[4542,192973,192975],{"id":192974},"spring-io-2025-recap","Spring I/O 2025 Recap",[651,192977,192978],{},"As I mentioned this was my 2nd trip to the great city of Barcelona Spain for the best Spring Conference on the planet, Spring I/O. Because of my trip to Amsterdam I was able to get into Barcelona Wednesday afternoon rested and ready to go. I headed to the speaker dinner that night and as always that did not disappoint. The food and drinks were amazing but the conversations were the best part as always. I had a really great conversation with the great Taylor Desseyn. He has a content creator that travels with him and until that night I didn't realize that this is what I need in my life. I had a great conversation with them about creating content on the road and the different gear and tools involved to make this happen.",[187764,192980],{"id":192981},"1925260125045940489",[651,192983,192984],{},"I didn't stay long because I had a long first day at Spring I/O and I wanted to make sure I got a good night sleep.",[5909,192986,192988],{"id":192987},"spring-io-keynote","Spring I/O Keynote",[651,192990,192991],{},"I woke up early on Thursday morning because I had to be there a little bit before the crowd got in because I was going to be in the keynote. I work for the Tanzu Division at Broadcom and we are a major sponsor of Spring I/O. Because of that I got to go up on stage and say something nice about the great company that allows me to do what I love every day. I had to memorize a 2 minute speech that were backed by 6 slides.",[651,192993,192994],{},[660,192995],{"alt":192996,"src":192997},"Dan Vega Keynote","/images/newsletter/2025/05/27/keynote.png",[651,192999,193000],{},"I thought I did a pretty good job even with being a little nervous having to memorize a script. For me, I would much rather give a 4 hour workshop than have to memorize a 2-minute script. I didn't get any pictures of me on stage during the keynote so if you happen to have gotten any please send them over.",[651,193002,193003],{},"After I was done the keynote was ready to get started. The great Juergen Hoeller led us off by talking about all the new and exciting features coming in Spring Framework 7 and Spring Boot 4. As always I thought he did an amazing job of giving us a glimpse into what is coming next.",[651,193005,193006],{},"There were a bunch of really great presentations after that but I won't spoil them for you. If you haven't had a chance to check out the keynote it has already been posted to the Spring I/O channel. Big kudos to everyone involved in the keynote 👏🏻",[5988,193008],{"id":193009},"oUK1Np4OvnM",[5909,193011,193013],{"id":193012},"code-smarter-not-harder","Code Smarter, Not Harder",[651,193015,193016],{},"After the keynote I went to the speaker lounge to get everything ready for my talk because I was in the 2nd slot today. My talk was titled \"Code Smarter, Not Harder. AI Powered Dev Hacks for all\". I really enjoy giving this talk where I get to share some of the workflows I use AI for every day. Chances are you probably know about a lot of them but if I can introduce you to 1 or 2 new things I have done my job.",[651,193018,193019],{},"First off I saw the registrations for my talk and I was like this can't be right. Sure enough I got into my room and there was standing room only. We even had a bunch of folks sitting on the ground in the front. I was blown away by the interest and support. I thought the talk went really well and based on the questions and comments I received throughout the conference, so did you ☺️",[651,193021,193022,193026],{},[660,193023],{"alt":193024,"src":193025},"Standing Room Only","/images/newsletter/2025/05/27/standing-room-only.jpeg",[660,193027],{"alt":193028,"src":193029},"Full Room","/images/newsletter/2025/05/27/full-room.jpeg",[651,193031,193032],{},"This talk was recorded and will eventually be posted online. When it does I will let you know as long as you are subscribed here or on Twitter.",[5909,193034,193036],{"id":193035},"spring-ai-workshop-day-1","Spring AI Workshop Day 1",[651,193038,193039],{},"After my talk I had a quick lunch break and a chance to prep for the Spring AI workshop. Mark Pollack was running this workshop and asked myself and another colleague if we wanted to help out. I jumped at the opportunity and Mark was nice enough to let me run point on it from the beginning. I have been doing similar workshops on an introduction to Spring AI for the last year so I had plenty of material to share with the group. I thought we brought the right level of introduction for this workshop and attendees were able to get up and running and write their own Spring AI applications within the first hour.",[651,193041,193042,193046],{},[660,193043],{"alt":193044,"src":193045},"Workshop Photo 1","/images/newsletter/2025/05/27/workshop-1.jpeg",[660,193047],{"alt":193048,"src":193049},"Workshop Photo 2","/images/newsletter/2025/05/27/workshop-2.jpeg",[651,193051,193052],{},"I want to just say a big thank you to Sergi Almar and all the volunteers at Spring I/O. Thank you for including me in such a wonderful conference and congrats on running such an amazing event. In the closing ceremony Sergi let us know that the current venue they have been at for the last 10 years or so was getting demolished to make room for a new one. That meant they had to find a new venue for next year. Thankfully Sergi was able to secure one and Spring I/O will be back in 2026 🥳",[651,193054,193055],{},[660,193056],{"alt":193057,"src":193058},"Spring I/O Group Photo","/images/newsletter/2025/05/27/save-the-date.png",[4542,193060,193062],{"id":193061},"spring-ai-10-goes-ga","Spring AI 1.0 Goes GA",[651,193064,193065],{},[660,193066],{"alt":193067,"src":193068},"Spring AI Logo","/images/newsletter/2025/05/27/spring-ai-logo.png",[651,193070,193071],{},"Spring AI dominated conversations at Spring I/O this year, and for good reason. After navigating 2+ years of development against the rapidly evolving AI landscape, Spring AI 1.0 finally reached general availability last week. The team deliberately took their time, not just because AI is a moving target, but because they wanted to nail the foundational architecture before committing to API stability. That patience paid off with a rock-solid 1.0 release that won't introduce breaking changes as the ecosystem matures.",[651,193073,193074],{},"This isn't just another framework release. It's a comprehensive platform that transforms how we integrate AI into enterprise applications. Spring AI's \"Augmented LLM\" approach goes far beyond simple request-response interactions, offering support for 20 different AI models from Anthropic to ZhiPu through a portable ChatClient API that handles multi-modal inputs and structured JSON responses. What makes this release special is the production-ready feature set: vector store abstractions supporting 20 databases, a lightweight ETL pipeline for data ingestion, sophisticated RAG implementations, and conversational memory management with JDBC, Cassandra, and Neo4j support. A feature I really love is the Advisor API, an interceptor chain that lets you modify prompts dynamically by injecting retrieved data and conversation history, essentially turning your AI interactions into intelligent, context-aware conversations. Plus, with built-in observability through Micrometer, you can finally monitor model latency, token usage, and costs in production environments where \"hope and good vibes\" aren't sufficient monitoring strategies.",[651,193076,193077,193078,193082],{},"If you want to learn more you can check out this ",[812,193079,24879],{"href":193080,"rel":193081},"https://spring.io/blog/2025/05/20/spring-ai-1-0-GA-released",[816],". I'm also working on a comprehensive tutorial for YouTube that will teach you everything you need to know to get up and running with Spring AI. I should have more on this the next time you hear from me on this newsletter.",[651,193084,193085],{},[2939,193086,187762],{},[651,193088,193089],{},"I'm a big fan of Claude Code, and now you can use it directly in the terminal in IntelliJ with some extra features that we didn't have before.",[187764,193091],{"id":193092},"1926590686373007576",[651,193094,193095],{},"Speaking of Claude and Anthropic, they released 2 new models last week.",[187764,193097],{"id":193098},"1925593896329019640",[4542,193100,188139],{"id":157703},[651,193102,188297],{},[651,193104,187950,193105,69920,193107,193109],{},[41107,193106],{},[41107,193108],{},[812,193110,53869],{"href":82688,"rel":193111},[816],{"title":674,"searchDepth":790,"depth":790,"links":193113},[193114,193119,193120],{"id":192974,"depth":790,"text":192975,"children":193115},[193116,193117,193118],{"id":192987,"depth":892,"text":192988},{"id":193012,"depth":892,"text":193013},{"id":193035,"depth":892,"text":193036},{"id":193061,"depth":790,"text":193062},{"id":157703,"depth":790,"text":188139},{"slug":192974,"date":193122},"2025-05-27T00:00:00.000Z","/newsletter/2025/05/27/spring-io-2025-recap",{"title":192944,"description":192949},"newsletter/2025/05/27/spring-io-2025-recap","oXTRRFJW5eHsfaqasWH-4gBeSVWPOAomZCpPrqx4_No",{"id":193128,"title":193129,"body":193130,"description":193333,"extension":793,"meta":193334,"navigation":797,"path":193337,"seo":193338,"stem":193339,"__hash__":193340},"content/newsletter/2025/06/02/spring-ai-course-preview.md","Spring AI Course, MCP Security and my talk from Spring I/O",{"type":648,"value":193131,"toc":193325},[193132,193140,193143,193148,193154,193158,193166,193169,193174,193206,193212,193218,193223,193229,193233,193241,193244,193246,193251,193256,193272,193277,193291,193296,193299,193302,193306,193309,193312,193314,193316],[651,193133,193134,193135,193139],{},"Happy Monday and welcome to another edition of the newsletter. I'm happy to announce that I have finished the last chapter in my book, ",[812,193136,193138],{"href":187486,"rel":193137},[816],"The Fundamentals of Software Engineering",". The last chapter was on AI, a subject I really love talking about. The real challenge with this chapter was not putting an entire book into a chapter and focusing on what software developers need to know and answering the question I keep getting asked which is \"Will AI replace me?\"",[651,193141,193142],{},"This doesn't mean that it is finished and ready to go to print, but it's a huge milestone to say the least. We still have a lot of edits to go through and a couple of start-to-finish reads to make sure everything is flowing correctly, but we are really happy with where it is. In fact, our editor sent along this message which made me smile and made me so happy because I have poured so much of my knowledge and experience into this book.",[1004,193144,193145],{},[651,193146,193147],{},"I've completed a beginning-to-end review of the book so far. I have to say...I wish I had this book when I graduated from college.",[651,193149,193150,193151,184565],{},"Other than that, travel is ramping down for me which is really exciting because I get to focus on a few things that I have on my plate. I have a quick customer visit in Chicago next week and other than that I don't have anything planned until KCDC and SpringOne in August. After that, travel is going to ramp up in the fall because for whatever reason I have been accepted to a bunch of events that I'm excited about. As always, if you want to find out where I will be speaking next you can check out the ",[812,193152,120451],{"href":120449,"rel":193153},[816],[4542,193155,193157],{"id":193156},"master-spring-ai-build-intelligent-java-applications","Master Spring AI: Build Intelligent Java Applications",[651,193159,193160,193161,193165],{},"I have really enjoyed traveling to customers and conferences and talking about AI over the last year. I love talking about everything that has been happening in this rapidly evolving space. One of the topics I have really enjoyed talking about is Spring AI. This new project in the Spring ecosystem has been in the works for almost 2 years now and recently ",[812,193162,193164],{"href":193080,"rel":193163},[816],"Spring AI 1.0 GA"," was released.",[651,193167,193168],{},"I have released a ton of content around Spring AI on my YouTube channel but there is a need for an update because of everything that has happened in the framework over the last year or so. With that I'm happy to announce that I'm recording a new masterclass on everything you need to know to get up and running with 1.0. I will be recording the videos this week and hope to release it soon. The only decision I have left is should I release it as one long course or break it up into multiple videos. If you have any feedback on this or what you would like to see in this course let me know.",[651,193170,193171],{},[2939,193172,193173],{},"What You'll Learn:",[5316,193175,193176,193182,193188,193194,193200],{},[5332,193177,193178,193181],{},[2939,193179,193180],{},"AI Essentials for Java Developers"," - Understand LLMs, prompt engineering techniques, and how to choose the right model for your needs",[5332,193183,193184,193187],{},[2939,193185,193186],{},"Spring AI Deep Dive"," - Go beyond simple API calls to leverage Spring AI's powerful features including streaming responses, structured outputs, and multimodal capabilities (images, audio)",[5332,193189,193190,193193],{},[2939,193191,193192],{},"Real-World Implementation"," - Work with both proprietary (OpenAI, Anthropic) and open-source models using Ollama and Docker",[5332,193195,193196,193199],{},[2939,193197,193198],{},"Advanced Techniques"," - Master Retrieval Augmented Generation (RAG), build custom tools and functions, and explore the Model Context Protocol (MCP) for cross-language tool sharing",[5332,193201,193202,193205],{},[2939,193203,193204],{},"Production Best Practices"," - Handle memory management, implement proper testing strategies for non-deterministic outputs, and understand pricing/token optimization",[651,193207,193208,193211],{},[2939,193209,193210],{},"Who This Is For:"," Java developers who want to integrate AI capabilities into their applications without switching languages or frameworks. No prior AI experience required - we'll cover everything from \"What is AI?\" to building sophisticated AI-powered features.",[651,193213,193214,193217],{},[2939,193215,193216],{},"Key Takeaway:"," Spring AI is more than just a wrapper for LLM APIs - it's a complete framework that brings the full power of modern AI to the Java ecosystem. By the end of this course, you'll be confidently building intelligent applications that leverage the latest in generative AI technology.",[651,193219,193220],{},[7300,193221,193222],{},"Get hands-on with practical examples, real-world tools integration, and production-ready code you can immediately apply to your projects.",[651,193224,193225,193226,664],{},"If you want to get your hands on this new course which will be made available for free on YouTube pay attention to this newsletter and make sure you're ",[812,193227,164098],{"href":101295,"rel":193228},[816],[4542,193230,193232],{"id":193231},"securing-spring-ai-mcp-servers-with-oauth-2","Securing Spring AI MCP Servers with OAuth 2",[651,193234,193235,193236,193240],{},"You can't go anywhere on the internet right now without seeing a video or reading an article about Model Context Protocol (MCP). I keep reading about security vulnerabilities in MCP servers and it is something we need to think about. I decided to record a video over on the Spring Developer YouTube channel last week based on an ",[812,193237,52469],{"href":193238,"rel":193239},"https://spring.io/blog/2025/05/19/spring-ai-mcp-client-oauth2",[816]," from my friend and colleague Daniel Garnier-Moiroux on securing MCP servers with OAuth 2 and Spring Security.",[651,193242,193243],{},"In this video, I walk through how to lock down your Spring AI Model Context Protocol (MCP) servers using OAuth 2 authentication. With developers rushing to build MCP servers, security is often overlooked—but exposing these endpoints without proper authentication is asking for trouble.",[5909,193245,192544],{"id":187412},[651,193247,193248,193250],{},[2939,193249,11142],{},": MCP servers are just like REST APIs. If you're exposing tools or data you don't want public, you need proper security measures in place.",[651,193252,193253,193255],{},[2939,193254,11155],{},": Using Spring Security with OAuth 2, you can quickly add authentication to your MCP servers. The tutorial demonstrates:",[5316,193257,193258,193261,193266,193269],{},[5332,193259,193260],{},"Adding Spring Authorization Server and Resource Server dependencies",[5332,193262,193263,193264],{},"Configuring OAuth 2 client credentials in ",[676,193265,16242],{},[5332,193267,193268],{},"Setting up a basic security configuration that requires authentication for all requests",[5332,193270,193271],{},"Testing with curl commands and the MCP Inspector tool",[651,193273,193274,2391],{},[2939,193275,193276],{},"What's Covered",[5316,193278,193279,193282,193285,193288],{},[5332,193280,193281],{},"Converting an unsecured weather MCP server to require OAuth 2 tokens",[5332,193283,193284],{},"Using client credentials grant for machine-to-machine authentication",[5332,193286,193287],{},"Testing secured endpoints with bearer tokens",[5332,193289,193290],{},"Configuring CORS for browser-based testing tools",[651,193292,193293,193295],{},[2939,193294,140744],{},": The article mentions future enhancements like implementing authorization code flow for user authentication and role-based access control (RBAC) for fine-grained permissions.",[651,193297,193298],{},"This is based on an excellent article by Daniel Garnier-Moiroux on the Spring blog. If you're building MCP servers in 2025, implementing proper security isn't optional—it's essential.",[5988,193300],{"id":193301},"gBUnLYFwwyE",[4542,193303,193305],{"id":193304},"code-smarter-not-harder-at-spring-io","Code Smarter, Not Harder at Spring I/O",[651,193307,193308],{},"A couple of weeks ago I had the privilege of attending and speaking at one of my favorite conferences, Spring I/O. One of the talks I gave was on some of my AI powered dev hacks I use in my day-to-day workflows. You are probably using some of these already but my hope with this talk is to share what I'm doing and hopefully you learn 1 or 2 things that you can incorporate into your daily workflows. You can find the recording below and as always I would love your feedback.",[5988,193310],{"id":193311},"koYCcvPvaY0",[4542,193313,188139],{"id":157703},[651,193315,188297],{},[651,193317,187950,193318,69920,193320,193322],{},[41107,193319],{},[41107,193321],{},[812,193323,53869],{"href":82688,"rel":193324},[816],{"title":674,"searchDepth":790,"depth":790,"links":193326},[193327,193328,193331,193332],{"id":193156,"depth":790,"text":193157},{"id":193231,"depth":790,"text":193232,"children":193329},[193330],{"id":187412,"depth":892,"text":192544},{"id":193304,"depth":790,"text":193305},{"id":157703,"depth":790,"text":188139},"Happy Monday and welcome to another edition of the newsletter. I'm happy to announce that I have finished the last chapter in my book, The Fundamentals of Software Engineering. The last chapter was on AI, a subject I really love talking about. The real challenge with this chapter was not putting an entire book into a chapter and focusing on what software developers need to know and answering the question I keep getting asked which is \"Will AI replace me?\"",{"slug":193335,"date":193336},"spring-ai-course-preview","2025-06-02T12:00:00.000Z","/newsletter/2025/06/02/spring-ai-course-preview",{"title":193129,"description":193333},"newsletter/2025/06/02/spring-ai-course-preview","eJszBZsxgygogXdRCde_XkoM5BoTwKC8Z3L_kDDQoY4",{"id":193342,"title":193343,"body":193344,"description":193348,"extension":793,"meta":193575,"navigation":797,"path":193578,"seo":193579,"stem":193580,"__hash__":193581},"content/newsletter/2025/06/16/spring-office-hours-to-spring-ai-mastery.md","From Spring Office Hours to Spring AI Mastery",{"type":648,"value":193345,"toc":193562},[193346,193349,193355,193358,193364,193367,193369,193374,193382,193386,193389,193392,193396,193405,193408,193412,193415,193418,193422,193425,193431,193434,193437,193440,193444,193447,193450,193508,193511,193514,193546,193549,193551,193553],[651,193347,193348],{},"Happy Monday and welcome to another edition of the newsletter. I want to start by extending a belated Happy Father's Day to all the dads out there. For me, it was a day filled with both happiness and sadness. On one hand, becoming a dad has always been my dream, and I couldn't be more blessed with my two little girls who fill my heart daily. On the other hand, this was my first Father's Day without my dad, who passed away late last year. I'm grateful for the time we spent together and take comfort knowing we'll meet again someday. Love you, Dad.",[651,193350,193351],{},[660,193352],{"alt":193353,"src":193354},"Happy Father's Day Message","/images/newsletter/2025/06/16/happy-father-s-day-message-with-a-touching-tribute.png",[651,193356,193357],{},"Last week, I spent time in Chicago with a customer. While it was a quick trip, it proved highly productive. I had the opportunity to deliver two presentations that were both well-received. I began by discussing what's new in Spring, recapping everything we've gained in the Spring Boot 3.x line. I focused on this because Spring Boot 3.3 just released in May and will be the last minor release in the 3.x line. After that, we explored what's coming in Spring Boot 3.4. I also presented on building intelligent applications in Java with Spring AI.",[651,193359,193360,193361,664],{},"Additionally, I participated in a Spring One Tour over two days, where I presented on Spring AI and Spring for GraphQL. I absolutely love discussing all the exciting projects in the Spring ecosystem, and it was a fantastic week. This concludes my travel schedule until KCDC in mid-August, and I'm looking forward to some downtime to catch up on various projects. After that conference, I have a packed schedule through October, so I'll spend part of my summer preparing for all the amazing trips ahead. As always, if you're curious about my upcoming speaking engagements, you can check out my ",[812,193362,120451],{"href":120449,"rel":193363},[816],[651,193365,193366],{},"In this edition of the newsletter, I want to update you on what we've been doing on Spring Office Hours. I also spent the better part of last week preparing my comprehensive Spring AI course, which I'll be releasing on YouTube soon.",[4542,193368,97345],{"id":97344},[651,193370,193371],{},[660,193372],{"alt":97345,"src":193373},"/images/newsletter/2025/06/16/streamyard-thumbnail_talking_heads.png",[651,193375,193376,193377,193381],{},"DaShaun and I have been having a lot of fun with Spring Office Hours. It's really hard to believe that we are halfway through season 4 already but here we are. We have been doing our best to bring you some really great guests and cover a wide variety of topics. If you're new to Spring Office Hours we (usually) live stream every Monday from 1-2 EDT. You can catch the replays on the ",[812,193378,193380],{"href":101239,"rel":193379},[816],"Spring Developer YouTube channel"," and we turn this into a podcast so you can listen to use wherever you get your podcasts. In case you have missed the last few episodes here is what we have been up to:",[5909,193383,193385],{"id":193384},"s4e14-spring-ai-10-ga-release-with-mark-pollak-and-christian-tzolov","S4E14 - Spring AI 1.0 GA Release with Mark Pollak and Christian Tzolov",[651,193387,193388],{},"Join Dan Vega and DaShaun Carter as they celebrate a major milestone in the Spring ecosystem with the general availability release of Spring AI 1.0. In this episode, the hosts welcome Spring AI team members Mark Pollak and Christian Tzolov to discuss the incredible journey from conception to GA release. They'll explore the powerful features that make Spring AI the go-to framework for building AI-powered applications, dive into Model Context Protocol (MCP) capabilities, and share exciting insights about what's on the horizon for Spring AI. Whether you're new to AI development or a seasoned practitioner, this episode offers valuable perspectives on integrating AI into Spring applications and the future of enterprise AI development.",[5988,193390],{"id":193391},"Lp8-baWZYos",[5909,193393,193395],{"id":193394},"s4e15-exposing-the-interview-process-with-taylor-desseyn","S4E15 - Exposing the Interview Process with Taylor Desseyn",[651,193397,193398,193399,193404],{},"Join Dan Vega and DaShaun Carter as they welcome Taylor Desseyn, VP of Global Community at ",[812,193400,193403],{"href":193401,"rel":193402},"https://torc.dev",[816],"torc.dev"," and founder of Dads in Tech. Fresh from his presentation \"Exposing the Interview Process\" at Spring I/O 2025 in Barcelona, Taylor shares unfiltered insights from interviewing thousands of engineers and helping hundreds of companies scale their engineering teams. The hosts dive deep into the current job market realities, interview best practices, and how developers can navigate today's challenging hiring landscape. Learn practical strategies to stand out in your job search and discover what's really happening behind the scenes in tech hiring.",[5988,193406],{"id":193407},"z53y6ajYkRo",[5909,193409,193411],{"id":193410},"s4e16-spring-grpc-with-dave-syer-and-chris-bono","S4E16 - Spring gRPC with Dave Syer and Chris Bono",[651,193413,193414],{},"Join Dan Vega and DaShaun Carter for the latest updates from the Spring Ecosystem. In this episode, Dan and DaShaun are joined by Spring team members Dave Syer and Chris Bono to explore the official Spring gRPC project. At the end of this episode, you will understand how Spring gRPC provides a Spring-friendly API for developing high-performance gRPC applications, including server and client autoconfiguration, Spring Boot integration, and streamlined Protocol Buffer handling. The hosts will demonstrate how to build gRPC services with Spring's familiar dependency injection patterns and discuss the benefits of gRPC's HTTP/2-based communication over traditional REST APIs.",[5988,193416],{"id":193417},"Klw1x6mtCSc",[5909,193419,193421],{"id":193420},"spring-ai-course","Spring AI Course",[651,193423,193424],{},"Exciting news! I've been working hard behind the scenes on something special, and I wanted you to be the first to know about it.",[651,193426,193427,193428,664],{},"After a ton of prep work, I'm heading into the studio this week to record a brand-new, comprehensive YouTube course: ",[2939,193429,193430],{},"AI for Java Developers: Building Intelligent Applications with Spring AI",[651,193432,193433],{},"The world of software is rapidly adopting Artificial Intelligence, and it's clear that building AI-powered apps is a critical new skill for Java developers. This course is my answer to that. It's designed specifically for the enterprise Java developer, skipping the deep ML theory to focus on practical, hands-on coding with the new Spring AI 1.0 framework.",[651,193435,193436],{},"As a subscriber to this newsletter, you'll get the exclusive first announcement the moment it goes live.",[651,193438,193439],{},"To give you a sneak peek at what's in store, here's the full agenda for the course.",[4542,193441,193443],{"id":193442},"course-agenda-️","Course Agenda 🗺️",[651,193445,193446],{},"This course is packed with practical lessons to take you from the basics to advanced real-world applications.",[5909,193448,193449],{"id":38484},"What You Will Learn:",[5316,193451,193452,193458,193466,193472,193478,193492,193498],{},[5332,193453,193454,193457],{},[2939,193455,193456],{},"AI Fundamentals for Developers:"," Get a quick introduction to the core concepts of AI, Machine Learning, Deep Learning, and LLMs, explained from a developer's point of view—no complex math required.",[5332,193459,193460,193463,193464,664],{},[2939,193461,193462],{},"Getting Started with Spring AI:"," We'll walk through setting up your environment, getting API keys, and building your first application with Spring AI's ",[676,193465,122385],{},[5332,193467,193468,193471],{},[2939,193469,193470],{},"Mastering Prompt Engineering:"," Learn how to \"talk\" to AI models effectively. We'll cover everything from simple prompts to advanced techniques like few-shot prompting, chain-of-thought, and using XML tags for complex requests.",[5332,193473,193474,193477],{},[2939,193475,193476],{},"Core Spring AI Features:"," Dive deep into the powerful features of Spring AI, including prompt templates, getting structured output (like JSON), and exploring multimodal capabilities with text, images, and audio.",[5332,193479,193480,193483,193484,193487,193488,193491],{},[2939,193481,193482],{},"Solving LLM Limitations:"," Learn how to overcome common LLM challenges like stale data and hallucinations using powerful patterns like ",[2939,193485,193486],{},"Retrieval Augmented Generation (RAG)"," to connect to your own documents and ",[2939,193489,193490],{},"Tool Calling"," to integrate with external APIs.",[5332,193493,193494,193497],{},[2939,193495,193496],{},"Proprietary vs. Open Source Models:"," Understand the landscape of AI models. We'll discuss how to choose the right one for your job and how to run open-source models locally using tools like Ollama.",[5332,193499,193500,193503,193504,193507],{},[2939,193501,193502],{},"Observability & Evaluation:"," You'll learn how to monitor your AI applications to track costs and performance, and how to use Spring AI's ",[676,193505,193506],{},"Evaluator"," tools to test for factual accuracy and relevance.",[4542,193509,193510],{"id":187412},"Key Takeaways 🏆",[651,193512,193513],{},"By the end of this course, you'll be able to:",[5316,193515,193516,193522,193528,193534,193540],{},[5332,193517,193518,193521],{},[2939,193519,193520],{},"Build practical AI features"," into your Spring Boot applications.",[5332,193523,193524,193527],{},[2939,193525,193526],{},"Connect LLMs to your own data"," using the RAG pattern.",[5332,193529,193530,193533],{},[2939,193531,193532],{},"Create AI agents"," that can use external tools to perform actions.",[5332,193535,193536,193539],{},[2939,193537,193538],{},"Confidently select, test, and monitor"," AI models for production use.",[5332,193541,193542,193545],{},[2939,193543,193544],{},"Think like an AI engineer",", applying best practices to create reliable and intelligent systems.",[651,193547,193548],{},"I can't wait to get this course out to you. Keep an eye on your inbox for the official release announcement!",[4542,193550,188139],{"id":157703},[651,193552,188297],{},[651,193554,187950,193555,69920,193557,193559],{},[41107,193556],{},[41107,193558],{},[812,193560,53869],{"href":82688,"rel":193561},[816],{"title":674,"searchDepth":790,"depth":790,"links":193563},[193564,193570,193573,193574],{"id":97344,"depth":790,"text":97345,"children":193565},[193566,193567,193568,193569],{"id":193384,"depth":892,"text":193385},{"id":193394,"depth":892,"text":193395},{"id":193410,"depth":892,"text":193411},{"id":193420,"depth":892,"text":193421},{"id":193442,"depth":790,"text":193443,"children":193571},[193572],{"id":38484,"depth":892,"text":193449},{"id":187412,"depth":790,"text":193510},{"id":157703,"depth":790,"text":188139},{"slug":193576,"date":193577},"spring-office-hours-to-spring-ai-mastery","2025-06-16T12:00:00.000Z","/newsletter/2025/06/16/spring-office-hours-to-spring-ai-mastery",{"title":193343,"description":193348},"newsletter/2025/06/16/spring-office-hours-to-spring-ai-mastery","11AndXlKHByqQsl6Fhjp2n1uSXP6XBQht80iFAB1cmI",{"id":193583,"title":193584,"body":193585,"description":193589,"extension":793,"meta":193931,"navigation":797,"path":193936,"seo":193937,"stem":193938,"__hash__":193939},"content/newsletter/2025/06/30/ai-for-java-developers-course.md","AI for Java Developers: From Zero to Production in 5.5 Hours",{"type":648,"value":193586,"toc":193920},[193587,193590,193593,193596,193599,193602,193606,193609,193612,193618,193624,193629,193643,193648,193662,193667,193684,193689,193703,193706,193712,193715,193718,193721,193724,193728,193734,193737,193745,193751,193754,193758,193761,193767,193772,193796,193800,193803,193810,193814,193833,193837,193843,193847,193853,193859,193863,193866,193871,193874,193882,193886,193889,193892,193895,193898,193901,193904,193906,193911],[651,193588,193589],{},"Happy Monday and welcome to another edition of the newsletter. It feels like yesterday that we entered the month of June and just like that June is almost over. We are full swing into summer and I'm loving every single part of it.",[651,193591,193592],{},"This past weekend we had an opportunity to spend some time in the Columbus OH area for a wedding. I have never actually been to Dublin and I can say this is one of the most underrated cities in Ohio. Such a beautiful area with some really nice walkable areas for everyone.",[187764,193594],{"id":193595},"1939648690622460006",[651,193597,193598],{},"Next week I'll be on a beach in my happy place on Hilton Head Island in South Carolina so I probably won't have any updates over the next couple of weeks. I think about you a lot and how lucky I am to be a part of this community and the opportunity I have to affect so many people. I hope you are having a great day. If you get a chance take a couple of minutes today try and think about what you are grateful for.",[651,193600,193601],{},"This week's newsletter is really exciting for me. I was able to wrap up and publish a course that has been a long time in the works. I'll also tell you about an MCP server I published and a conversation I had with Dan Dobrin on Spring Office Hours.",[4542,193603,193605],{"id":193604},"ai-for-java-developers-course-is-now-live","AI for Java Developers Course is now Live",[651,193607,193608],{},"I'm happy to announce that my latest course is now available for FREE on YouTube. I took everything that I have learned over the last 12-18 months and poured it into one of my most comprehensive courses yet.",[651,193610,193611],{},"I'm often asked with all the momentum in the Python space for AI, what does this mean for Java developers. If you want to get into Machine Learning & Data Science, Python is a great language to learn. But if all you want to do is consume these Large Language Models, you would be hard pressed to find a better place to do this than Java. Java is the language of the enterprise and integration with AI is now a requirement.",[651,193613,193614,193617],{},[2939,193615,193616],{},"Here's what makes this course different:"," Instead of just showing you how to make REST calls to OpenAI, you'll learn how to solve the real problems that emerge when building production AI applications. We dive deep into prompt engineering, tackle LLM limitations head-on, and explore the Swiss Army knife of solutions that Spring AI provides.",[651,193619,193620,193621],{},"🎯 ",[2939,193622,193623],{},"What you'll learn:",[651,193625,193626],{},[2939,193627,193628],{},"AI Foundations & Spring AI Mastery",[5316,193630,193631,193634,193637,193640],{},[5332,193632,193633],{},"AI fundamentals (ML, deep learning, LLMs) for Java developers",[5332,193635,193636],{},"Spring AI architecture and portable API design",[5332,193638,193639],{},"Prompt engineering techniques from basic to advanced",[5332,193641,193642],{},"Multimodal AI: text, image, and audio generation",[651,193644,193645],{},[2939,193646,193647],{},"Overcoming LLM Limitations",[5316,193649,193650,193653,193656,193659],{},[5332,193651,193652],{},"Prompt guarding and stuffing strategies",[5332,193654,193655],{},"Retrieval Augmented Generation (RAG) with vector databases",[5332,193657,193658],{},"Function calling and tool integration",[5332,193660,193661],{},"Model Context Protocol (MCP) servers for reusable AI components",[651,193663,193664],{},[2939,193665,193666],{},"Production-Ready Development",[5316,193668,193669,193672,193675,193678,193681],{},[5332,193670,193671],{},"Local vs cloud models (cost optimization & security)",[5332,193673,193674],{},"Open source models with Ollama and Docker",[5332,193676,193677],{},"Observability with Prometheus and Grafana dashboards",[5332,193679,193680],{},"Testing strategies for deterministic and non-deterministic AI responses",[5332,193682,193683],{},"Chat memory and conversation management",[651,193685,193686],{},[2939,193687,193688],{},"Real-World Applications",[5316,193690,193691,193694,193697,193700],{},[5332,193692,193693],{},"Building intelligent REST APIs and streaming responses",[5332,193695,193696],{},"Structured output for type-safe AI responses",[5332,193698,193699],{},"Multiple model integration in single applications",[5332,193701,193702],{},"Enterprise security and deployment considerations",[651,193704,193705],{},"This isn't just theory. Every concept includes hands-on coding with Spring Boot, so you'll walk away with practical skills you can implement immediately in your Java applications.",[651,193707,193708,193711],{},[2939,193709,193710],{},"The best part?"," You'll build everything using the Java and Spring tools you already know and love. No need to learn Python or become a data scientist. This course bridges the gap between traditional Java development and the AI revolution.",[651,193713,193714],{},"From zero to building intelligent apps in 5.5+ hours of hands-on content. This isn't just another AI course. It's your complete roadmap to becoming an AI-powered Java developer who can ship real applications that matter.",[651,193716,193717],{},"Ready to level up your career and join the AI revolution as a Java developer?",[651,193719,193720],{},"Watch the full course below 👇🏻",[5988,193722],{"id":193723},"FzLABAppJfM",[4542,193725,193727],{"id":193726},"beehiiv-mcp-server","Beehiiv MCP Server",[651,193729,193730,193731,664],{},"While I was putting the Spring AI course together I thought it would be a good opportunity to talk about a real life use case for an MCP server I built. This newsletter that you are reading right now is thanks to a wonderful platform called ",[812,193732,187645],{"href":187520,"rel":193733},[816],[651,193735,193736],{},"I author my newsletter in Beehiiv, and then I publish it to my website. My website requires the content to be in Markdown but it also requires some special markdown blocks. I have images, YouTube embeds and Tweets all which require a specific format. In the past this was a very manual process. Now I use the Beehiiv MCP server to pull my latest newsletter, convert it to the specific markdown and then write the file and images in the correct location.",[651,193738,193739,193740,193744],{},"If you want to check out the source code for the MCP server you can find it ",[812,193741,18263],{"href":193742,"rel":193743},"https://github.com/danvega/beehiiv-mcp-server",[816],". An interesting feature of this repo is the native images. Not everyone who wants to use this MCP server will have Java installed or know how to use it the JAR. I added a GitHub action that will publish native images for Windows, MacOS, and Linux.",[651,193746,193747],{},[660,193748],{"alt":193749,"src":193750},"MCP Server Architecture","/images/newsletter/2025/06/30/beehiiv-mcp-server.png",[651,193752,193753],{},"If you're a fellow Beehiiv author and find this useful feedback is welcome.",[4542,193755,193757],{"id":193756},"spring-office-hours-s4e17-spring-ai-google-gemini-beyond-the-demo","Spring Office Hours S4E17: Spring AI & Google Gemini Beyond the Demo",[5909,193759,193760],{"id":187412},"Key Takeaways",[651,193762,193763,193766],{},[2939,193764,193765],{},"Model Selection Philosophy",": Don't chase every new model release. Choose based on your specific use case - long context windows for document work, cost efficiency for high throughput, or performance for confined tasks.",[651,193768,193769,2391],{},[2939,193770,193771],{},"Google's AI Ecosystem Simplified",[5316,193773,193774,193784,193790],{},[5332,193775,193776,193783],{},[2939,193777,193778],{},[812,193779,193782],{"href":193780,"rel":193781},"https://Gemini.Google.com",[816],"Gemini.Google.com",": Consumer chatbot",[5332,193785,193786,193789],{},[2939,193787,193788],{},"AI Studio",": Developer playground with advanced controls",[5332,193791,193792,193795],{},[2939,193793,193794],{},"Vertex AI",": Enterprise platform supporting Gemini + 10,000+ Hugging Face models",[5909,193797,193799],{"id":193798},"major-technical-highlights","Major Technical Highlights",[5259,193801,138710],{"id":193802},"cost-optimization",[651,193804,193805,193806,193809],{},"Google's ",[2939,193807,193808],{},"implicit context caching"," automatically reduces costs when reusing documents across queries. Gemini Flash costs just $0.10/$0.40 per million tokens vs competitors at $75/$150.",[5259,193811,193813],{"id":193812},"mcp-vs-a2a-standards","MCP vs A2A Standards",[5316,193815,193816,193821,193827],{},[5332,193817,193818,193820],{},[2939,193819,150571],{},": Connects agents to internal tools/APIs (great for controlled environments)",[5332,193822,193823,193826],{},[2939,193824,193825],{},"A2A",": Enables secure agent-to-agent communication across organizations",[5332,193828,193829,193832],{},[2939,193830,193831],{},"Big news",": Google donated A2A to Linux Foundation today, making it an open standard",[5259,193834,193836],{"id":193835},"enterprise-reality-check","Enterprise Reality Check",[651,193838,193839,193842],{},[2939,193840,193841],{},"Java developers ARE AI developers."," No need to re-platform to Python. The new unified GenAI SDK provides GA Java support with Spring Boot integration and native image compatibility.",[5909,193844,193846],{"id":193845},"whats-new","What's New",[651,193848,193849,193852],{},[2939,193850,193851],{},"Agent Development Kit (ADK)",": Open-source Java framework for building multi-agent workflows with Spring Boot integration and visual debugging.",[651,193854,193855,193858],{},[2939,193856,193857],{},"Serverless AI",": Deploy Gemma 3 on Cloud Run for pay-per-use pricing and sub-second startup times - perfect for private document processing without sharing data externally.",[5909,193860,193862],{"id":193861},"bottom-line","Bottom Line",[651,193864,193865],{},"We're shifting from AI experimentation to production implementation. Java developers have everything needed to build robust, cost-effective AI applications today without abandoning existing infrastructure.",[651,193867,193868],{},[2939,193869,193870],{},"Watch the full replay below for live demonstrations and deeper technical discussions.",[5988,193872],{"id":193873},"x7df-U4qJe4",[651,193875,193876,193877,193881],{},"This week on ",[812,193878,97345],{"href":193879,"rel":193880},"https://www.youtube.com/watch?v=A0H8i9qc_8M",[816]," I sit down with Craig Walls as we do a little AI show and tell and chat about Craig's upcoming book, Spring AI in Action.",[651,193883,193884],{},[2939,193885,187762],{},[651,193887,193888],{},"I spent some time last week exploring the Spring AI codebase and attempting to add a new feature.",[187764,193890],{"id":193891},"1938666620823425058",[651,193893,193894],{},"Google launched it's new Gemini CLI which is open source!",[187764,193896],{"id":193897},"1937860467843625124",[651,193899,193900],{},"I have found myself using Claude Code more than any other tool out there and I really enjoyed this conversation between Riley and Micky.",[187764,193902],{"id":193903},"1937698759644971276",[4542,193905,188139],{"id":157703},[651,193907,188142,193908,664],{},[812,193909,51474],{"href":51472,"rel":193910},[816],[651,193912,187950,193913,69920,193915,193917],{},[41107,193914],{},[41107,193916],{},[812,193918,53869],{"href":82688,"rel":193919},[816],{"title":674,"searchDepth":790,"depth":790,"links":193921},[193922,193923,193924,193930],{"id":193604,"depth":790,"text":193605},{"id":193726,"depth":790,"text":193727},{"id":193756,"depth":790,"text":193757,"children":193925},[193926,193927,193928,193929],{"id":187412,"depth":892,"text":193760},{"id":193798,"depth":892,"text":193799},{"id":193845,"depth":892,"text":193846},{"id":193861,"depth":892,"text":193862},{"id":157703,"depth":790,"text":188139},{"slug":193932,"date":193933,"tags":193934,"newsletter":797,"published":797},"ai-for-java-developers-course","2025-06-30T00:56:06.000Z",[126904,36422,123549,22510,7077,193935,150571,67441],"LLM","/newsletter/2025/06/30/ai-for-java-developers-course",{"title":193584,"description":193589},"newsletter/2025/06/30/ai-for-java-developers-course","TFemwIBvL_58JvI18PtiALf37k_gSSbbmU2Z3UJKfAM",{"id":193941,"title":193942,"body":193943,"description":194063,"extension":793,"meta":194064,"navigation":797,"path":194068,"seo":194069,"stem":194070,"__hash__":194071},"content/newsletter/2025/07/21/from-vacation-brain-to-workflow-gains.md","From Vacation Brain to Workflow Gains: Two AI Tools Worth Your Time",{"type":648,"value":193944,"toc":194057},[193945,193948,193962,193965,193969,193972,193979,193982,193988,193998,194001,194005,194008,194011,194014,194024,194027,194029,194032,194035,194038,194041,194043,194048],[651,193946,193947],{},"Happy Monday and welcome to another edition of the newsletter. I'm back after a nice vacation to Hilton Head with the family and feeling refreshed. I spent some time last week cleaning up Notion which is a tool that I use to track everything I do. What this means for you is that I have a good backlog of what videos I will be working on next and I published 2 videos last week that I will tell you more about in this newsletter.",[651,193949,193950,193951,23212,193954,193957,193958,664],{},"Next up for me is going to be working on more videos and preparing for a bunch of talks over the next few months. In August I will be speaking at ",[812,193952,173081],{"href":97034,"rel":193953},[816],[812,193955,82113],{"href":192466,"rel":193956},[816],". These are a couple of my favorite conferences to attend and speak at. KCDC brings a wide variety of sessions and some of my favorite BBQ. SpringOne at VMware Explore is our flagship conference and a great opportunity for me to connect with so many customers and developers from the community. As always if you want to find out where I will be speaking next you can check out ",[812,193959,193961],{"href":120449,"rel":193960},[816],"this page on my website",[651,193963,193964],{},"In this edition of the newsletter I will be focusing on the 2 new videos I published last week.",[4542,193966,193968],{"id":193967},"from-chatbot-to-code-collaborator-why-claude-code-changed-my-development-workflow","From Chatbot to Code Collaborator: Why Claude Code Changed My Development Workflow",[651,193970,193971],{},"Forget everything you think you know about AI coding tools. Claude Code isn't another chatbot that spits out random snippets you'll spend hours debugging. It's an agentic command-line tool that actually understands your project context, respects your existing workflows, and builds complete applications without breaking your codebase.",[651,193973,193974,193975,193978],{},"To demonstrate Claude Code's capabilities, I walked through building a Spring Boot application that integrates with the JSON Placeholder service, implements caching, and follows production-ready patterns. What impressed me wasn't just the code quality—it was the intelligent planning mode that lets you iterate on architecture before writing a single line. Instead of immediately generating code, Claude Code helps you think through the implementation strategy, respects your coding guidelines (stored in a simple ",[676,193976,193977],{},"claude.md"," file), and even runs integration tests to verify everything works correctly.",[651,193980,193981],{},"The game-changer here is the sandboxed approach: Claude Code operates within your project directory, understanding your entire codebase structure while maintaining security boundaries. Combined with smart features like automatic branch management, cost-effective pricing through Pro plans ($20/month vs. expensive API usage), and seamless IDE integration, it feels like having an experienced pair programmer who never gets tired and always follows your team's conventions.",[651,193983,193984,193987],{},[2939,193985,193986],{},"Key takeaway:"," Stop trying to replace your existing tools with AI—enhance them instead. Use start.spring.io to scaffold projects, then let Claude Code handle the implementation details while you focus on architecture and business logic.",[651,193989,193990,193993,193994,193997],{},[2939,193991,193992],{},"Try this:"," Start with project exploration (",[676,193995,193996],{},"explain this project to me",") before writing any code. Use planning mode (Shift+Tab) to iterate on implementation strategies, and always work in branches for complex features.",[5988,193999],{"id":194000},"-jYlp2oJh_o",[4542,194002,194004],{"id":194003},"building-real-world-mcp-servers-my-beehiiv-newsletter-automation","Building Real-World MCP Servers: My Beehiiv Newsletter Automation",[651,194006,194007],{},"Last week I tackled a workflow problem that had been bugging me for months. I write my newsletters in Beehiiv because I love the editor, but getting that content onto my personal website required tedious manual conversion to very specific markdown formats. Every single time, I'd copy content, reformat images, convert YouTube embeds to my custom components, and handle Twitter embeds - it was eating up way too much time.",[651,194009,194010],{},"So I built something to fix it: a custom MCP server that connects to the Beehiiv API. Now I can simply tell Claude Desktop \"grab my latest draft and create the markdown file for my website,\" and it handles everything automatically. The MCP server provides tools for getting posts, publications, and subscriptions, while Claude does the heavy lifting of content transformation, spell checking, and file creation. What used to be a 30-minute manual process now takes seconds, and the result is exactly the markdown structure my Vue/Nuxt site needs.",[651,194012,194013],{},"This isn't just another \"hello world\" MCP tutorial - it's a real automation I use every week that saves me significant time. The video walks through the complete code, shows the Claude Desktop integration, and demonstrates the actual workflow in action.",[651,194015,194016,194023],{},[2939,194017,194018],{},[812,194019,194022],{"href":194020,"rel":194021},"https://youtu.be/bP9dhrI1wKA",[816],"Watch the full walkthrough here"," to see how I built this MCP server and get inspired for your own workflow automations. If you've been wondering about practical MCP applications beyond simple examples, this one's for you!",[5988,194025],{"id":194026},"bP9dhrI1wKA",[4542,194028,187762],{"id":165429},[651,194030,194031],{},"I don't understand why this is happening.",[187764,194033],{"id":194034},"1945874259814822110",[651,194036,194037],{},"Working on improving my video creation process so I began documenting it this week. Happy to share more on this if anyone is interested.",[187764,194039],{"id":194040},"1945846240878506240",[4542,194042,188139],{"id":157703},[651,194044,188142,194045,188147],{},[812,194046,51474],{"href":51472,"rel":194047},[816],[651,194049,187950,194050,69920,194052,194054],{},[41107,194051],{},[41107,194053],{},[812,194055,53869],{"href":82688,"rel":194056},[816],{"title":674,"searchDepth":790,"depth":790,"links":194058},[194059,194060,194061,194062],{"id":193967,"depth":790,"text":193968},{"id":194003,"depth":790,"text":194004},{"id":165429,"depth":790,"text":187762},{"id":157703,"depth":790,"text":188139},"Happy Monday and welcome to another edition of the newsletter. I'm back after a nice vacation to Hilton Head with the family and feeling refreshed. I spent some time last week cleaning up Notion which is a tool that I use to track everything I do.",{"slug":194065,"date":194066,"tags":194067,"newsletter":797,"published":797},"from-vacation-brain-to-workflow-gains","2025-07-21",[126904,149783,150571,7077,123549],"/newsletter/2025/07/21/from-vacation-brain-to-workflow-gains",{"title":193942,"description":194063},"newsletter/2025/07/21/from-vacation-brain-to-workflow-gains","_Yvn8d4e6d5Z_e0pCupv35FCMd9-9UOxRLTqlummb2U",["Reactive",194073],{"$scolor-mode":194074,"$ssite-config":194077},{"preference":194075,"value":194075,"unknown":797,"forced":194076},"system",false,{"_context":194078,"_priority":194081,"env":31448,"name":194085,"url":53869},{"name":194079,"env":194075,"url":194080},"vendorEnv","runtimeEnv",{"name":194082,"env":194083,"url":194084},-5,-15,0,"vega-nuxt",["Set"],["ShallowReactive",194088],{"searchBlogPosts":-1,"blog-posts":-1},"/blog/2?tag=spring"]